diff --git a/PyExpLabSys/__init__.py b/PyExpLabSys/__init__.py index f5a52e8b..7e82ecaa 100644 --- a/PyExpLabSys/__init__.py +++ b/PyExpLabSys/__init__.py @@ -4,10 +4,10 @@ equipment and data parsers for various softwares file types, both particularly pertaining to surface science.""" -__author__ = 'CINF ' -__email__ = 'knielsen@fysik.dtu.dk' -__version__ = '0.1.dev0' -__website__ = 'https://github.com/CINF/PyExpLabSys' -__license__ = 'GNU GPL3' -__description__ = 'The PyExpLabSys package contains drivers for various experimental equipment and data parsers for various softwares file types, both particularly pertaining to surface science.' -__uri__ = 'https://github.com/CINF/PyExpLabSys' +__author__ = "CINF " +__email__ = "knielsen@fysik.dtu.dk" +__version__ = "0.1.dev0" +__website__ = "https://github.com/CINF/PyExpLabSys" +__license__ = "GNU GPL3" +__description__ = "The PyExpLabSys package contains drivers for various experimental equipment and data parsers for various softwares file types, both particularly pertaining to surface science." +__uri__ = "https://github.com/CINF/PyExpLabSys" diff --git a/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_commands.py b/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_commands.py index 2d1bf8c8..5aeafd08 100644 --- a/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_commands.py +++ b/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_commands.py @@ -1,25 +1,27 @@ import minimalmodbus from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) import time class Motor(object): - def __init__(self, port, slave_adress=1): - self.instrument = minimalmodbus.Instrument(port, slave_adress, mode = minimalmodbus.MODE_RTU) + self.instrument = minimalmodbus.Instrument( + port, slave_adress, mode=minimalmodbus.MODE_RTU + ) self.instrument.serial.baudrate = 9600 self.instrument.serial.bytesize = 8 self.instrument.serial.parity = minimalmodbus.serial.PARITY_EVEN self.instrument.serial.stopbits = 1 - self.instrument.serial.timeout = 1 #seconds + self.instrument.serial.timeout = 1 # seconds """ Maintenance """ def get_status(self): try: response = self.instrument.read_long(126, signed=True) - status = int('{0:b}'.format(response)[-6]) + status = int("{0:b}".format(response)[-6]) except IndexError: status = 0 return status @@ -27,7 +29,7 @@ def get_status(self): def get_home_end(self): try: response = self.instrument.read_long(126, signed=True) - status = int('{0:b}'.format(response)[-5]) + status = int("{0:b}".format(response)[-5]) except IndexError: status = 0 return status @@ -35,11 +37,11 @@ def get_home_end(self): def get_move(self): try: response = self.instrument.read_long(126, signed=True) - status = int('{0:b}'.format(response)[-14]) + status = int("{0:b}".format(response)[-14]) except IndexError: status = 0 return status - + def get_alarm(self): response = self.instrument.read_long(128, signed=True) response = hex(response)[2:] @@ -52,7 +54,7 @@ def reset_alarm(self): def get_alarm_record(self): alarm_record = [] for i in range(10): - response = self.instrument.read_long(130+2*i, signed=True) + response = self.instrument.read_long(130 + 2 * i, signed=True) alarm_record.append(hex(response)[2:]) return alarm_record @@ -63,74 +65,70 @@ def clear_alarm_record(self): def get_alarm_status(self): try: response = self.instrument.read_long(126, signed=True) - status = int('{0:b}'.format(response)[-8]) + status = int("{0:b}".format(response)[-8]) except IndexError: status = 0 return status - + def clear_ETO(self): - " Clear the ETO (External Torque Off) mode " + "Clear the ETO (External Torque Off) mode" self.instrument.write_long(416, 1, signed=True) self.instrument.write_long(416, 0, signed=True) - def save_RAM_to_non_volatile(self): - " Writes the parameters saved in the RAM to the non-volatile memory, which can be rewritten approx. 100,000 times " + "Writes the parameters saved in the RAM to the non-volatile memory, which can be rewritten approx. 100,000 times" self.instrument.write_long(402, 1, signed=True) time.sleep(0.1) self.instrument.write_long(402, 0, signed=True) - + def load_non_volatile_to_RAM(self): - " Read the parameters saved in the non-volatile memory to the RAM. NB! All operation data and parameters saved in the RAM are overwritten " + "Read the parameters saved in the non-volatile memory to the RAM. NB! All operation data and parameters saved in the RAM are overwritten" self.instrument.write_long(400, 1, signed=True) self.instrument.write_long(400, 0, signed=True) def load_RAM_to_direct(self): - " Read and write the operation data number to be used in direct data operation " + "Read and write the operation data number to be used in direct data operation" operation_number = self.instrument.read_long(88, signed=True) self.instrument.write_long(88, operation_number, signed=True) - """ Commands required for direct data operation. Set values are stored in RAM """ """ Read """ - + def get_operation_data_number(self): response = self.instrument.read_long(88, signed=True) return response - + def get_operation_trigger(self): response = self.instrument.read_long(102, signed=True) return response - - + def get_operation_type(self): response = self.instrument.read_long(90, signed=True) return response - - + def get_operating_speed(self): response = self.instrument.read_long(94, signed=True) return response def get_starting_changing_rate(self): - response = self.instrument.read_long(96, signed=True)/1000 + response = self.instrument.read_long(96, signed=True) / 1000 return response - + def get_stopping_deceleration(self): - response = self.instrument.read_long(98, signed=True)/1000 + response = self.instrument.read_long(98, signed=True) / 1000 return response - + def get_operating_current(self): - response = self.instrument.read_long(100, signed=True)/10 + response = self.instrument.read_long(100, signed=True) / 10 return response - + def get_position(self): - response = self.instrument.read_long(92, signed=True)/100 + response = self.instrument.read_long(92, signed=True) / 100 return response def get_command_position(self): - response = self.instrument.read_long(198, signed=True)/100 + response = self.instrument.read_long(198, signed=True) / 100 return response def get_group_id(self): @@ -149,19 +147,19 @@ def set_operation_type(self, setting): self.instrument.write_long(90, setting, signed=True) def set_position(self, setting): - self.instrument.write_long(92, int(setting*100), signed=True) + self.instrument.write_long(92, int(setting * 100), signed=True) def set_operating_speed(self, setting): self.instrument.write_long(94, setting, signed=True) - + def set_starting_changing_rate(self, setting): - self.instrument.write_long(96, setting*1000, signed=True) - + self.instrument.write_long(96, setting * 1000, signed=True) + def set_stopping_deceleration(self, setting): - self.instrument.write_long(98, setting*1000, signed=True) - + self.instrument.write_long(98, setting * 1000, signed=True) + def set_operating_current(self, setting): - self.instrument.write_long(100, setting*10, signed=True) + self.instrument.write_long(100, setting * 10, signed=True) def set_group_id(self, parent_slave): self.instrument.write_long(48, parent_slave, signed=True) @@ -171,24 +169,23 @@ def home(self): self.instrument.write_long(124, 16, signed=True) self.instrument.write_long(124, 0, signed=True) - def stop(self): self.instrument.write_long(124, 32, signed=True) self.instrument.write_long(124, 0, signed=True) """ Commands required for initial data operation """ """ Input is made by operation data number. Below are functions to read from and write to the registers corresponding to operation data No. 0 to 63. Settable items are: Type, position, operating speed, starting/changing rate, stop, and operating current """ - + """ Read """ def get_initial_group_id(self): response = self.instrument.read_long(5012, signed=True) return response - + def get_initial_position(self, operation_number): - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_initial_operating_speed(self): response = self.instrument.read_long(1152, signed=True) return response @@ -196,29 +193,29 @@ def get_initial_operating_speed(self): def get_initial_starting_speed(self): response = self.instrument.read_long(644, signed=True) return response - + def get_initial_starting_changing_rate(self): - response = self.instrument.read_long(1536, signed=True)/1000 + response = self.instrument.read_long(1536, signed=True) / 1000 return response - + def get_initial_stopping_deceleration(self): - response = self.instrument.read_long(1664, signed=True)/1000 + response = self.instrument.read_long(1664, signed=True) / 1000 return response - + def get_initial_operating_current(self): - response = self.instrument.read_long(1792, signed=True)/10 + response = self.instrument.read_long(1792, signed=True) / 10 return response - + def get_initial_operation_type(self, operation_number): - response = self.instrument.read_long(1280+operation_number*2, signed=True) + response = self.instrument.read_long(1280 + operation_number * 2, signed=True) return response - + def get_initial_positive_software_limit(self): - response = self.instrument.read_long(904, signed=True)/100 + response = self.instrument.read_long(904, signed=True) / 100 return response - + def get_initial_negative_software_limit(self): - response = self.instrument.read_long(906, signed=True)/100 + response = self.instrument.read_long(906, signed=True) / 100 return response def get_initial_electronic_gear_A(self): @@ -238,89 +235,89 @@ def get_initial_zhome_starting_speed(self): return response def get_initial_zhome_acceleration_deceleration(self): - response = self.instrument.read_long(690, signed=True)/1000 + response = self.instrument.read_long(690, signed=True) / 1000 return response - + def get_Home_location(self): operation_number = 1 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_ISS_location(self): operation_number = 2 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response def get_Mg_XPS_location(self): operation_number = 3 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_Al_XPS_location(self): operation_number = 4 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_SIG_location(self): operation_number = 5 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_HPC_location(self): operation_number = 6 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response - + def get_Baking_location(self): operation_number = 7 - response = self.instrument.read_long(1024+operation_number*2, signed=True)/100 + response = self.instrument.read_long(1024 + operation_number * 2, signed=True) / 100 return response """ Write """ def set_initial_position(self, operation_number, setting): - """ Setting range: -2,147,438,648 to 2,147,438,648 steps """ - self.instrument.write_long(1024+operation_number*2, setting*100, signed=True) - + """Setting range: -2,147,438,648 to 2,147,438,648 steps""" + self.instrument.write_long(1024 + operation_number * 2, setting * 100, signed=True) + def set_initial_operating_speed(self, setting): - """ Setting range: -4,000,000 to 4,000,000 Hz """ + """Setting range: -4,000,000 to 4,000,000 Hz""" self.instrument.write_long(1152, int(setting), signed=True) - + def set_initial_starting_speed(self, setting): - """ Setting range: 0 to 4,000,000 Hz """ + """Setting range: 0 to 4,000,000 Hz""" self.instrument.write_long(644, int(setting), signed=True) - + def set_initial_starting_changing_rate(self, setting): - """ Setting range: 1 to 1,000,000,000 (unit is kHz/s, s or ms/kHz) """ - self.instrument.write_long(1536, int(setting*1000), signed=True) - + """Setting range: 1 to 1,000,000,000 (unit is kHz/s, s or ms/kHz)""" + self.instrument.write_long(1536, int(setting * 1000), signed=True) + def set_initial_stopping_deceleration(self, setting): - """ Setting range: 1 to 1,000,000,000 (unit is kHz/s, s or ms/kHz) """ - self.instrument.write_long(1664, int(setting*1000), signed=True) - + """Setting range: 1 to 1,000,000,000 (unit is kHz/s, s or ms/kHz)""" + self.instrument.write_long(1664, int(setting * 1000), signed=True) + def set_initial_operating_current(self, setting): - """ Setting range: 0 to 1,000 (1=0.1 %) """ - self.instrument.write_long(1792, int(setting*10), signed=True) + """Setting range: 0 to 1,000 (1=0.1 %)""" + self.instrument.write_long(1792, int(setting * 10), signed=True) def set_initial_operation_type(self, operation_number, setting): - self.instrument.write_long(1280+operation_number*2, setting, signed=True) - + self.instrument.write_long(1280 + operation_number * 2, setting, signed=True) + def set_initial_group_id(self, parent_slave): - """ Setting range: -1: Disable (no group transmission, initial value); 1 to 31: Group ID 1 to 31. NB! Do not use 0 """ + """Setting range: -1: Disable (no group transmission, initial value); 1 to 31: Group ID 1 to 31. NB! Do not use 0""" self.instrument.write_long(5012, int(parent_slave), signed=True) def set_initial_positive_software_limit(self, setting): - self.instrument.write_long(904, int(setting*100), signed=True) - + self.instrument.write_long(904, int(setting * 100), signed=True) + def set_initial_negative_software_limit(self, setting): - self.instrument.write_long(906, int(setting*100), signed=True) + self.instrument.write_long(906, int(setting * 100), signed=True) def set_initial_electronic_gear_A(self, setting): self.instrument.write_long(896, int(setting), signed=True) def set_initial_electronic_gear_B(self, setting): self.instrument.write_long(898, int(setting), signed=True) - + def set_initial_zhome_operating_speed(self, setting): self.instrument.write_long(688, int(setting), signed=True) @@ -328,28 +325,40 @@ def set_initial_zhome_starting_speed(self, setting): self.instrument.write_long(692, int(setting), signed=True) def set_initial_zhome_acceleration_deceleration(self, setting): - self.instrument.write_long(690, int(setting*1000), signed=True) - + self.instrument.write_long(690, int(setting * 1000), signed=True) + def set_ISS_location(self, setting): operation_number = 2 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) def set_Mg_XPS_location(self, setting): operation_number = 3 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) - + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) + def set_Al_XPS_location(self, setting): operation_number = 4 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) - + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) + def set_SIG_location(self, setting): operation_number = 5 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) - + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) + def set_HPC_location(self, setting): operation_number = 6 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) - + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) + def set_Baking_location(self, setting): operation_number = 7 - self.instrument.write_long(1024+operation_number*2, int(setting*100), signed=True) \ No newline at end of file + self.instrument.write_long( + 1024 + operation_number * 2, int(setting * 100), signed=True + ) diff --git a/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_socket.py b/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_socket.py index cb863b4b..a3453b34 100644 --- a/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_socket.py +++ b/PyExpLabSys/apps/Stepper_motor_control/Modbus_comm_socket.py @@ -1,6 +1,7 @@ import minimalmodbus import serial from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) from PyExpLabSys.common.sockets import DateDataPullSocket from PyExpLabSys.common.sockets import DataPushSocket @@ -11,26 +12,28 @@ from datetime import datetime, timedelta import json + # The read_parameter function calls functions from the Modbus_comm_commands for the x, y and z motor def read_parameter(parameter): - data = [0,0,0] - data[0] = eval('motx.' + parameter + '()') - data[1] = eval('moty.' + parameter + '()') - data[2] = eval('motz.' + parameter + '()') + data = [0, 0, 0] + data[0] = eval("motx." + parameter + "()") + data[1] = eval("moty." + parameter + "()") + data[2] = eval("motz." + parameter + "()") return data + # The read_parameter_all function calls functions from the Modbus_comm_commands for the x, y, z and z2 motor def read_parameter_all(parameter): - data = [0,0,0,0] - data[0] = eval('motx.' + parameter + '()') - data[1] = eval('moty.' + parameter + '()') - data[2] = eval('motz.' + parameter + '()') - data[3] = eval('motz2.' + parameter + '()') + data = [0, 0, 0, 0] + data[0] = eval("motx." + parameter + "()") + data[1] = eval("moty." + parameter + "()") + data[2] = eval("motz." + parameter + "()") + data[3] = eval("motz2." + parameter + "()") return data + # The motor_socket calss is a threaded class that runs the socket and receive and send data class motor_socket(threading.Thread): - def __init__(self, datasocket, pushsocket): threading.Thread.__init__(self) self.datasocket = datasocket @@ -38,211 +41,271 @@ def __init__(self, datasocket, pushsocket): # Initialise checks that are used to stop and pause threaded processes self.quit = False self.pause = False - - + # The run function is a threaded function that runs undtil closed by a keyboardinterrupt # Reads parameters from the motor drivers and send it to the data pull socket def run(self): - command_check = 'none' - alarm_checkx = 'none' - alarm_checky = 'none' - alarm_checkz = 'none' - alarm_type = {'0': 'No alarm', '10': 'Excessive position deviation', '20': 'Overcurrent', '21': 'Main circuit overheat', - '22': 'Overvoltage (AC/DC power input driver)', '23': 'Main power supply OFF', '25': 'Undervoltage', - '26': 'Motor overheat', '28': 'Sensor error', '2A': 'ABZO sensor communication error', - '30': 'Overload', '31': 'Overspeed', '33': 'Absolute position error', - '34': 'Command pulse error', '41': 'EEPROM error', '42': 'Sensor error at power on', - '43': 'Rotation error at power on', '44': 'Encoder EEPROM error', '45': 'Motor combination error', - '4A': 'Return-to-home incomplete', '51': 'Regeneration unit overheat (only AC power input driver)', - '53': 'Emergency stop circuit error', '60': '±LS both sides active', '61': 'Reverse ±LS connection', - '62': 'Return-to-home operation error', '63': 'No HOMES', '64': 'TIM, Z, SLIT signal error', - '66': 'Hardware overtravel', '67': 'Software overtravel', '68': 'Emergency stop', - '6A': 'Return-to-home operation offset error', '6D': 'Mechanical overtravel', '70': 'Operation data error', - '71': 'Electronic gear setting error', '72': 'Wrap setting error', '81': 'Network bus error', - '83': 'Communication switch setting error', '84': 'RS-485 communication error', - '85': 'RS-485 communication timeout', '8E': 'Network converter error', 'F0': 'CPU error'} + command_check = "none" + alarm_checkx = "none" + alarm_checky = "none" + alarm_checkz = "none" + alarm_type = { + "0": "No alarm", + "10": "Excessive position deviation", + "20": "Overcurrent", + "21": "Main circuit overheat", + "22": "Overvoltage (AC/DC power input driver)", + "23": "Main power supply OFF", + "25": "Undervoltage", + "26": "Motor overheat", + "28": "Sensor error", + "2A": "ABZO sensor communication error", + "30": "Overload", + "31": "Overspeed", + "33": "Absolute position error", + "34": "Command pulse error", + "41": "EEPROM error", + "42": "Sensor error at power on", + "43": "Rotation error at power on", + "44": "Encoder EEPROM error", + "45": "Motor combination error", + "4A": "Return-to-home incomplete", + "51": "Regeneration unit overheat (only AC power input driver)", + "53": "Emergency stop circuit error", + "60": "±LS both sides active", + "61": "Reverse ±LS connection", + "62": "Return-to-home operation error", + "63": "No HOMES", + "64": "TIM, Z, SLIT signal error", + "66": "Hardware overtravel", + "67": "Software overtravel", + "68": "Emergency stop", + "6A": "Return-to-home operation offset error", + "6D": "Mechanical overtravel", + "70": "Operation data error", + "71": "Electronic gear setting error", + "72": "Wrap setting error", + "81": "Network bus error", + "83": "Communication switch setting error", + "84": "RS-485 communication error", + "85": "RS-485 communication timeout", + "8E": "Network converter error", + "F0": "CPU error", + } while not self.quit: while self.pause: time.sleep(0.1) - self.command_position = read_parameter('get_command_position') - self.datasocket.set_point('command_position', self.command_position) - self.status = read_parameter_all('get_status') - self.datasocket.set_point('status', self.status) - self.home_end = read_parameter('get_home_end') - self.datasocket.set_point('home_end', self.home_end) - self.move = read_parameter('get_move') - self.datasocket.set_point('move', self.move) - self.operating_speed = read_parameter('get_operating_speed') - self.alarm_status = read_parameter('get_alarm_status') + self.command_position = read_parameter("get_command_position") + self.datasocket.set_point("command_position", self.command_position) + self.status = read_parameter_all("get_status") + self.datasocket.set_point("status", self.status) + self.home_end = read_parameter("get_home_end") + self.datasocket.set_point("home_end", self.home_end) + self.move = read_parameter("get_move") + self.datasocket.set_point("move", self.move) + self.operating_speed = read_parameter("get_operating_speed") + self.alarm_status = read_parameter("get_alarm_status") if self.alarm_status[0] == 1: current_alarm = motx.get_alarm() if current_alarm != alarm_checkx: alarm_checkx = current_alarm - message = 'Motor X alarm {}: {}'.format(current_alarm, alarm_type[current_alarm]) - datasocket.set_point_now('message', message) + message = "Motor X alarm {}: {}".format( + current_alarm, alarm_type[current_alarm] + ) + datasocket.set_point_now("message", message) if self.alarm_status[1] == 1: current_alarm = moty.get_alarm() if current_alarm != alarm_checky: alarm_checky = current_alarm - message = 'Motor Y alarm {}: {}'.format(current_alarm, alarm_type[current_alarm]) - datasocket.set_point_now('message', message) + message = "Motor Y alarm {}: {}".format( + current_alarm, alarm_type[current_alarm] + ) + datasocket.set_point_now("message", message) if self.alarm_status[2] == 1: current_alarm = motz.get_alarm() if current_alarm != alarm_checkz: alarm_checkz = current_alarm - message = 'Motor Z alarm {}: {}'.format(current_alarm, alarm_type[current_alarm]) - datasocket.set_point_now('message', message) + message = "Motor Z alarm {}: {}".format( + current_alarm, alarm_type[current_alarm] + ) + datasocket.set_point_now("message", message) # Checks if there has been a new message sent from the GUI # If a new message is recieved it performes various commands # Some threaded functions can be executed and can only be stopped by calling the 'stop' command from the GUI try: - new_update = self.pushsocket.last[1]['command'] + new_update = self.pushsocket.last[1]["command"] if new_update != command_check: command_check = new_update command = new_update[11:] - if command == 'stop': + if command == "stop": self.thread_quit = True motx.stop() moty.stop() motz.stop() - print('Stop button pressed') - datasocket.set_point_now('message', 'Stop button pressed') - if command == 'move_home': + print("Stop button pressed") + datasocket.set_point_now("message", "Stop button pressed") + if command == "move_home": self.thread_quit = False thread = threading.Thread(target=move_home) thread.start() - if command == 'move_ISS': + if command == "move_ISS": self.thread_quit = False - thread = threading.Thread(target=move_chamber, args=('ISS',)) + thread = threading.Thread(target=move_chamber, args=("ISS",)) thread.start() - if command == 'move_Mg_XPS': + if command == "move_Mg_XPS": self.thread_quit = False - thread = threading.Thread(target=move_chamber, args=('Mg_XPS',)) + thread = threading.Thread(target=move_chamber, args=("Mg_XPS",)) thread.start() - if command == 'move_Al_XPS': + if command == "move_Al_XPS": self.thread_quit = False - thread = threading.Thread(target=move_chamber, args=('Al_XPS',)) + thread = threading.Thread(target=move_chamber, args=("Al_XPS",)) thread.start() - if command == 'move_SIG': + if command == "move_SIG": self.thread_quit = False - thread = threading.Thread(target=move_chamber, args=('SIG',)) + thread = threading.Thread(target=move_chamber, args=("SIG",)) thread.start() - if command == 'move_Baking': + if command == "move_Baking": self.thread_quit = False - thread = threading.Thread(target=move_chamber, args=('Baking',)) + thread = threading.Thread(target=move_chamber, args=("Baking",)) thread.start() - if command[0:8] == 'relative': + if command[0:8] == "relative": self.thread_quit = True - if command[8] == 'X': + if command[8] == "X": motx.set_position(float(command[9:])) motx.set_operation_trigger(1) - if command[8] == 'Y': + if command[8] == "Y": moty.set_position(float(command[9:])) moty.set_operation_trigger(1) - if command[8] == 'Z': + if command[8] == "Z": motz.set_position(float(command[9:])) motz.set_operation_trigger(1) - if command[0:8] == 'absolute': + if command[0:8] == "absolute": self.thread_quit = True - if command[8] == 'X': + if command[8] == "X": command_position = self.command_position[0] - motx.set_position((float(command[9:])*100-command_position*100)/100) + motx.set_position( + (float(command[9:]) * 100 - command_position * 100) / 100 + ) motx.set_operation_trigger(1) - if command[8] == 'Y': + if command[8] == "Y": command_position = self.command_position[1] - moty.set_position((float(command[9:])*100-command_position*100)/100) + moty.set_position( + (float(command[9:]) * 100 - command_position * 100) / 100 + ) moty.set_operation_trigger(1) - if command[8] == 'Z': + if command[8] == "Z": command_position = self.command_position[2] - motz.set_position((float(command[9:])*100-command_position*100)/100) + motz.set_position( + (float(command[9:]) * 100 - command_position * 100) / 100 + ) motz.set_operation_trigger(1) - if command[0:17] == 'show_alarm_record': - if command[17] == 'X': + if command[0:17] == "show_alarm_record": + if command[17] == "X": alarm_record = motx.get_alarm_record() - alarm_record.append('X') - alarm_record.append('alarm_record') - datasocket.set_point_now('message', alarm_record) - if command[17] == 'Y': + alarm_record.append("X") + alarm_record.append("alarm_record") + datasocket.set_point_now("message", alarm_record) + if command[17] == "Y": alarm_record = moty.get_alarm_record() - alarm_record.append('Y') - alarm_record.append('alarm_record') - datasocket.set_point_now('message', alarm_record) - if command[17] == 'Z': + alarm_record.append("Y") + alarm_record.append("alarm_record") + datasocket.set_point_now("message", alarm_record) + if command[17] == "Z": alarm_record = motz.get_alarm_record() - alarm_record.append('Z') - alarm_record.append('alarm_record') - datasocket.set_point_now('message', alarm_record) - if command[0:18] == 'clear_alarm_record': - if command[18] == 'X': + alarm_record.append("Z") + alarm_record.append("alarm_record") + datasocket.set_point_now("message", alarm_record) + if command[0:18] == "clear_alarm_record": + if command[18] == "X": motx.clear_alarm_record() - datasocket.set_point_now('message', 'Motor X alarm record reset') - if command[18] == 'Y': + datasocket.set_point_now("message", "Motor X alarm record reset") + if command[18] == "Y": moty.clear_alarm_record() - datasocket.set_point_now('message', 'Motor Y alarm record reset') - if command[18] == 'Z': + datasocket.set_point_now("message", "Motor Y alarm record reset") + if command[18] == "Z": motz.clear_alarm_record() - datasocket.set_point_now('message', 'Motor Z alarm record reset') - if command == 'reset_alarms': + datasocket.set_point_now("message", "Motor Z alarm record reset") + if command == "reset_alarms": motx.reset_alarm() moty.reset_alarm() motz.reset_alarm() - alarm_checkx = 'none' - alarm_checky = 'none' - alarm_checkz = 'none' - datasocket.set_point_now('message', 'Current alarms reset') - if command == 'clear_ETO': + alarm_checkx = "none" + alarm_checky = "none" + alarm_checkz = "none" + datasocket.set_point_now("message", "Current alarms reset") + if command == "clear_ETO": motx.clear_ETO() moty.clear_ETO() motz.clear_ETO() - datasocket.set_point_now('message', 'External Torque Off mode cleared') - if command == 'table_update': + datasocket.set_point_now("message", "External Torque Off mode cleared") + if command == "table_update": self.table_update() - if command[:10] == 'table_save': + if command[:10] == "table_save": data = self.pushsocket.last[1] self.table_save(data) except (TypeError, KeyError): continue - print('Socket closed') + print("Socket closed") # The table_update function reads the parameter and location data of each driver and send it through the pull socket to the GUI # It also send a update_check message to tell the GUI that it has finished def table_update(self): - parameterlist = ['operating_speed', 'starting_speed', 'starting_changing_rate', - 'stopping_deceleration', 'operating_current', 'positive_software_limit', - 'negative_software_limit', 'electronic_gear_A', 'electronic_gear_B', - 'zhome_operating_speed', 'zhome_starting_speed', 'zhome_acceleration_deceleration', - 'group_id'] + parameterlist = [ + "operating_speed", + "starting_speed", + "starting_changing_rate", + "stopping_deceleration", + "operating_current", + "positive_software_limit", + "negative_software_limit", + "electronic_gear_A", + "electronic_gear_B", + "zhome_operating_speed", + "zhome_starting_speed", + "zhome_acceleration_deceleration", + "group_id", + ] for i in parameterlist: - self.datasocket.set_point(i, read_parameter_all('get_initial_' + i)) - - parameterlist = ['ISS', 'Mg_XPS', 'Al_XPS', 'SIG', 'HPC', 'Baking'] + self.datasocket.set_point(i, read_parameter_all("get_initial_" + i)) + + parameterlist = ["ISS", "Mg_XPS", "Al_XPS", "SIG", "HPC", "Baking"] for i in parameterlist: - self.datasocket.set_point(i, read_parameter('get_' + i + '_location')) - self.datasocket.set_point_now('update_check', True) + self.datasocket.set_point(i, read_parameter("get_" + i + "_location")) + self.datasocket.set_point_now("update_check", True) time.sleep(1.5) - self.datasocket.set_point_now('update_check', False) + self.datasocket.set_point_now("update_check", False) # The table_save function saves the data received from to GUI onto each driver def table_save(self, data): data = json.loads(data) - parameterlist = ['operating_speed', 'starting_speed', 'starting_changing_rate', - 'stopping_deceleration', 'operating_current', 'positive_software_limit', - 'negative_software_limit', 'electronic_gear_A', 'electronic_gear_B', - 'zhome_operating_speed', 'zhome_starting_speed', 'zhome_acceleration_deceleration', - 'group_id'] + parameterlist = [ + "operating_speed", + "starting_speed", + "starting_changing_rate", + "stopping_deceleration", + "operating_current", + "positive_software_limit", + "negative_software_limit", + "electronic_gear_A", + "electronic_gear_B", + "zhome_operating_speed", + "zhome_starting_speed", + "zhome_acceleration_deceleration", + "group_id", + ] for i in parameterlist: - eval('motx.' + 'set_initial_' + i + '({})'.format(data[i][0])) - eval('moty.' + 'set_initial_' + i + '({})'.format(data[i][1])) - eval('motz.' + 'set_initial_' + i + '({})'.format(data[i][2])) - eval('motz2.' + 'set_initial_' + i + '({})'.format(data[i][3])) - - parameterlist = ['ISS', 'Mg_XPS', 'Al_XPS', 'SIG', 'HPC', 'Baking'] + eval("motx." + "set_initial_" + i + "({})".format(data[i][0])) + eval("moty." + "set_initial_" + i + "({})".format(data[i][1])) + eval("motz." + "set_initial_" + i + "({})".format(data[i][2])) + eval("motz2." + "set_initial_" + i + "({})".format(data[i][3])) + + parameterlist = ["ISS", "Mg_XPS", "Al_XPS", "SIG", "HPC", "Baking"] for i in parameterlist: self.datasocket.set_point(i, data[i]) - eval('motx.' + 'set_' + i + '_location({})'.format(data[i][0])) - eval('moty.' + 'set_' + i + '_location({})'.format(data[i][1])) - eval('motz.' + 'set_' + i + '_location({})'.format(data[i][2])) - + eval("motx." + "set_" + i + "_location({})".format(data[i][0])) + eval("moty." + "set_" + i + "_location({})".format(data[i][1])) + eval("motz." + "set_" + i + "_location({})".format(data[i][2])) + motx.save_RAM_to_non_volatile() moty.save_RAM_to_non_volatile() motz.save_RAM_to_non_volatile() @@ -252,6 +315,7 @@ def table_save(self, data): motz.load_RAM_to_direct() motz2.load_RAM_to_direct() + # The move_home function moves each motor home # If the x motor position is greater than 350 mm it produces an error # The function sleeps in between movement and checks if a stop signal has been sent from the GUI @@ -262,45 +326,46 @@ def move_home(): xcommand_position = motor_socket.command_position[0] ycommand_position = motor_socket.command_position[1] zcommand_position = motor_socket.command_position[2] - xspeed = motor_socket.operating_speed[0]/100 - yspeed = motor_socket.operating_speed[1]/100 - zspeed = motor_socket.operating_speed[2]/100 + xspeed = motor_socket.operating_speed[0] / 100 + yspeed = motor_socket.operating_speed[1] / 100 + zspeed = motor_socket.operating_speed[2] / 100 if xcommand_position < 350: - delay = (abs(zcommand_position)/zspeed)+1 + delay = (abs(zcommand_position) / zspeed) + 1 motor_socket.pause = True time.sleep(1.5) motz.home() motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Z home') - - delay = (abs(ycommand_position)/yspeed)+1 + datasocket.set_point_now("message", "Z home") + + delay = (abs(ycommand_position) / yspeed) + 1 motor_socket.pause = True time.sleep(1.5) moty.home() motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Y home') + datasocket.set_point_now("message", "Y home") - delay = (abs(xcommand_position)/xspeed)+1 + delay = (abs(xcommand_position) / xspeed) + 1 motor_socket.pause = True time.sleep(1.5) motx.home() motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'X home') + datasocket.set_point_now("message", "X home") elif xcommand_position > 350: - datasocket.set_point_now('message', 'Error: X position is greater than 350 mm') - print('Error') + datasocket.set_point_now("message", "Error: X position is greater than 350 mm") + print("Error") + # The move_chamber function moves to a given location in the chamber # If the x motor position is greater than 350 mm it produces an error @@ -309,122 +374,146 @@ def move_chamber(location): xcommand_position = motor_socket.command_position[0] ycommand_position = motor_socket.command_position[1] zcommand_position = motor_socket.command_position[2] - xspeed = motor_socket.operating_speed[0]/100 - yspeed = motor_socket.operating_speed[1]/100 - zspeed = motor_socket.operating_speed[2]/100 + xspeed = motor_socket.operating_speed[0] / 100 + yspeed = motor_socket.operating_speed[1] / 100 + zspeed = motor_socket.operating_speed[2] / 100 if xcommand_position < 350: - delay = (abs(zcommand_position)/zspeed)+1 + delay = (abs(zcommand_position) / zspeed) + 1 motor_socket.pause = True time.sleep(1.5) - target = read_parameter('get_' + location + '_location') + target = read_parameter("get_" + location + "_location") x_target = target[0] y_target = target[1] z_target = target[2] motz.home() motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Z home') - - delay = (abs(ycommand_position)/yspeed)+1 + datasocket.set_point_now("message", "Z home") + + delay = (abs(ycommand_position) / yspeed) + 1 motor_socket.pause = True time.sleep(1.5) moty.home() motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Y home') - + datasocket.set_point_now("message", "Y home") + motor_socket.pause = True time.sleep(1.5) - motx.set_position((x_target*100-xcommand_position*100)/100) - delay = (abs(x_target-xcommand_position)/xspeed)+1 + motx.set_position((x_target * 100 - xcommand_position * 100) / 100) + delay = (abs(x_target - xcommand_position) / xspeed) + 1 motx.set_operation_trigger(1) motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'X in target position') - + datasocket.set_point_now("message", "X in target position") + motor_socket.pause = True time.sleep(1.5) motz.set_position(z_target) - delay = (abs(z_target)/zspeed)+1 + delay = (abs(z_target) / zspeed) + 1 motz.set_operation_trigger(1) motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Z in target position') - + datasocket.set_point_now("message", "Z in target position") + motor_socket.pause = True time.sleep(1.5) moty.set_position(y_target) - delay = (abs(y_target)/yspeed)+1 + delay = (abs(y_target) / yspeed) + 1 moty.set_operation_trigger(1) motor_socket.pause = False - for i in range(math.ceil(delay)*5): + for i in range(math.ceil(delay) * 5): if motor_socket.thread_quit: return time.sleep(0.2) - datasocket.set_point_now('message', 'Y in target position') - + datasocket.set_point_now("message", "Y in target position") + elif xcommand_position > 350: - datasocket.set_point_now('message', 'Error: X position is greater than 350 mm') - print('Error') + datasocket.set_point_now("message", "Error: X position is greater than 350 mm") + print("Error") # Run when the script is opened # Checks the status of the motors and opens the push and pull socket # When a keyboardinterrupt is produced it send a signal to close the threaded function updating the GUI before closing -if __name__ == '__main__': - - port = '/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FT1EI9CY-if00-port0' - +if __name__ == "__main__": + port = "/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FT1EI9CY-if00-port0" + motx = Motor(port, 1) moty = Motor(port, 2) motz = Motor(port, 3) motz2 = Motor(port, 4) - - status = [False, True] - print('Mot X status: {}'.format(status[motx.get_status()])) - print('Mot Y status: {}'.format(status[moty.get_status()])) - print('Mot Z status: {}'.format(status[motz.get_status()])) - print('Mot Z2 status: {}'.format(status[motz2.get_status()])) - - datasocketlist = ['command_position', 'status', 'target_position', 'home_end', - 'move', 'operating_speed', 'starting_speed', 'starting_changing_rate', - 'stopping_deceleration', 'operating_current', 'positive_software_limit', - 'negative_software_limit', 'electronic_gear_A', 'electronic_gear_B', - 'zhome_operating_speed', 'zhome_starting_speed', 'zhome_acceleration_deceleration', - 'group_id', 'ISS', 'Mg_XPS', 'Al_XPS', 'SIG', 'HPC', 'Baking', 'message', 'update_check'] - - if motx.get_status() == 1 and moty.get_status() == 1 and motz.get_status() == 1 and motz2.get_status() == 1: - datasocket = DateDataPullSocket('motor_controller', datasocketlist, port = 9000) + + status = [False, True] + print("Mot X status: {}".format(status[motx.get_status()])) + print("Mot Y status: {}".format(status[moty.get_status()])) + print("Mot Z status: {}".format(status[motz.get_status()])) + print("Mot Z2 status: {}".format(status[motz2.get_status()])) + + datasocketlist = [ + "command_position", + "status", + "target_position", + "home_end", + "move", + "operating_speed", + "starting_speed", + "starting_changing_rate", + "stopping_deceleration", + "operating_current", + "positive_software_limit", + "negative_software_limit", + "electronic_gear_A", + "electronic_gear_B", + "zhome_operating_speed", + "zhome_starting_speed", + "zhome_acceleration_deceleration", + "group_id", + "ISS", + "Mg_XPS", + "Al_XPS", + "SIG", + "HPC", + "Baking", + "message", + "update_check", + ] + + if ( + motx.get_status() == 1 + and moty.get_status() == 1 + and motz.get_status() == 1 + and motz2.get_status() == 1 + ): + datasocket = DateDataPullSocket("motor_controller", datasocketlist, port=9000) datasocket.start() - - pushsocket = DataPushSocket('motor_push_control', action = 'store_last') + + pushsocket = DataPushSocket("motor_push_control", action="store_last") pushsocket.start() - + motor_socket = motor_socket(datasocket, pushsocket) motor_socket.start() - + try: while not motor_socket.quit: time.sleep(1) except KeyboardInterrupt: - print('Quitting') + print("Quitting") motor_socket.quit = True time.sleep(2) datasocket.stop() pushsocket.stop() else: - print('No communication with the instrument') - - + print("No communication with the instrument") diff --git a/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI.py b/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI.py index e67bcfa8..d3243334 100644 --- a/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI.py +++ b/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI.py @@ -14,21 +14,23 @@ # It will also check if a update is finished to signal the table_update function to write to the tables class update_GUI(QtCore.QThread): data_update = QtCore.pyqtSignal(object) + def __init__(self): QtCore.QThread.__init__(self) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.update_check = False def run(self): - command = 'json_wn' - host_port = ('rasppi134', 9000) + command = "json_wn" + host_port = ("rasppi134", 9000) while True: - self.sock.sendto(command.encode('ascii'), host_port) + self.sock.sendto(command.encode("ascii"), host_port) received = json.loads(self.sock.recv(2048).decode()) - self.update_check = received['update_check'][1] + self.update_check = received["update_check"][1] self.data_update.emit(received) time.sleep(0.2) + # The GUI class is where changes to the GUI are performed # The GUI layout is loaded in from the script stepper_motor_GUI_design class stepper_motor_GUI(object): @@ -38,80 +40,81 @@ def __init__(self): self.MainWindow = QtWidgets.QMainWindow() self.gui = stepper_motor_ui() self.gui.setupUi(self.MainWindow) - self.gui.pushButton.clicked.connect(lambda: self.confirmation_box('home')) - self.gui.pushButton_2.clicked.connect(lambda: self.confirmation_box('ISS')) - self.gui.pushButton_3.clicked.connect(lambda: self.confirmation_box('Mg_XPS')) - self.gui.pushButton_14.clicked.connect(lambda: self.confirmation_box('Al_XPS')) - self.gui.pushButton_11.clicked.connect(lambda: self.confirmation_box('SIG')) - self.gui.pushButton_4.clicked.connect(lambda: self.confirmation_box('HPC')) - self.gui.pushButton_15.clicked.connect(lambda: self.confirmation_box('Baking')) - self.gui.pushButton_6.clicked.connect(lambda: self.send_command('stop')) + self.gui.pushButton.clicked.connect(lambda: self.confirmation_box("home")) + self.gui.pushButton_2.clicked.connect(lambda: self.confirmation_box("ISS")) + self.gui.pushButton_3.clicked.connect(lambda: self.confirmation_box("Mg_XPS")) + self.gui.pushButton_14.clicked.connect(lambda: self.confirmation_box("Al_XPS")) + self.gui.pushButton_11.clicked.connect(lambda: self.confirmation_box("SIG")) + self.gui.pushButton_4.clicked.connect(lambda: self.confirmation_box("HPC")) + self.gui.pushButton_15.clicked.connect(lambda: self.confirmation_box("Baking")) + self.gui.pushButton_6.clicked.connect(lambda: self.send_command("stop")) self.gui.pushButton_5.clicked.connect(self.move_command) - self.gui.pushButton_7.clicked.connect(lambda: self.send_command('reset_alarms')) - self.gui.pushButton_8.clicked.connect(lambda: self.alarm_record('show_alarm_record')) - self.gui.pushButton_10.clicked.connect(lambda: self.alarm_record('clear_alarm_record')) - self.gui.pushButton_9.clicked.connect(lambda: self.send_command('clear_ETO')) - self.gui.pushButton_12.clicked.connect(lambda: self.update_trigger('update')) - self.gui.pushButton_13.clicked.connect(lambda: self.update_trigger('save')) + self.gui.pushButton_7.clicked.connect(lambda: self.send_command("reset_alarms")) + self.gui.pushButton_8.clicked.connect(lambda: self.alarm_record("show_alarm_record")) + self.gui.pushButton_10.clicked.connect(lambda: self.alarm_record("clear_alarm_record")) + self.gui.pushButton_9.clicked.connect(lambda: self.send_command("clear_ETO")) + self.gui.pushButton_12.clicked.connect(lambda: self.update_trigger("update")) + self.gui.pushButton_13.clicked.connect(lambda: self.update_trigger("save")) - self.constant_update = update_GUI() - self.constant_update.data_update.connect(self.update, ) + self.constant_update.data_update.connect( + self.update, + ) self.constant_update.start() self.table_update() self.update_start = False self.save_start = False - self.message = '' - + self.message = "" + # The update function is threaded and update certain status and location information def update(self, data): - Mg_XPS = data['Mg_XPS'] - Al_XPS = data['Al_XPS'] - ISS = data['ISS'] - SIG = data['SIG'] - HPC = data['HPC'] - Baking = data['Baking'] + Mg_XPS = data["Mg_XPS"] + Al_XPS = data["Al_XPS"] + ISS = data["ISS"] + SIG = data["SIG"] + HPC = data["HPC"] + Baking = data["Baking"] - color = ['red', 'green'] - self.gui.label_6.setStyleSheet("background-color: {}".format(color[data['status'][0]])) - self.gui.label_7.setStyleSheet("background-color: {}".format(color[data['status'][1]])) - self.gui.label_8.setStyleSheet("background-color: {}".format(color[data['status'][2]])) - self.gui.label_9.setStyleSheet("background-color: {}".format(color[data['status'][3]])) - if data['move'] == [0,0,0]: + color = ["red", "green"] + self.gui.label_6.setStyleSheet("background-color: {}".format(color[data["status"][0]])) + self.gui.label_7.setStyleSheet("background-color: {}".format(color[data["status"][1]])) + self.gui.label_8.setStyleSheet("background-color: {}".format(color[data["status"][2]])) + self.gui.label_9.setStyleSheet("background-color: {}".format(color[data["status"][3]])) + if data["move"] == [0, 0, 0]: self.gui.label_14.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_14.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == [0,0,0]: + if data["command_position"] == [0, 0, 0]: self.gui.label_15.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_15.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == ISS: + if data["command_position"] == ISS: self.gui.label_16.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_16.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == Mg_XPS: + if data["command_position"] == Mg_XPS: self.gui.label_17.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_17.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == Al_XPS: + if data["command_position"] == Al_XPS: self.gui.label_31.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_31.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == HPC: + if data["command_position"] == HPC: self.gui.label_23.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_23.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == SIG: + if data["command_position"] == SIG: self.gui.label_29.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_29.setStyleSheet("background-color: {}".format(color[0])) - if data['command_position'] == Baking: + if data["command_position"] == Baking: self.gui.label_33.setStyleSheet("background-color: {}".format(color[1])) else: self.gui.label_33.setStyleSheet("background-color: {}".format(color[0])) - self.gui.label_19.setText("X: {} mm".format(data['command_position'][0])) - self.gui.label_20.setText("Y: {} mm".format(data['command_position'][1])) - self.gui.label_21.setText("Z: {} mm".format(data['command_position'][2])) + self.gui.label_19.setText("X: {} mm".format(data["command_position"][0])) + self.gui.label_20.setText("Y: {} mm".format(data["command_position"][1])) + self.gui.label_21.setText("Z: {} mm".format(data["command_position"][2])) # Calls the table update and save function if the buttons are pressed on the GUI if self.update_start == True: self.table_update() @@ -122,34 +125,68 @@ def update(self, data): # Reads the current message from the socket and checks if it is a new message # If alarm_record is received the 10 latest alarms will be displayed in the list widget # If a different message is received it is displayed in the list widget - new_message = '[' + str(datetime.fromtimestamp(data['message'][0]))[11:19] + ']: ' + str(data['message'][1]) - if new_message != self.message and new_message != '[01:00:00]: 0.0': + new_message = ( + "[" + + str(datetime.fromtimestamp(data["message"][0]))[11:19] + + "]: " + + str(data["message"][1]) + ) + if new_message != self.message and new_message != "[01:00:00]: 0.0": self.message = new_message - if self.message[-14:-2] == 'alarm_record': - alarm_record = data['message'][1] - alarm_type = {'0': 'No alarm', '10': 'Excessive position deviation', '20': 'Overcurrent', '21': 'Main circuit overheat', - '22': 'Overvoltage (AC/DC power input driver)', '23': 'Main power supply OFF', '25': 'Undervoltage', - '26': 'Motor overheat', '28': 'Sensor error', '2A': 'ABZO sensor communication error', - '30': 'Overload', '31': 'Overspeed', '33': 'Absolute position error', - '34': 'Command pulse error', '41': 'EEPROM error', '42': 'Sensor error at power on', - '43': 'Rotation error at power on', '44': 'Encoder EEPROM error', '45': 'Motor combination error', - '4A': 'Return-to-home incomplete', '51': 'Regeneration unit overheat (only AC power input driver)', - '53': 'Emergency stop circuit error', '60': '±LS both sides active', '61': 'Reverse ±LS connection', - '62': 'Return-to-home operation error', '63': 'No HOMES', '64': 'TIM, Z, SLIT signal error', - '66': 'Hardware overtravel', '67': 'Software overtravel', '68': 'Emergency stop', - '6A': 'Return-to-home operation offset error', '6D': 'Mechanical overtravel', '70': 'Operation data error', - '71': 'Electronic gear setting error', '72': 'Wrap setting error', '81': 'Network bus error', - '83': 'Communication switch setting error', '84': 'RS-485 communication error', - '85': 'RS-485 communication timeout', '8E': 'Network converter error', 'F0': 'CPU error'} + if self.message[-14:-2] == "alarm_record": + alarm_record = data["message"][1] + alarm_type = { + "0": "No alarm", + "10": "Excessive position deviation", + "20": "Overcurrent", + "21": "Main circuit overheat", + "22": "Overvoltage (AC/DC power input driver)", + "23": "Main power supply OFF", + "25": "Undervoltage", + "26": "Motor overheat", + "28": "Sensor error", + "2A": "ABZO sensor communication error", + "30": "Overload", + "31": "Overspeed", + "33": "Absolute position error", + "34": "Command pulse error", + "41": "EEPROM error", + "42": "Sensor error at power on", + "43": "Rotation error at power on", + "44": "Encoder EEPROM error", + "45": "Motor combination error", + "4A": "Return-to-home incomplete", + "51": "Regeneration unit overheat (only AC power input driver)", + "53": "Emergency stop circuit error", + "60": "±LS both sides active", + "61": "Reverse ±LS connection", + "62": "Return-to-home operation error", + "63": "No HOMES", + "64": "TIM, Z, SLIT signal error", + "66": "Hardware overtravel", + "67": "Software overtravel", + "68": "Emergency stop", + "6A": "Return-to-home operation offset error", + "6D": "Mechanical overtravel", + "70": "Operation data error", + "71": "Electronic gear setting error", + "72": "Wrap setting error", + "81": "Network bus error", + "83": "Communication switch setting error", + "84": "RS-485 communication error", + "85": "RS-485 communication timeout", + "8E": "Network converter error", + "F0": "CPU error", + } for i in range(10): - alarm_record[i] = alarm_record[i] + ' ' + alarm_type[alarm_record[i]] + alarm_record[i] = alarm_record[i] + " " + alarm_type[alarm_record[i]] self.gui.listWidget.addItem("Motor {} alarm record:".format(alarm_record[-2])) for i in range(10): - item = 'Alarm {}: {}'.format(i+1, alarm_record[i]) + item = "Alarm {}: {}".format(i + 1, alarm_record[i]) self.gui.listWidget.addItem("{}".format(item)) else: self.gui.listWidget.addItem("{}".format(self.message)) - + # The confirmation_box function is called when a location button is hit # Displays a pop-out window with an okay and a cancel button # If okay is pressed the send_command function is called @@ -160,18 +197,24 @@ def confirmation_box(self, command): msg.setText("Move to {}".format(command)) msg.setWindowTitle("Confirmation box") msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) - + value = msg.exec_() if value == QtWidgets.QMessageBox.Ok: - command = 'move_' + command + command = "move_" + command self.send_command(command) # Sends a command to the push socket that is then executed on the raspberry pi def send_command(self, command): - host_port = ('rasppi134', 8500) - message = 'raw_wn#command:str:' + '[' + str(datetime.fromtimestamp(time.time()))[11:19].replace(':','-') + '] ' + command - self.sock.sendto(message.encode('ascii'), host_port) - + host_port = ("rasppi134", 8500) + message = ( + "raw_wn#command:str:" + + "[" + + str(datetime.fromtimestamp(time.time()))[11:19].replace(":", "-") + + "] " + + command + ) + self.sock.sendto(message.encode("ascii"), host_port) + # The move_command function is called when the free movement button is pressed # Displays a pop-out window with an okay and cancel button # If okay is pressed the send_command function is called @@ -185,68 +228,79 @@ def move_command(self): msg.setText("{} of motor {}: {} mm".format(operation_type, mot_number, position)) msg.setWindowTitle("Confirmation box") msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) - + value = msg.exec_() if value == QtWidgets.QMessageBox.Cancel: return - if operation_type == 'Relative movement': - command = 'relative' + str(mot_number) + str (position) - if operation_type == 'Absolute movement': - command = 'absolute' + str(mot_number) + str (position) + if operation_type == "Relative movement": + command = "relative" + str(mot_number) + str(position) + if operation_type == "Absolute movement": + command = "absolute" + str(mot_number) + str(position) self.send_command(command) - + # The alarm_record function sends a command to the raspberry pi to show or clear the alarm record # The raspberry pi sends its answer through the pull socket def alarm_record(self, command): - if command == 'show_alarm_record': + if command == "show_alarm_record": mot_number = self.gui.comboBox_4.currentText() command = command + str(mot_number) - if command == 'clear_alarm_record': + if command == "clear_alarm_record": mot_number = self.gui.comboBox_5.currentText() command = command + str(mot_number) self.send_command(command) - + # The update_trigger function will call the table_update or table_save functions without calling them directly # This is done as to alow the screen to become opaque when the functions are being run # PyQt will not update the main GUI layout undtil a given function has been completed # Therefore the functions has to be called via a signal to the threaded update function def update_trigger(self, command): self.MainWindow.setWindowOpacity(0.9) - if command == 'update': + if command == "update": self.update_start = True - if command == 'save': + if command == "save": self.save_start = True - + # The table_update function pdates the table widgets with parameters and locations. # As this functions takes some time for the raspberry pi the function will wait for the pi to send a signal that it is done # Then the function writes the new data into the tables def table_update(self): - self.send_command('table_update') - + self.send_command("table_update") + while self.constant_update.update_check == False: time.sleep(0.1) - + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - command = 'json_wn' - host_port = ('rasppi134', 9000) - self.sock.sendto(command.encode('ascii'), host_port) + command = "json_wn" + host_port = ("rasppi134", 9000) + self.sock.sendto(command.encode("ascii"), host_port) data = json.loads(self.sock.recv(2048).decode()) - parameterlist = ['operating_speed', 'starting_speed', 'starting_changing_rate', - 'stopping_deceleration', 'operating_current', 'positive_software_limit', - 'negative_software_limit', 'electronic_gear_A', 'electronic_gear_B', - 'zhome_operating_speed', 'zhome_starting_speed', 'zhome_acceleration_deceleration', 'group_id'] + parameterlist = [ + "operating_speed", + "starting_speed", + "starting_changing_rate", + "stopping_deceleration", + "operating_current", + "positive_software_limit", + "negative_software_limit", + "electronic_gear_A", + "electronic_gear_B", + "zhome_operating_speed", + "zhome_starting_speed", + "zhome_acceleration_deceleration", + "group_id", + ] for i in range(4): for n in range(13): - item = QtWidgets.QTableWidgetItem('{}'.format(data[parameterlist[n]][i])) + item = QtWidgets.QTableWidgetItem("{}".format(data[parameterlist[n]][i])) item.setTextAlignment(QtCore.Qt.AlignCenter) self.gui.tableWidget.setItem(n, i, item) - parameterlist = ['ISS', 'Mg_XPS', 'Al_XPS', 'SIG', 'HPC', 'Baking'] + parameterlist = ["ISS", "Mg_XPS", "Al_XPS", "SIG", "HPC", "Baking"] for i in range(3): for n in range(6): - item = QtWidgets.QTableWidgetItem('{}'.format(data[parameterlist[n]][i])) + item = QtWidgets.QTableWidgetItem("{}".format(data[parameterlist[n]][i])) item.setTextAlignment(QtCore.Qt.AlignCenter) self.gui.tableWidget_2.setItem(n, i, item) @@ -254,24 +308,44 @@ def table_update(self): # The table_save function reads the data in the tables and sends it to the raspberry pi where it is writen onto the motor drivers def table_save(self): - parameterdic = {'command': '[' + str(datetime.fromtimestamp(time.time()))[11:19].replace(':','-') + '] ' + 'table_save', 'operating_speed': [0,0,0,0], 'starting_speed': [0,0,0,0], 'starting_changing_rate': [0,0,0,0], - 'stopping_deceleration': [0,0,0,0], 'operating_current': [0,0,0,0], 'positive_software_limit': [0,0,0,0], - 'negative_software_limit': [0,0,0,0], 'electronic_gear_A': [0,0,0,0], 'electronic_gear_B': [0,0,0,0], - 'zhome_operating_speed': [0,0,0,0], 'zhome_starting_speed': [0,0,0,0], 'zhome_acceleration_deceleration': [0,0,0,0], - 'group_id': [0,0,0,0], 'ISS': [0,0,0], 'Mg_XPS': [0,0,0], 'Al_XPS': [0,0,0], 'SIG': [0,0,0], 'HPC': [0,0,0], 'Baking': [0,0,0]} + parameterdic = { + "command": "[" + + str(datetime.fromtimestamp(time.time()))[11:19].replace(":", "-") + + "] " + + "table_save", + "operating_speed": [0, 0, 0, 0], + "starting_speed": [0, 0, 0, 0], + "starting_changing_rate": [0, 0, 0, 0], + "stopping_deceleration": [0, 0, 0, 0], + "operating_current": [0, 0, 0, 0], + "positive_software_limit": [0, 0, 0, 0], + "negative_software_limit": [0, 0, 0, 0], + "electronic_gear_A": [0, 0, 0, 0], + "electronic_gear_B": [0, 0, 0, 0], + "zhome_operating_speed": [0, 0, 0, 0], + "zhome_starting_speed": [0, 0, 0, 0], + "zhome_acceleration_deceleration": [0, 0, 0, 0], + "group_id": [0, 0, 0, 0], + "ISS": [0, 0, 0], + "Mg_XPS": [0, 0, 0], + "Al_XPS": [0, 0, 0], + "SIG": [0, 0, 0], + "HPC": [0, 0, 0], + "Baking": [0, 0, 0], + } item = self.gui.tableWidget.item(0, 0) for i in range(4): - for n in range(1,14): - item = self.gui.tableWidget.item(n-1, i) + for n in range(1, 14): + item = self.gui.tableWidget.item(n - 1, i) parameterdic[list(parameterdic.items())[n][0]][i] = float(item.text()) for i in range(3): - for n in range(14,20): - item = self.gui.tableWidget_2.item(n-14, i) + for n in range(14, 20): + item = self.gui.tableWidget_2.item(n - 14, i) parameterdic[list(parameterdic.items())[n][0]][i] = float(item.text()) - message = 'json_wn#' + json.dumps(parameterdic) - host_port = ('rasppi134', 8500) - self.sock.sendto(message.encode('ascii'), host_port) + message = "json_wn#" + json.dumps(parameterdic) + host_port = ("rasppi134", 8500) + self.sock.sendto(message.encode("ascii"), host_port) self.MainWindow.setWindowOpacity(1) diff --git a/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI_design.py b/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI_design.py index 27c915a6..289e5a99 100644 --- a/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI_design.py +++ b/PyExpLabSys/apps/Stepper_motor_control/stepper_motor_GUI_design.py @@ -26,24 +26,24 @@ def setupUi(self, stepper_motor_GUI): self.tab.setObjectName("tab") self.label = QtWidgets.QLabel(self.tab) self.label.setGeometry(QtCore.QRect(20, 0, 201, 41)) - self.label.setStyleSheet("font: 75 20pt \"MS Shell Dlg 2\";") + self.label.setStyleSheet('font: 75 20pt "MS Shell Dlg 2";') self.label.setIndent(-1) self.label.setObjectName("label") self.label_2 = QtWidgets.QLabel(self.tab) self.label_2.setGeometry(QtCore.QRect(20, 40, 31, 41)) - self.label_2.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_2.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_2.setObjectName("label_2") self.label_3 = QtWidgets.QLabel(self.tab) self.label_3.setGeometry(QtCore.QRect(20, 80, 31, 41)) - self.label_3.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_3.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_3.setObjectName("label_3") self.label_4 = QtWidgets.QLabel(self.tab) self.label_4.setGeometry(QtCore.QRect(20, 120, 51, 41)) - self.label_4.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_4.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_4.setObjectName("label_4") self.label_5 = QtWidgets.QLabel(self.tab) self.label_5.setGeometry(QtCore.QRect(20, 160, 51, 41)) - self.label_5.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_5.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_5.setObjectName("label_5") self.label_6 = QtWidgets.QLabel(self.tab) self.label_6.setGeometry(QtCore.QRect(230, 50, 31, 31)) @@ -63,19 +63,19 @@ def setupUi(self, stepper_motor_GUI): self.label_9.setObjectName("label_9") self.label_10 = QtWidgets.QLabel(self.tab) self.label_10.setGeometry(QtCore.QRect(20, 200, 171, 41)) - self.label_10.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_10.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_10.setObjectName("label_10") self.label_11 = QtWidgets.QLabel(self.tab) self.label_11.setGeometry(QtCore.QRect(20, 240, 171, 41)) - self.label_11.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_11.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_11.setObjectName("label_11") self.label_12 = QtWidgets.QLabel(self.tab) self.label_12.setGeometry(QtCore.QRect(20, 280, 171, 41)) - self.label_12.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_12.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_12.setObjectName("label_12") self.label_13 = QtWidgets.QLabel(self.tab) self.label_13.setGeometry(QtCore.QRect(20, 320, 171, 41)) - self.label_13.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_13.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_13.setObjectName("label_13") self.label_14 = QtWidgets.QLabel(self.tab) self.label_14.setGeometry(QtCore.QRect(230, 210, 31, 31)) @@ -98,24 +98,24 @@ def setupUi(self, stepper_motor_GUI): self.pushButton.setObjectName("pushButton") self.label_18 = QtWidgets.QLabel(self.tab) self.label_18.setGeometry(QtCore.QRect(320, 0, 291, 41)) - self.label_18.setStyleSheet("font: 75 20pt \"MS Shell Dlg 2\";") + self.label_18.setStyleSheet('font: 75 20pt "MS Shell Dlg 2";') self.label_18.setIndent(-1) self.label_18.setObjectName("label_18") self.label_19 = QtWidgets.QLabel(self.tab) self.label_19.setGeometry(QtCore.QRect(320, 40, 231, 41)) - self.label_19.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_19.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_19.setObjectName("label_19") self.label_20 = QtWidgets.QLabel(self.tab) self.label_20.setGeometry(QtCore.QRect(320, 80, 231, 41)) - self.label_20.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_20.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_20.setObjectName("label_20") self.label_21 = QtWidgets.QLabel(self.tab) self.label_21.setGeometry(QtCore.QRect(320, 120, 231, 41)) - self.label_21.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_21.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_21.setObjectName("label_21") self.label_22 = QtWidgets.QLabel(self.tab) self.label_22.setGeometry(QtCore.QRect(20, 440, 171, 41)) - self.label_22.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_22.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_22.setObjectName("label_22") self.label_23 = QtWidgets.QLabel(self.tab) self.label_23.setGeometry(QtCore.QRect(230, 450, 31, 31)) @@ -123,7 +123,7 @@ def setupUi(self, stepper_motor_GUI): self.label_23.setObjectName("label_23") self.label_24 = QtWidgets.QLabel(self.tab) self.label_24.setGeometry(QtCore.QRect(320, 160, 291, 41)) - self.label_24.setStyleSheet("font: 75 20pt \"MS Shell Dlg 2\";") + self.label_24.setStyleSheet('font: 75 20pt "MS Shell Dlg 2";') self.label_24.setIndent(-1) self.label_24.setObjectName("label_24") self.pushButton_2 = QtWidgets.QPushButton(self.tab) @@ -158,17 +158,18 @@ def setupUi(self, stepper_motor_GUI): self.label_25.setObjectName("label_25") self.label_26 = QtWidgets.QLabel(self.tab) self.label_26.setGeometry(QtCore.QRect(20, 540, 251, 41)) - self.label_26.setStyleSheet("font: 75 20pt \"MS Shell Dlg 2\";") + self.label_26.setStyleSheet('font: 75 20pt "MS Shell Dlg 2";') self.label_26.setIndent(-1) self.label_26.setObjectName("label_26") self.pushButton_6 = QtWidgets.QPushButton(self.tab) self.pushButton_6.setGeometry(QtCore.QRect(1070, 10, 81, 41)) - self.pushButton_6.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";\n" -"color: rgb(255, 0, 0)") + self.pushButton_6.setStyleSheet( + 'font: 20pt "MS Shell Dlg 2";\n' "color: rgb(255, 0, 0)" + ) self.pushButton_6.setObjectName("pushButton_6") self.label_27 = QtWidgets.QLabel(self.tab) self.label_27.setGeometry(QtCore.QRect(650, 0, 161, 41)) - self.label_27.setStyleSheet("font: 75 20pt \"MS Shell Dlg 2\";") + self.label_27.setStyleSheet('font: 75 20pt "MS Shell Dlg 2";') self.label_27.setIndent(-1) self.label_27.setObjectName("label_27") self.pushButton_7 = QtWidgets.QPushButton(self.tab) @@ -200,7 +201,7 @@ def setupUi(self, stepper_motor_GUI): self.pushButton_11.setObjectName("pushButton_11") self.label_28 = QtWidgets.QLabel(self.tab) self.label_28.setGeometry(QtCore.QRect(20, 400, 171, 41)) - self.label_28.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_28.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_28.setObjectName("label_28") self.label_29 = QtWidgets.QLabel(self.tab) self.label_29.setGeometry(QtCore.QRect(230, 410, 31, 31)) @@ -217,7 +218,7 @@ def setupUi(self, stepper_motor_GUI): self.pushButton_15.setObjectName("pushButton_15") self.label_30 = QtWidgets.QLabel(self.tab) self.label_30.setGeometry(QtCore.QRect(20, 360, 171, 41)) - self.label_30.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_30.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_30.setObjectName("label_30") self.label_31 = QtWidgets.QLabel(self.tab) self.label_31.setGeometry(QtCore.QRect(230, 370, 31, 31)) @@ -225,7 +226,7 @@ def setupUi(self, stepper_motor_GUI): self.label_31.setObjectName("label_31") self.label_32 = QtWidgets.QLabel(self.tab) self.label_32.setGeometry(QtCore.QRect(20, 480, 171, 41)) - self.label_32.setStyleSheet("font: 20pt \"MS Shell Dlg 2\";") + self.label_32.setStyleSheet('font: 20pt "MS Shell Dlg 2";') self.label_32.setObjectName("label_32") self.label_33 = QtWidgets.QLabel(self.tab) self.label_33.setGeometry(QtCore.QRect(230, 490, 31, 31)) @@ -387,7 +388,9 @@ def retranslateUi(self, stepper_motor_GUI): self.pushButton_15.setText(_translate("stepper_motor_GUI", "Baking")) self.label_30.setText(_translate("stepper_motor_GUI", "Al XPS:")) self.label_32.setText(_translate("stepper_motor_GUI", "Baking:")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("stepper_motor_GUI", "Driver")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab), _translate("stepper_motor_GUI", "Driver") + ) item = self.tableWidget.verticalHeaderItem(0) item.setText(_translate("stepper_motor_GUI", "Operating speed [Hz]")) item = self.tableWidget.verticalHeaderItem(1) @@ -445,4 +448,7 @@ def retranslateUi(self, stepper_motor_GUI): item.setText(_translate("stepper_motor_GUI", "Motor Y")) item = self.tableWidget_2.horizontalHeaderItem(2) item.setText(_translate("stepper_motor_GUI", "Motor Z")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("stepper_motor_GUI", "Settings")) + self.tabWidget.setTabText( + self.tabWidget.indexOf(self.tab_2), + _translate("stepper_motor_GUI", "Settings"), + ) diff --git a/PyExpLabSys/apps/bakeout.py b/PyExpLabSys/apps/bakeout.py index 40126622..17a2c41e 100644 --- a/PyExpLabSys/apps/bakeout.py +++ b/PyExpLabSys/apps/bakeout.py @@ -5,27 +5,35 @@ import sys import threading import curses -import wiringpi as wp # pylint: disable=F0401 +import wiringpi as wp # pylint: disable=F0401 from PyExpLabSys.common.sockets import LiveSocket from PyExpLabSys.common.sockets import DataPushSocket from PyExpLabSys.common.sockets import DateDataPullSocket from PyExpLabSys.common.utilities import get_logger from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) try: - sys.path.append('/home/pi/PyExpLabSys/machines/' + sys.argv[1]) + sys.path.append("/home/pi/PyExpLabSys/machines/" + sys.argv[1]) except IndexError: - print('You need to give the name of the raspberry pi as an argument') - print('This will ensure that the correct settings file will be used') + print("You need to give the name of the raspberry pi as an argument") + print("This will ensure that the correct settings file will be used") exit() -import settings # pylint: disable=F0401 +import settings # pylint: disable=F0401 + +LOGGER = get_logger( + "Bakeout", + level="info", + file_log=True, + file_name="bakeout_logger.txt", + terminal_log=False, +) -LOGGER = get_logger('Bakeout', level='info', file_log=True, - file_name='bakeout_logger.txt', terminal_log=False) class CursesTui(threading.Thread): - """ Text UI for bakeout program """ + """Text UI for bakeout program""" + def __init__(self, baker_instance): threading.Thread.__init__(self) self.baker = baker_instance @@ -39,16 +47,18 @@ def __init__(self, baker_instance): def run(self): while not self.baker.quit: - self.screen.addstr(2, 2, 'Running') + self.screen.addstr(2, 2, "Running") tui_string = "Watchdog TTL: {0:.0f} " self.screen.addstr(4, 2, tui_string.format(self.watchdog.time_to_live)) tui_string = "Watchdog Timer: {0:.1f}" - self.screen.addstr(5, 2, tui_string.format(time.time() - - self.watchdog.timer) + ' ') - self.screen.addstr(6, 2, "Watchdog safe: " + - str(self.watchdog.watchdog_safe) + ' ') - self.screen.addstr(8, 2, 'Current channel status:') + self.screen.addstr( + 5, 2, tui_string.format(time.time() - self.watchdog.timer) + " " + ) + self.screen.addstr( + 6, 2, "Watchdog safe: " + str(self.watchdog.watchdog_safe) + " " + ) + self.screen.addstr(8, 2, "Current channel status:") for channel in range(1, 7): if settings.count_from_right: pin = channel @@ -56,35 +66,43 @@ def run(self): pin = 7 - channel self.screen.addstr(9, 6 * channel, str(wp.digitalRead(pin))) - self.screen.addstr(12, 2, 'Channel duty cycles') + self.screen.addstr(12, 2, "Channel duty cycles") for i in range(1, 7): - self.screen.addstr(13, 7 * i, str(self.baker.dutycycles[i - 1]) + ' ') + self.screen.addstr(13, 7 * i, str(self.baker.dutycycles[i - 1]) + " ") key = self.screen.getch() - keyboard_actions = {ord('1'): [1, 1], ord('!'): [1, -1], - ord('2'): [2, 1], ord('"'): [2, -1], - ord('3'): [3, 1], ord('#'): [3, -1], - ord('4'): [4, 1], 194: [4, -1], - ord('5'): [5, 1], ord('%'): [5, -1], - ord('6'): [6, 1], ord('&'): [6, -1]} + keyboard_actions = { + ord("1"): [1, 1], + ord("!"): [1, -1], + ord("2"): [2, 1], + ord('"'): [2, -1], + ord("3"): [3, 1], + ord("#"): [3, -1], + ord("4"): [4, 1], + 194: [4, -1], + ord("5"): [5, 1], + ord("%"): [5, -1], + ord("6"): [6, 1], + ord("&"): [6, -1], + } if key in keyboard_actions: channel = keyboard_actions[key][0] sign = keyboard_actions[key][1] self.baker.modify_dutycycle(channel, settings.step_size * sign) - if key == ord('q'): + if key == ord("q"): self.baker.quit = True - self.screen.addstr(2, 2, 'Quitting....') + self.screen.addstr(2, 2, "Quitting....") - message = 'Press 1 to increase channel 1, shift-1 to decrease channel 1' + message = "Press 1 to increase channel 1, shift-1 to decrease channel 1" self.screen.addstr(16, 2, message) - self.screen.addstr(17, 2, 'Likewise for other channels, press q to quit') + self.screen.addstr(17, 2, "Likewise for other channels, press q to quit") self.screen.refresh() time.sleep(0.2) def stop(self): - """ Leave the terminal in a clean state """ + """Leave the terminal in a clean state""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -92,7 +110,8 @@ def stop(self): class Watchdog(threading.Thread): - """ Make sure heating stops if control loop fails """ + """Make sure heating stops if control loop fails""" + def __init__(self): threading.Thread.__init__(self) wp.pinMode(0, 1) @@ -102,19 +121,19 @@ def __init__(self): self.watchdog_safe = True self.quit = False self.time_to_live = 0 - self.reactivate()#Initially activate Watchdog + self.reactivate() # Initially activate Watchdog self.reset_ttl() def reset_ttl(self): - """ Reset ttl """ + """Reset ttl""" self.time_to_live = 100 def decrease_ttl(self): - """ Decrease ttl """ + """Decrease ttl""" self.time_to_live = self.time_to_live - 1 def reactivate(self): - """ Reactivate safety timer """ + """Reactivate safety timer""" wp.digitalWrite(0, 1) time.sleep(0.1) wp.digitalWrite(0, 0) @@ -139,7 +158,8 @@ def run(self): class Bakeout(threading.Thread): - """ The actual heater """ + """The actual heater""" + def __init__(self): threading.Thread.__init__(self) self.watchdog = Watchdog() @@ -149,26 +169,26 @@ def __init__(self): self.setup = settings.setup self.quit = False - for i in range(0, 7): #Set GPIO pins to output + for i in range(0, 7): # Set GPIO pins to output wp.pinMode(i, 1) self.setup = settings.setup self.dutycycles = [0, 0, 0, 0, 0, 0] - channels = ['1', '2', '3', '4', '5', '6'] + channels = ["1", "2", "3", "4", "5", "6"] # Setup up extra status for the diode relay status - diode_channels = ['diode' + number for number in channels] + diode_channels = ["diode" + number for number in channels] self.diode_channel_last = {name: None for name in diode_channels} # Setup sockets - self.livesocket = LiveSocket(self.setup + '-bakeout', channels + diode_channels) + self.livesocket = LiveSocket(self.setup + "-bakeout", channels + diode_channels) self.livesocket.start() - self.pullsocket = DateDataPullSocket(self.setup + '-bakeout', channels, timeouts=None) + self.pullsocket = DateDataPullSocket(self.setup + "-bakeout", channels, timeouts=None) self.pullsocket.start() - self.pushsocket = DataPushSocket(self.setup + '-push_control', action='enqueue') + self.pushsocket = DataPushSocket(self.setup + "-push_control", action="enqueue") self.pushsocket.start() def activate(self, pin): - """ Activate a pin """ + """Activate a pin""" if settings.count_from_right: pin = pin else: @@ -181,13 +201,13 @@ def activate(self, pin): new_status = False # Send on/off status out on live socket - name = 'diode{}'.format(pin) + name = "diode{}".format(pin) if self.diode_channel_last[name] is not new_status: self.livesocket.set_point_now(name, new_status) self.diode_channel_last[name] = new_status def deactivate(self, pin): - """ De-activate a pin """ + """De-activate a pin""" if settings.count_from_right: pin = pin else: @@ -195,25 +215,25 @@ def deactivate(self, pin): wp.digitalWrite(pin, 0) # Send on/off status out on live socket - name = 'diode{}'.format(pin) + name = "diode{}".format(pin) if self.diode_channel_last[name] is not False: self.livesocket.set_point_now(name, False) self.diode_channel_last[name] = False def modify_dutycycle(self, channel, amount=None, value=None): - """ Change the dutycycle of a channel """ + """Change the dutycycle of a channel""" if amount is not None: - self.dutycycles[channel-1] = self.dutycycles[channel-1] + amount + self.dutycycles[channel - 1] = self.dutycycles[channel - 1] + amount if value is not None: - self.dutycycles[channel-1] = value + self.dutycycles[channel - 1] = value - if self.dutycycles[channel-1] > 1: - self.dutycycles[channel-1] = 1 - if self.dutycycles[channel-1] < 0.0001: - self.dutycycles[channel-1] = 0 - self.livesocket.set_point_now(str(channel), self.dutycycles[channel-1]) - self.pullsocket.set_point_now(str(channel), self.dutycycles[channel-1]) - return self.dutycycles[channel-1] + if self.dutycycles[channel - 1] > 1: + self.dutycycles[channel - 1] = 1 + if self.dutycycles[channel - 1] < 0.0001: + self.dutycycles[channel - 1] = 0 + self.livesocket.set_point_now(str(channel), self.dutycycles[channel - 1]) + self.pullsocket.set_point_now(str(channel), self.dutycycles[channel - 1]) + return self.dutycycles[channel - 1] def run(self): totalcycles = settings.number_of_cycles @@ -223,10 +243,10 @@ def run(self): while not self.quit: start_time = time.time() qsize = self.pushsocket.queue.qsize() - LOGGER.debug('qsize: ' + str(qsize)) + LOGGER.debug("qsize: " + str(qsize)) while qsize > 0: element = self.pushsocket.queue.get() - LOGGER.debug('Element: ' + str(element)) + LOGGER.debug("Element: " + str(element)) channel = list(element.keys())[0] value = element[channel] self.modify_dutycycle(int(channel), value=value) @@ -234,7 +254,7 @@ def run(self): self.watchdog.reset_ttl() for i in range(1, 7): - if (1.0 * cycle/totalcycles) < self.dutycycles[i - 1]: + if (1.0 * cycle / totalcycles) < self.dutycycles[i - 1]: self.activate(i) else: self.deactivate(i) @@ -246,11 +266,12 @@ def run(self): time.sleep(sleep_time - run_time) except IOError: self.quit = True - LOGGER.fatal('Program runs too slow to perform this operation!') - for i in range(0, 7): # Ready to quit + LOGGER.fatal("Program runs too slow to perform this operation!") + for i in range(0, 7): # Ready to quit self.deactivate(i) -if __name__ == '__main__': + +if __name__ == "__main__": wp.wiringPiSetup() time.sleep(1) diff --git a/PyExpLabSys/apps/bakeoutweb/bakeoutweb.py b/PyExpLabSys/apps/bakeoutweb/bakeoutweb.py index 254c3ef1..82e2cdfe 100644 --- a/PyExpLabSys/apps/bakeoutweb/bakeoutweb.py +++ b/PyExpLabSys/apps/bakeoutweb/bakeoutweb.py @@ -1,4 +1,3 @@ - """Web app for the magnificient bakeout app""" import json @@ -20,28 +19,27 @@ SOCKET = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) HOSTNAME = os.environ["MACHINE"] LOG.info("Using hostname: %s", HOSTNAME) -sys.path.append('/home/pi/PyExpLabSys/machines/' + HOSTNAME) -import settings # pylint: disable=wrong-import-position, import-error - +sys.path.append("/home/pi/PyExpLabSys/machines/" + HOSTNAME) +import settings # pylint: disable=wrong-import-position, import-error SETTINGS_DEFAULTS = { - 'web_diode_color_scheme': 'green', - 'web_polling_time_msec': 5000, + "web_diode_color_scheme": "green", + "web_polling_time_msec": 5000, } def get_settings(): """Form the settings for the javascript interface""" - web_settings = {'hostname': HOSTNAME} + web_settings = {"hostname": HOSTNAME} for key, value in SETTINGS_DEFAULTS.items(): web_settings[key] = getattr(settings, key, value) return web_settings -@app.route('/') -@app.route('/') -def frontpage(debug=''): +@app.route("/") +@app.route("/") +def frontpage(debug=""): """Produce the frontpage""" LOG.info("frontpage, debug is %s", debug) json_input = get_settings() @@ -49,28 +47,35 @@ def frontpage(debug=''): row_elements = [ # Rows of id prefix, row title and element - ('state{}', 'Current state', '
'), - ('current_value{}', 'Current setpoint', 'N/A'), - ('requested_value{}', 'Change setpoint', - ''), + ( + "state{}", + "Current state", + '
', + ), + ("current_value{}", "Current setpoint", "N/A"), + ( + "requested_value{}", + "Change setpoint", + '', + ), ] - return render_template('frontpage.html', row_elements=row_elements, json_input=json_input) + return render_template("frontpage.html", row_elements=row_elements, json_input=json_input) -@app.route('/set/') +@app.route("/set/") def set_channel(request_parameters_string): """Page to set parameters on the bakeout box""" LOG.debug("set request: %s", request_parameters_string) - SOCKET.sendto(b"json_wn#" + request_parameters_string.encode('ascii'), (HOSTNAME, 8500)) + SOCKET.sendto(b"json_wn#" + request_parameters_string.encode("ascii"), (HOSTNAME, 8500)) reply = SOCKET.recv(1024) LOG.debug("for set request got reply: %s", reply) # We return just the channel name return list(json.loads(request_parameters_string).keys())[0] -@app.route('/get/') +@app.route("/get/") def get_channel(channel_number): """Page to get parameters from the bakeout box""" LOG.debug("get request: %s", channel_number) @@ -78,7 +83,7 @@ def get_channel(channel_number): SOCKET.sendto(b"json_wn", (HOSTNAME, 9000)) else: SOCKET.sendto(channel_number.encode("ascii") + b"#json", (HOSTNAME, 9000)) - reply = SOCKET.recv(1024).decode('ascii') + reply = SOCKET.recv(1024).decode("ascii") print("for get request got reply: %s", reply) if channel_number != "all": data = json.loads(reply) diff --git a/PyExpLabSys/apps/edwards_nxds_logger.py b/PyExpLabSys/apps/edwards_nxds_logger.py index e697bedf..58c634e8 100644 --- a/PyExpLabSys/apps/edwards_nxds_logger.py +++ b/PyExpLabSys/apps/edwards_nxds_logger.py @@ -8,12 +8,13 @@ from PyExpLabSys.common.value_logger import ValueLogger from PyExpLabSys.common.database_saver import ContinuousDataSaver + # from PyExpLabSys.common.sockets import DateDataPullSocket # from PyExpLabSys.common.sockets import LiveSocket from PyExpLabSys.drivers.edwards_nxds import EdwardsNxds HOSTNAME = socket.gethostname() -machine_path = pathlib.Path.home() / 'machines' / HOSTNAME +machine_path = pathlib.Path.home() / "machines" / HOSTNAME sys.path.append(str(machine_path)) # try: @@ -28,22 +29,23 @@ class PumpReader(threading.Thread): - """ Read pump parameters """ + """Read pump parameters""" + def __init__(self, port): threading.Thread.__init__(self) - port = '/dev/serial/by-id/' + port + port = "/dev/serial/by-id/" + port self.pump = EdwardsNxds(port) self.values = {} - self.values['temperature'] = -1 - self.values['controller_temperature'] = -1 - self.values['rotational_speed'] = -1 - self.values['run_hours'] = -1 - self.values['controller_run_hours'] = -1 - self.values['time_to_service'] = -1 + self.values["temperature"] = -1 + self.values["controller_temperature"] = -1 + self.values["rotational_speed"] = -1 + self.values["run_hours"] = -1 + self.values["controller_run_hours"] = -1 + self.values["time_to_service"] = -1 self.quit = False def value(self, channel): - """ Return the value of the reader """ + """Return the value of the reader""" value = self.values[channel] return value @@ -54,29 +56,37 @@ def run(self): try: temperatures = self.pump.read_pump_temperature() controller_status = self.pump.pump_controller_status() - self.values['temperature'] = temperatures['pump'] - self.values['controller_temperature'] = temperatures['controller'] - self.values['rotational_speed'] = self.pump.read_pump_status()['rotational_speed'] - self.values['run_hours'] = self.pump.read_run_hours() - self.values['controller_run_hours'] = controller_status['controller_run_time'] - self.values['time_to_service'] = controller_status['time_to_service'] + self.values["temperature"] = temperatures["pump"] + self.values["controller_temperature"] = temperatures["controller"] + self.values["rotational_speed"] = self.pump.read_pump_status()[ + "rotational_speed" + ] + self.values["run_hours"] = self.pump.read_run_hours() + self.values["controller_run_hours"] = controller_status["controller_run_time"] + self.values["time_to_service"] = controller_status["time_to_service"] except OSError: - print('Error reading from pump') + print("Error reading from pump") time.sleep(2) - self.values['temperature'] = -1 - self.values['controller_temperature'] = -1 - self.values['rotational_speed'] = -1 - self.values['run_hours'] = -1 - self.values['controller_run_hours'] = -1 - self.values['time_to_service'] = -1 + self.values["temperature"] = -1 + self.values["controller_temperature"] = -1 + self.values["rotational_speed"] = -1 + self.values["run_hours"] = -1 + self.values["controller_run_hours"] = -1 + self.values["time_to_service"] = -1 def main(): - """ Main function """ + """Main function""" pumpreaders = {} loggers = {} - channels = ['temperature', 'controller_temperature', 'run_hours', 'rotational_speed', - 'controller_run_hours', 'time_to_service'] + channels = [ + "temperature", + "controller_temperature", + "run_hours", + "rotational_speed", + "controller_run_hours", + "time_to_service", + ] codenames = [] for port, codename in settings.channels.items(): pumpreaders[port] = PumpReader(port) @@ -85,9 +95,10 @@ def main(): pumpreaders[port].loggers = {} for channel in channels: - codenames.append(codename + '_' + channel) # Build the list of codenames - loggers[port + channel] = ValueLogger(pumpreaders[port], comp_val=0.9, - channel=channel, maximumtime=600) + codenames.append(codename + "_" + channel) # Build the list of codenames + loggers[port + channel] = ValueLogger( + pumpreaders[port], comp_val=0.9, channel=channel, maximumtime=600 + ) loggers[port + channel].start() # socket = DateDataPullSocket('Pump Reader', codenames, timeouts=2.0) @@ -95,10 +106,12 @@ def main(): # live_socket = LiveSocket('Pump Reader', codenames) # live_socket.start() - db_logger = ContinuousDataSaver(continuous_data_table=settings.table, - username=credentials.user, - password=credentials.passwd, - measurement_codenames=codenames) + db_logger = ContinuousDataSaver( + continuous_data_table=settings.table, + username=credentials.user, + password=credentials.passwd, + measurement_codenames=codenames, + ) db_logger.start() time.sleep(10) @@ -111,17 +124,17 @@ def main(): if loggers[port + channel].is_alive is False: alive = False - codename = base_codename + '_' + channel + codename = base_codename + "_" + channel value = loggers[port + channel].read_value() # socket.set_point_now(codename, value) # live_socket.set_point_now(codename, value) if loggers[port + channel].read_trigged(): - print(port + ' ' + channel + ': ' + str(value)) + print(port + " " + channel + ": " + str(value)) db_logger.save_point_now(codename, value) loggers[port + channel].clear_trigged() -if __name__ == '__main__': +if __name__ == "__main__": while True: try: main() diff --git a/PyExpLabSys/apps/emission_control.py b/PyExpLabSys/apps/emission_control.py index 7f489b4b..786353b7 100644 --- a/PyExpLabSys/apps/emission_control.py +++ b/PyExpLabSys/apps/emission_control.py @@ -6,13 +6,15 @@ import PyExpLabSys.drivers.cpx400dp as CPX import PyExpLabSys.aux.pid as pid from PyExpLabSys.common.sockets import DateDataSocket -#from PyExpLabSys.common.loggers import ContinuousLogger -#from PyExpLabSys.common.sockets import LiveSocket + +# from PyExpLabSys.common.loggers import ContinuousLogger +# from PyExpLabSys.common.sockets import LiveSocket from ABElectronics_DeltaSigmaPi import DeltaSigma class CursesTui(threading.Thread): - """ Text gui for emission control """ + """Text gui for emission control""" + def __init__(self, emission_control_instance): threading.Thread.__init__(self) self.eci = emission_control_instance @@ -25,38 +27,80 @@ def __init__(self, emission_control_instance): def run(self): while True: - self.screen.addstr(3, 2, 'Running') - self.screen.addstr(4, 2, "Calculated voltage: {0:.2f}V ".format(self.eci.wanted_voltage)) - self.screen.addstr(5, 2, "Filament voltage: {0:.2f}V ".format(self.eci.filament['voltage'])) - self.screen.addstr(6, 2, "Filament current: {0:.2f}A ".format(self.eci.filament['current'])) - if self.eci.filament['current'] > 0.01: - self.screen.addstr(5, 40, "Filament resisance: {0:.2f}Ohm ".format(self.eci.filament['voltage'] / self.eci.filament['current'])) + self.screen.addstr(3, 2, "Running") + self.screen.addstr( + 4, + 2, + "Calculated voltage: {0:.2f}V ".format(self.eci.wanted_voltage), + ) + self.screen.addstr( + 5, + 2, + "Filament voltage: {0:.2f}V ".format(self.eci.filament["voltage"]), + ) + self.screen.addstr( + 6, + 2, + "Filament current: {0:.2f}A ".format(self.eci.filament["current"]), + ) + if self.eci.filament["current"] > 0.01: + self.screen.addstr( + 5, + 40, + "Filament resisance: {0:.2f}Ohm ".format( + self.eci.filament["voltage"] / self.eci.filament["current"] + ), + ) else: self.screen.addstr(5, 40, "Filament resisance: - ") - self.screen.addstr(6, 40, "Filament power: {0:.2f}W ".format(self.eci.filament['voltage'] * self.eci.filament['current'])) - self.screen.addstr(8, 2, "Grid Voltage: {0:.2f}V ".format(self.eci.bias['grid_voltage'])) - self.screen.addstr(8, 40, "Grid Current: {0:.3f}A ".format(self.eci.bias['grid_current'])) - self.screen.addstr(12, 2, "Emission current: {0:.4f}mA ".format(self.eci.emission_current)) + self.screen.addstr( + 6, + 40, + "Filament power: {0:.2f}W ".format( + self.eci.filament["voltage"] * self.eci.filament["current"] + ), + ) + self.screen.addstr( + 8, + 2, + "Grid Voltage: {0:.2f}V ".format(self.eci.bias["grid_voltage"]), + ) + self.screen.addstr( + 8, + 40, + "Grid Current: {0:.3f}A ".format(self.eci.bias["grid_current"]), + ) + self.screen.addstr( + 12, + 2, + "Emission current: {0:.4f}mA ".format(self.eci.emission_current), + ) self.screen.addstr(12, 40, "Setpoint: {0:.2f}mA".format(self.eci.setpoint)) - self.screen.addstr(13, 2, "Measured voltage: {0:.4f}mV ".format(self.eci.measured_voltage * 1000)) + self.screen.addstr( + 13, + 2, + "Measured voltage: {0:.4f}mV ".format(self.eci.measured_voltage * 1000), + ) try: - self.screen.addstr(14, 2, "Update rate: {0:.1f}Hz ".format(1/self.eci.looptime)) + self.screen.addstr( + 14, 2, "Update rate: {0:.1f}Hz ".format(1 / self.eci.looptime) + ) except ZeroDivisionError: pass n = self.screen.getch() - if n == ord('q'): + if n == ord("q"): self.eci.running = False - if n == ord('i'): + if n == ord("i"): self.eci.setpoint = self.eci.update_setpoint(self.eci.setpoint + 0.1) - if n == ord('d'): + if n == ord("d"): self.eci.setpoint = self.eci.update_setpoint(self.eci.setpoint - 0.1) self.screen.refresh() time.sleep(0.2) def stop(self): - """ Reset the terminal """ + """Reset the terminal""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -64,26 +108,27 @@ def stop(self): class EmissionControl(threading.Thread): - """ Control the emission of a filament. """ + """Control the emission of a filament.""" + def __init__(self, datasocket=None): threading.Thread.__init__(self) if datasocket is not None: self.datasocket = datasocket else: - self.datasocket = None + self.datasocket = None self.measured_voltage = 0 self.filament = {} - self.filament['device'] = CPX.CPX400DPDriver(1, usbchannel=0) - self.filament['voltage'] = 0 - self.filament['current'] = 0 - self.filament['idle_voltage'] = 3 - self.filament['device'].set_current_limit(5) - self.filament['device'].output_status(True) + self.filament["device"] = CPX.CPX400DPDriver(1, usbchannel=0) + self.filament["voltage"] = 0 + self.filament["current"] = 0 + self.filament["idle_voltage"] = 3 + self.filament["device"].set_current_limit(5) + self.filament["device"].output_status(True) self.bias = {} - self.bias['device'] = CPX.CPX400DPDriver(2, usbchannel=0) - self.bias['grid_voltage'] = 0 - self.bias['grid_current'] = 0 - self.bias['device'].output_status(True) + self.bias["device"] = CPX.CPX400DPDriver(2, usbchannel=0) + self.bias["grid_voltage"] = 0 + self.bias["grid_current"] = 0 + self.bias["device"].output_status(True) self.looptime = 0 self.update_setpoint(0.2) self.adc = DeltaSigma(0x68, 0x69, 18) @@ -95,68 +140,69 @@ def __init__(self, datasocket=None): self.pid.UpdateSetpoint(self.setpoint) def set_bias(self, bias): - """ Set the bias-voltage """ + """Set the bias-voltage""" if bias > -1: - self.bias['device'].set_voltage(bias) + self.bias["device"].set_voltage(bias) if bias < 5: pass # TODO: Implement check to make sure not to melt the filament def update_setpoint(self, setpoint): - """ Update the setpoint """ + """Update the setpoint""" self.setpoint = setpoint if self.datasocket is not None: - self.datasocket.set_point_now('setpoint', setpoint) + self.datasocket.set_point_now("setpoint", setpoint) def set_filament_voltage(self, U): - """ Set the filament voltage """ - return(self.filament['device'].set_voltage(U)) + """Set the filament voltage""" + return self.filament["device"].set_voltage(U) def read_filament_voltage(self): - """ Read the filament voltage """ - return(self.filament['device'].read_actual_voltage()) + """Read the filament voltage""" + return self.filament["device"].read_actual_voltage() def read_filament_current(self): - """ Read the filament current """ - return(self.filament['device'].read_actual_current()) + """Read the filament current""" + return self.filament["device"].read_actual_current() def read_grid_voltage(self): - """Read the actual grid voltage """ - return(self.bias['device'].read_actual_voltage()) + """Read the actual grid voltage""" + return self.bias["device"].read_actual_voltage() def read_grid_current(self): - """ Read the grid current as measured by power supply """ - return(self.bias['device'].read_actual_current()) + """Read the grid current as measured by power supply""" + return self.bias["device"].read_actual_current() def read_emission_current(self): - """ Read the actual emission current """ + """Read the actual emission current""" value = self.adc.readVoltage(1) self.measured_voltage = value current = 1000.0 * value / 3.4 # Resistance value read off component label - return(current) + return current def run(self): while self.running: - #time.sleep(0.1) + # time.sleep(0.1) t = time.time() self.emission_current = self.read_emission_current() - self.wanted_voltage = self.pid.WantedPower(self.emission_current) + self.filament['idle_voltage'] + self.wanted_voltage = ( + self.pid.WantedPower(self.emission_current) + self.filament["idle_voltage"] + ) self.pid.UpdateSetpoint(self.setpoint) self.set_filament_voltage(self.wanted_voltage) - self.filament['voltage'] = self.read_filament_voltage() - self.filament['current'] = self.read_filament_current() - self.bias['grid_voltage'] = self.read_grid_voltage() - self.bias['grid_current'] = self.read_grid_current() + self.filament["voltage"] = self.read_filament_voltage() + self.filament["current"] = self.read_filament_current() + self.bias["grid_voltage"] = self.read_grid_voltage() + self.bias["grid_current"] = self.read_grid_current() if self.datasocket is not None: - self.datasocket.set_point_now('emission', self.emission_current) + self.datasocket.set_point_now("emission", self.emission_current) self.looptime = time.time() - t self.setpoint = 0 self.set_filament_voltage(0) self.set_bias(0) -if __name__ == '__main__': - - datasocket = DateDataSocket(['setpoint', 'emission'], timeouts=[999999, 1.0]) +if __name__ == "__main__": + datasocket = DateDataSocket(["setpoint", "emission"], timeouts=[999999, 1.0]) datasocket.start() ec = EmissionControl(datasocket) diff --git a/PyExpLabSys/apps/ion_optics_controller.py b/PyExpLabSys/apps/ion_optics_controller.py index cb12dd00..ea716658 100644 --- a/PyExpLabSys/apps/ion_optics_controller.py +++ b/PyExpLabSys/apps/ion_optics_controller.py @@ -7,10 +7,13 @@ from PyExpLabSys.common.sockets import DateDataPullSocket from PyExpLabSys.common.sockets import DataPushSocket from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class CursesTui(threading.Thread): - """ Text user interface for ion optics control """ + """Text user interface for ion optics control""" + def __init__(self, ioc_class): threading.Thread.__init__(self) self.start_time = time.time() @@ -25,23 +28,23 @@ def __init__(self, ioc_class): def run(self): while not self.quit: - self.screen.addstr(2, 2, 'Running') - val = self.ioc.status['temperature'] + self.screen.addstr(2, 2, "Running") + val = self.ioc.status["temperature"] self.screen.addstr(3, 2, "Device Temeperature: {0:.2f}C ".format(val)) - if self.ioc.status['output_error'] is False: + if self.ioc.status["output_error"] is False: self.screen.addstr(4, 2, "All channels ok ") else: self.screen.addstr(4, 2, "Error in channel") - self.screen.addstr(7, 13, 'Set voltage') - self.screen.addstr(7, 27, 'Measured') - self.screen.addstr(7, 38, 'Status') + self.screen.addstr(7, 13, "Set voltage") + self.screen.addstr(7, 27, "Measured") + self.screen.addstr(7, 38, "Status") for i in range(0, len(self.ioc.lenses)): lens = self.ioc.lenses[i] ch_string = "Channel " + str(i + 1) + ": {0: >9.2f}V {1: >7.1f}V {2} " set_v = self.ioc.set_voltages[lens] actual_v = self.ioc.actual_voltages[lens] - status = self.ioc.status['channel_status'][i+1] + status = self.ioc.status["channel_status"][i + 1] self.screen.addstr(8 + i, 2, ch_string.format(set_v, actual_v, status)) val = self.ioc.pushsocket.queue.qsize() @@ -51,7 +54,7 @@ def run(self): self.screen.addstr(17, 2, "Run time: {0:.0f}s".format(val)) key_pressed = self.screen.getch() - if key_pressed == ord('q'): + if key_pressed == ord("q"): self.ioc.quit = True self.quit = True @@ -60,7 +63,7 @@ def run(self): self.stop() def stop(self): - """ Clean up console """ + """Clean up console""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -68,13 +71,14 @@ def stop(self): class IonOpticsControl(threading.Thread): - """ Main optics control """ + """Main optics control""" + def __init__(self, port, name, lenses): threading.Thread.__init__(self) - name = name + '_ion_optics' + name = name + "_ion_optics" self.pullsocket = DateDataPullSocket(name, lenses, timeouts=20.0) self.pullsocket.start() - self.pushsocket = DataPushSocket(name, action='enqueue') + self.pushsocket = DataPushSocket(name, action="enqueue") self.pushsocket.start() self.ion_optics = stahl_hv_400.StahlHV400(port) self.lenses = lenses @@ -84,27 +88,27 @@ def __init__(self, port, name, lenses): self.set_voltages[lens] = 0 self.actual_voltages[lens] = 0 self.status = {} - self.status['channel_status'] = {} - for i in range(1, len(self.lenses)+1): - self.status['channel_status'][i] = False - self.status['temperature'] = None - self.status['output_error'] = None + self.status["channel_status"] = {} + for i in range(1, len(self.lenses) + 1): + self.status["channel_status"][i] = False + self.status["temperature"] = None + self.status["output_error"] = None self.quit = False def run(self): current_lens = 1 while not self.quit: - self.status['temperature'] = self.ion_optics.read_temperature() - if self.status['temperature'] > 50: + self.status["temperature"] = self.ion_optics.read_temperature() + if self.status["temperature"] > 50: for lens in self.lenses: self.set_voltages[lens] = 0 - self.status['channel_status'] = self.ion_optics.check_channel_status() - self.status['output_error'] = False in self.status['channel_status'] + self.status["channel_status"] = self.ion_optics.check_channel_status() + self.status["output_error"] = False in self.status["channel_status"] actual_voltage = self.ion_optics.query_voltage(current_lens) - self.actual_voltages[self.lenses[current_lens-1]] = actual_voltage - self.pullsocket.set_point_now(self.lenses[current_lens-1], actual_voltage) + self.actual_voltages[self.lenses[current_lens - 1]] = actual_voltage + self.pullsocket.set_point_now(self.lenses[current_lens - 1], actual_voltage) if current_lens == len(self.lenses): current_lens = 1 diff --git a/PyExpLabSys/apps/mass_finder.py b/PyExpLabSys/apps/mass_finder.py index b8428564..7ac63fad 100644 --- a/PyExpLabSys/apps/mass_finder.py +++ b/PyExpLabSys/apps/mass_finder.py @@ -5,44 +5,44 @@ elements = {} -elements['H'] = {} -elements['H']['isotopes'] = [(0.999885, 0.999885), - (0.000115, 2.0141017778)] +elements["H"] = {} +elements["H"]["isotopes"] = [(0.999885, 0.999885), (0.000115, 2.0141017778)] -elements['C'] = {} -elements['C']['isotopes'] = [(0.9893, 12.0), - (0.0107, 13.0033548378)] +elements["C"] = {} +elements["C"]["isotopes"] = [(0.9893, 12.0), (0.0107, 13.0033548378)] -elements['O'] = {} -elements['O']['isotopes'] = [(0.99757, 15.99491461956), - (0.00038, 16.99913170), - (0.00205, 17.9991610)] +elements["O"] = {} +elements["O"]["isotopes"] = [ + (0.99757, 15.99491461956), + (0.00038, 16.99913170), + (0.00205, 17.9991610), +] -elements['Al'] = {} -elements['Al']['isotopes'] = [(1, 26.9815386)] +elements["Al"] = {} +elements["Al"]["isotopes"] = [(1, 26.9815386)] -elements['Cl'] = {} -elements['Cl']['isotopes'] = [(0.7576, 34.96885268), - (0.2424, 36.96590259)] +elements["Cl"] = {} +elements["Cl"]["isotopes"] = [(0.7576, 34.96885268), (0.2424, 36.96590259)] -elements['S'] = {} -elements['S']['isotopes'] = [(0.9493, 31.97207100), - (0.0076, 32.97145876), - (0.0429, 33.96786690), - (0.0002, 35.96708076)] +elements["S"] = {} +elements["S"]["isotopes"] = [ + (0.9493, 31.97207100), + (0.0076, 32.97145876), + (0.0429, 33.96786690), + (0.0002, 35.96708076), +] class MassCalculator(object): - def __init__(self, elements): self.elements = elements def isotope_spectrum(self, element_list): - """ Calculate isotope spectrum of molecule """ + """Calculate isotope spectrum of molecule""" spectrum = [] index_list = [] for element in element_list: - index_list.append(range(0, len(self.elements[element]['isotopes']))) + index_list.append(range(0, len(self.elements[element]["isotopes"]))) t = 0 for index in product(*index_list): @@ -55,7 +55,7 @@ def isotope_spectrum(self, element_list): mass = 0 # BUG!!! Identical atomic configurations er treated as separate and thus the numbers does not add up!!! for i in range(0, len(element_list)): - current_isotope = self.elements[element_list[i]]['isotopes'][index[i]] + current_isotope = self.elements[element_list[i]]["isotopes"][index[i]] propability = propability * current_isotope[0] mass = mass + current_isotope[1] if propability < 1e-9: @@ -68,34 +68,34 @@ def isotope_spectrum(self, element_list): fig = plt.figure() axis = fig.add_subplot(1, 1, 1) - axis.plot(mass_axis, intensity_axis, 'bo') - axis.set_yscale('log') + axis.plot(mass_axis, intensity_axis, "bo") + axis.set_yscale("log") plt.show() return sorted(spectrum, reverse=True) def elemental_combinations(self, target_mass, element_list=None): - """ Find all molecular combination with a highest peak close - to target mass """ + """Find all molecular combination with a highest peak close + to target mass""" max_amounts = {} for element in self.elements: # This currently assumes most abundant element is first in list - weight = self.elements[element]['isotopes'][0][1] - amount = int(math.floor(target_mass/weight)) + weight = self.elements[element]["isotopes"][0][1] + amount = int(math.floor(target_mass / weight)) if amount > 0: max_amounts[element] = amount range_lists = [] element_list = [] for element in max_amounts: - range_lists.append(range(0, max_amounts[element]+1)) + range_lists.append(range(0, max_amounts[element] + 1)) element_list.append(element) candidates = [] for index in product(*range_lists): weight = 0 for i in range(0, len(element_list)): - element_weight = index[i] * self.elements[element_list[i]]['isotopes'][0][1] + element_weight = index[i] * self.elements[element_list[i]]["isotopes"][0][1] weight += element_weight if (weight > target_mass - 0.5) and (weight < target_mass + 0.5): @@ -108,13 +108,12 @@ def elemental_combinations(self, target_mass, element_list=None): ms = MassCalculator(elements) -print(ms.isotope_spectrum(['C'] + ['H']*2)) - -#print(ms.isotope_spectrum(['C']*12 + ['H']*8 + ['S'])) +print(ms.isotope_spectrum(["C"] + ["H"] * 2)) -#t = time.time() -#ms.isotope_spectrum(['C']*12 + ['H']*8 + ['S']) -#ms.isotope_spectrum(['C']*14 + ['H']*14 + ['S']) -#print(time.time() - t) -#print(ms.elemental_combinations(85)) +# print(ms.isotope_spectrum(['C']*12 + ['H']*8 + ['S'])) +# t = time.time() +# ms.isotope_spectrum(['C']*12 + ['H']*8 + ['S']) +# ms.isotope_spectrum(['C']*14 + ['H']*14 + ['S']) +# print(time.time() - t) +# print(ms.elemental_combinations(85)) diff --git a/PyExpLabSys/apps/microreactor_livevalue_web.py b/PyExpLabSys/apps/microreactor_livevalue_web.py index 5321d6a0..f38c862f 100644 --- a/PyExpLabSys/apps/microreactor_livevalue_web.py +++ b/PyExpLabSys/apps/microreactor_livevalue_web.py @@ -9,28 +9,40 @@ import dash_html_components as html import plotly.graph_objs as go from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) ###### Python functions needed and global variables ##########3 FLOWNAMES = { - 'flow1':'Flow 1 [O2]', - 'flow2':'Flow 2 [He]', - 'flow3':'Flow 3 [H2]', - 'flow4':'Flow 4 [None]', - 'flow5':'Flow 5 [Ar]', - 'flow6':'Flow 6 [CO]', - } + "flow1": "Flow 1 [O2]", + "flow2": "Flow 2 [He]", + "flow3": "Flow 3 [H2]", + "flow4": "Flow 4 [None]", + "flow5": "Flow 5 [Ar]", + "flow6": "Flow 6 [CO]", +} NAMES = [ - 'thermocouple_temp', 'rtd_temp', 'chamber_pressure', 'reactor_pressure', 'buffer_pressure', - 'containment_pressure', 'flow1', 'flow2', 'flow3', 'flow4', 'flow5', 'flow6', - ] + "thermocouple_temp", + "rtd_temp", + "chamber_pressure", + "reactor_pressure", + "buffer_pressure", + "containment_pressure", + "flow1", + "flow2", + "flow3", + "flow4", + "flow5", + "flow6", +] INTERVAL = 5 HOURS = 18 -MAX_LENGTH = int(HOURS*3600/INTERVAL) +MAX_LENGTH = int(HOURS * 3600 / INTERVAL) + +ALL_DATA = {name: {"x": [], "y": []} for name in NAMES} -ALL_DATA = {name: {'x': [], 'y': []} for name in NAMES} def communicate_sock(network_adress, com, port=9000): """This is the socekt communications function""" @@ -40,89 +52,106 @@ def communicate_sock(network_adress, com, port=9000): try: command = com.encode() sock.sendto(command, (network_adress, port)) - received_bytes = sock.recv(1024)# - received = received_bytes.decode('ascii') - #expected string recived: 'time_since_epoch,value' - value = float(received[received.find(',')+1:]) + received_bytes = sock.recv(1024) # + received = received_bytes.decode("ascii") + # expected string recived: 'time_since_epoch,value' + value = float(received[received.find(",") + 1 :]) except ValueError: - value = -500.0#None + value = -500.0 # None return value + def all_values_update(): """Function to call update all values""" time = datetime.now() - - #Temperature values from thermocouple and RTD - thermocouple_temp = communicate_sock('rasppi12', 'microreactorng_temp_sample#raw') - rtd_temp = communicate_sock('rasppi05', 'temperature#raw') + + # Temperature values from thermocouple and RTD + thermocouple_temp = communicate_sock("rasppi12", "microreactorng_temp_sample#raw") + rtd_temp = communicate_sock("rasppi05", "temperature#raw") if isinstance(rtd_temp, float): rtd_temp = round(rtd_temp, 1) - - #Pressure values from NextGeneration setup - chamber_pressure = communicate_sock('microreactorng', 'read_pressure#labview', port=7654) - reactor_pressure = communicate_sock('rasppi16', 'M11200362H#raw') + + # Pressure values from NextGeneration setup + chamber_pressure = communicate_sock("microreactorng", "read_pressure#labview", port=7654) + reactor_pressure = communicate_sock("rasppi16", "M11200362H#raw") if reactor_pressure == 0: reactor_pressure = 1e-4 if isinstance(reactor_pressure, float): reactor_pressure = round(reactor_pressure, 3) - buffer_pressure = communicate_sock('rasppi36', 'microreactorng_pressure_buffer#raw') - containment_pressure = communicate_sock('microreactorng', 'read_containment#labview', port=7654) - - #Flow values from NextGeneration setup - flow1 = communicate_sock('rasppi16', 'M11200362C#raw') - flow2 = communicate_sock('rasppi16', 'M11200362A#raw') - flow3 = communicate_sock('rasppi16', 'M11200362E#raw') - flow4 = communicate_sock('rasppi16', 'M11200362D#raw') - flow5 = communicate_sock('rasppi16', 'M11210022B#raw') - flow6 = communicate_sock('rasppi16', 'M11200362G#raw') + buffer_pressure = communicate_sock("rasppi36", "microreactorng_pressure_buffer#raw") + containment_pressure = communicate_sock( + "microreactorng", "read_containment#labview", port=7654 + ) + + # Flow values from NextGeneration setup + flow1 = communicate_sock("rasppi16", "M11200362C#raw") + flow2 = communicate_sock("rasppi16", "M11200362A#raw") + flow3 = communicate_sock("rasppi16", "M11200362E#raw") + flow4 = communicate_sock("rasppi16", "M11200362D#raw") + flow5 = communicate_sock("rasppi16", "M11210022B#raw") + flow6 = communicate_sock("rasppi16", "M11200362G#raw") for i in [flow1, flow2, flow3, flow4, flow5, flow6]: if i is not None and len(str(i)) > 3: i = round(i, 2) values = [ - thermocouple_temp, rtd_temp, chamber_pressure, reactor_pressure, buffer_pressure, - containment_pressure, flow1, flow2, flow3, flow4, flow5, flow6, - ] + thermocouple_temp, + rtd_temp, + chamber_pressure, + reactor_pressure, + buffer_pressure, + containment_pressure, + flow1, + flow2, + flow3, + flow4, + flow5, + flow6, + ] for i, elem in enumerate(NAMES): - ALL_DATA[NAMES[i]]['x'].append(time) - ALL_DATA[NAMES[i]]['y'].append(values[i]) - if len(ALL_DATA[NAMES[i]]['x']) > MAX_LENGTH: - ALL_DATA[NAMES[i]]['x'].pop(0) - ALL_DATA[NAMES[i]]['y'].pop(0) - print(ALL_DATA['thermocouple_temp']['x'][-1]) + ALL_DATA[NAMES[i]]["x"].append(time) + ALL_DATA[NAMES[i]]["y"].append(values[i]) + if len(ALL_DATA[NAMES[i]]["x"]) > MAX_LENGTH: + ALL_DATA[NAMES[i]]["x"].pop(0) + ALL_DATA[NAMES[i]]["y"].pop(0) + print(ALL_DATA["thermocouple_temp"]["x"][-1]) t.sleep(1) + ### Colours for dash app and plots #### COLOURS = { - 'background':'#607D8B', - 'text1': '#BDBDBD', - 'text': '#5e7366', - 'main_chamber_pressure':'#AFB42B', - 'thermocouple_temp':'#FF9800', - 'rtd_temp':'#795548', - 'containment_pressure':'#F44336', - 'buffer_pressure':'#0097A7', - 'reactor_pressure':'#3F51B5', - 'flow1': '#FBC02D', - 'flow2': '#9C27B0', - 'flow3':'#1976D2', - 'flow4':'#a52a2a', - 'flow5':'#388E3C', - 'flow6':'#616161', - 'paper_bgcolor':'#020202', - 'plot_bgcolor':'#191A1A', - } - + "background": "#607D8B", + "text1": "#BDBDBD", + "text": "#5e7366", + "main_chamber_pressure": "#AFB42B", + "thermocouple_temp": "#FF9800", + "rtd_temp": "#795548", + "containment_pressure": "#F44336", + "buffer_pressure": "#0097A7", + "reactor_pressure": "#3F51B5", + "flow1": "#FBC02D", + "flow2": "#9C27B0", + "flow3": "#1976D2", + "flow4": "#a52a2a", + "flow5": "#388E3C", + "flow6": "#616161", + "paper_bgcolor": "#020202", + "plot_bgcolor": "#191A1A", +} APP = dash.Dash(__name__) -APP.css.append_css({'external_url': 'https://cdn.rawgit.com/plotly/dash-app-stylesheets/2d266c578d2a6e8850ebce48fdb52759b2aef506/stylesheet-oil-and-gas.css'}) +APP.css.append_css( + { + "external_url": "https://cdn.rawgit.com/plotly/dash-app-stylesheets/2d266c578d2a6e8850ebce48fdb52759b2aef506/stylesheet-oil-and-gas.css" + } +) - ############# Layout of application #################### +############# Layout of application #################### r""" . ------------------------------------------------------------------------------------ . | TITLE | @@ -156,71 +185,91 @@ def all_values_update(): . ------------------------------------------------------------------------------------ . """ -APP.layout = html.Div(style={'backgroundColor': COLOURS['paper_bgcolor']}, children=[ +APP.layout = html.Div( + style={"backgroundColor": COLOURS["paper_bgcolor"]}, + children=[ dcc.Interval( - id='interval-component', #id for update INTERVAL - interval=INTERVAL*1000, #time in ms between execution - n_intervals=0#number of times - ), - + id="interval-component", # id for update INTERVAL + interval=INTERVAL * 1000, # time in ms between execution + n_intervals=0, # number of times + ), #### headline ### - html.Div( - [ - html.H1( - 'Dashboard of MicroreactorANH', - style={'textAlign': 'center', 'color': COLOURS['text']}, - className='twelve columns' - ) - ], className='row'), - ### Main Plot #### - html.Div([ - html.Div([ - html.Div([dcc.Graph(id='plot_press', - animate=True, - className='twelve')], - className='row'), - ### Three Plots ### - html.Div([ - dcc.Graph(id='plot_temp', - animate=True, - className='four columns'), - dcc.Graph(id='plot_flow', - animate=True, - className='four columns'), - dcc.Graph(id='plot_not_used', - animate=True, - className='four columns')], - className='row')], - className='eight columns'), - ### Table ### - html.Div([ - html.Table(id='table_pressure')], - className='four columns'),], - className='row'), - - html.Div(id='intermediate-values', style={'display':'none'}) -]) + html.Div( + [ + html.H1( + "Dashboard of MicroreactorANH", + style={"textAlign": "center", "color": COLOURS["text"]}, + className="twelve columns", + ) + ], + className="row", + ), + ### Main Plot #### + html.Div( + [ + html.Div( + [ + html.Div( + [dcc.Graph(id="plot_press", animate=True, className="twelve")], + className="row", + ), + ### Three Plots ### + html.Div( + [ + dcc.Graph( + id="plot_temp", + animate=True, + className="four columns", + ), + dcc.Graph( + id="plot_flow", + animate=True, + className="four columns", + ), + dcc.Graph( + id="plot_not_used", + animate=True, + className="four columns", + ), + ], + className="row", + ), + ], + className="eight columns", + ), + ### Table ### + html.Div([html.Table(id="table_pressure")], className="four columns"), + ], + className="row", + ), + html.Div(id="intermediate-values", style={"display": "none"}), + ], +) ############## CALL BACK FUNCTIONS ############### + @APP.callback( - Output(component_id='intermediate-values', component_property='children'), - [Input('interval-component', 'n_intervals')]) + Output(component_id="intermediate-values", component_property="children"), + [Input("interval-component", "n_intervals")], +) def update_values(n): """Function to call update all values""" all_values_update() @APP.callback( - Output(component_id='plot_press', component_property='figure'), - [Input(component_id='intermediate-values', component_property='children')] - ) + Output(component_id="plot_press", component_property="figure"), + [Input(component_id="intermediate-values", component_property="children")], +) def update_press_graph(n): """Function to update pressure graph""" - lst = ALL_DATA['containment_pressure']['y']+\ - ALL_DATA['reactor_pressure']['y']+\ - ALL_DATA['buffer_pressure']['y']+\ - ALL_DATA['chamber_pressure']['y'] + lst = ( + ALL_DATA["containment_pressure"]["y"] + + ALL_DATA["reactor_pressure"]["y"] + + ALL_DATA["buffer_pressure"]["y"] + + ALL_DATA["chamber_pressure"]["y"] + ) if not [i for i in lst if i is not None]: ymin = 0 @@ -230,80 +279,104 @@ def update_press_graph(n): ymax = max(i for i in lst if i is not None) * 1.001 data = [ go.Scatter( - ALL_DATA['containment_pressure'], - marker=dict(color=COLOURS['containment_pressure']) - ), - go.Scatter( - ALL_DATA['reactor_pressure'], - marker=dict(color=COLOURS['reactor_pressure']) - ), + ALL_DATA["containment_pressure"], + marker=dict(color=COLOURS["containment_pressure"]), + ), go.Scatter( - ALL_DATA['buffer_pressure'], - marker=dict(color=COLOURS['buffer_pressure']) - )] + ALL_DATA["reactor_pressure"], marker=dict(color=COLOURS["reactor_pressure"]) + ), + go.Scatter(ALL_DATA["buffer_pressure"], marker=dict(color=COLOURS["buffer_pressure"])), + ] layout = go.Layout( - xaxis=dict(range=[ALL_DATA['thermocouple_temp']['x'][0], - ALL_DATA['thermocouple_temp']['x'][-1]]), - yaxis=dict(exponentformat='e', type='log'), + xaxis=dict( + range=[ + ALL_DATA["thermocouple_temp"]["x"][0], + ALL_DATA["thermocouple_temp"]["x"][-1], + ] + ), + yaxis=dict(exponentformat="e", type="log"), height=400, - margin={'l':35, 'r':35, 'b':35, 't':45}, - hovermode='closest', - legend={'orientation':'h'}, - title='Pressures', - plot_bgcolor='#191A1A', - paper_bgcolor='#020202', + margin={"l": 35, "r": 35, "b": 35, "t": 45}, + hovermode="closest", + legend={"orientation": "h"}, + title="Pressures", + plot_bgcolor="#191A1A", + paper_bgcolor="#020202", showlegend=False, - font=dict(color=COLOURS['text1']) - ) - return {'data':data, 'layout':layout} + font=dict(color=COLOURS["text1"]), + ) + return {"data": data, "layout": layout} + @APP.callback( - Output(component_id='plot_temp', component_property='figure'), - [Input(component_id='intermediate-values', component_property='children')] - ) + Output(component_id="plot_temp", component_property="figure"), + [Input(component_id="intermediate-values", component_property="children")], +) def update_temp_graph(n): """Function to updtae temperature plots""" - if len([i for i in (ALL_DATA['thermocouple_temp']['y']+ALL_DATA['rtd_temp']['y']) if i is not None]) == 0: + if ( + len( + [ + i + for i in (ALL_DATA["thermocouple_temp"]["y"] + ALL_DATA["rtd_temp"]["y"]) + if i is not None + ] + ) + == 0 + ): ymin = 0 ymax = 1 else: - y_axis = [i for i in (ALL_DATA['rtd_temp']['y']+ALL_DATA['thermocouple_temp']['y']) if i is not None] + y_axis = [ + i + for i in (ALL_DATA["rtd_temp"]["y"] + ALL_DATA["thermocouple_temp"]["y"]) + if i is not None + ] ymin = min(y_axis) * 0.999 ymax = max(y_axis) * 1.001 data = [ + go.Scatter(ALL_DATA["rtd_temp"], marker=dict(color=COLOURS["rtd_temp"])), go.Scatter( - ALL_DATA['rtd_temp'], - marker=dict(color=COLOURS['rtd_temp']) - ), - go.Scatter( - ALL_DATA['thermocouple_temp'], - marker=dict(color=COLOURS['thermocouple_temp']) - )] + ALL_DATA["thermocouple_temp"], + marker=dict(color=COLOURS["thermocouple_temp"]), + ), + ] layout = go.Layout( - xaxis=dict(range=[ALL_DATA['thermocouple_temp']['x'][0], - ALL_DATA['thermocouple_temp']['x'][-1]]), + xaxis=dict( + range=[ + ALL_DATA["thermocouple_temp"]["x"][0], + ALL_DATA["thermocouple_temp"]["x"][-1], + ] + ), yaxis=dict(range=[ymin, ymax]), height=300, - margin={'l':35, 'r':35, 'b':35, 't':45}, - hovermode='closest', - legend={'orientation':'h'}, - title='Temperature', - plot_bgcolor='#191A1A', - paper_bgcolor='#020202', + margin={"l": 35, "r": 35, "b": 35, "t": 45}, + hovermode="closest", + legend={"orientation": "h"}, + title="Temperature", + plot_bgcolor="#191A1A", + paper_bgcolor="#020202", showlegend=False, - font=dict(color=COLOURS['text1']) - ) - return {'data':data, 'layout':layout} + font=dict(color=COLOURS["text1"]), + ) + return {"data": data, "layout": layout} @APP.callback( - Output(component_id='plot_flow', component_property='figure'), - [Input(component_id='intermediate-values', component_property='children')] - ) + Output(component_id="plot_flow", component_property="figure"), + [Input(component_id="intermediate-values", component_property="children")], +) def update_flow_graph(n): """Function to update flows plot""" - lst = ALL_DATA['flow1']['y']+ALL_DATA['flow2']['y']+ALL_DATA['flow3']['y']+ALL_DATA['flow4']['y']+ALL_DATA['flow5']['y']+ALL_DATA['flow6']['y'] + lst = ( + ALL_DATA["flow1"]["y"] + + ALL_DATA["flow2"]["y"] + + ALL_DATA["flow3"]["y"] + + ALL_DATA["flow4"]["y"] + + ALL_DATA["flow5"]["y"] + + ALL_DATA["flow6"]["y"] + ) if len([i for i in lst if i is not None]) == 0: ymin = 0 @@ -316,191 +389,253 @@ def update_flow_graph(n): else: ymax = 10 data = [ + go.Scatter(ALL_DATA["flow1"], marker=dict(color=COLOURS["flow1"])), + go.Scatter(ALL_DATA["flow2"], marker=dict(color=COLOURS["flow2"])), + go.Scatter(ALL_DATA["flow3"], marker=dict(color=COLOURS["flow3"])), + go.Scatter(ALL_DATA["flow4"], marker=dict(color=COLOURS["flow4"])), + go.Scatter(ALL_DATA["flow5"], marker=dict(color=COLOURS["flow5"])), go.Scatter( - ALL_DATA['flow1'], - marker=dict(color=COLOURS['flow1']) - ), - go.Scatter( - ALL_DATA['flow2'], - marker=dict(color=COLOURS['flow2']) - ), - go.Scatter( - ALL_DATA['flow3'], - marker=dict(color=COLOURS['flow3']) - ), - go.Scatter( - ALL_DATA['flow4'], - marker=dict(color=COLOURS['flow4']) - ), - go.Scatter( - ALL_DATA['flow5'], - marker=dict(color=COLOURS['flow5']) - ), - go.Scatter( - ALL_DATA['flow6'], - marker=dict(color=COLOURS['flow6']),)] + ALL_DATA["flow6"], + marker=dict(color=COLOURS["flow6"]), + ), + ] layout = go.Layout( - xaxis=dict(range=[ALL_DATA['thermocouple_temp']['x'][0], - ALL_DATA['thermocouple_temp']['x'][-1]]), + xaxis=dict( + range=[ + ALL_DATA["thermocouple_temp"]["x"][0], + ALL_DATA["thermocouple_temp"]["x"][-1], + ] + ), yaxis=dict(range=[0, ymax]), height=300, - margin={'l':35, 'r':35, 'b':35, 't':45}, - hovermode='closest', - legend={'orientation':'h'}, - title='Flows', - plot_bgcolor='#191A1A', - paper_bgcolor='#020202', + margin={"l": 35, "r": 35, "b": 35, "t": 45}, + hovermode="closest", + legend={"orientation": "h"}, + title="Flows", + plot_bgcolor="#191A1A", + paper_bgcolor="#020202", showlegend=False, - font=dict(color=COLOURS['text1']) - ) + font=dict(color=COLOURS["text1"]), + ) + + return {"data": data, "layout": layout} - return {'data':data, 'layout':layout} @APP.callback( - Output(component_id='plot_not_used', component_property='figure'), - [Input(component_id='intermediate-values', component_property='children')] - ) + Output(component_id="plot_not_used", component_property="figure"), + [Input(component_id="intermediate-values", component_property="children")], +) def update_not_in_use_graph(n): """Function to update Mainchamber plot""" - lst = ALL_DATA['chamber_pressure']['y'] + lst = ALL_DATA["chamber_pressure"]["y"] if len([i for i in lst if i is not None]) == 0: ymin = 0 ymax = 1 else: - ymin = min(i for i in lst if i is not None)*0.999 - ymax = max(i for i in lst if i is not None)*1.001 + ymin = min(i for i in lst if i is not None) * 0.999 + ymax = max(i for i in lst if i is not None) * 1.001 data = [ go.Scatter( - ALL_DATA['chamber_pressure'], - marker=dict(color=COLOURS['main_chamber_pressure']) - )] + ALL_DATA["chamber_pressure"], + marker=dict(color=COLOURS["main_chamber_pressure"]), + ) + ] layout = go.Layout( - xaxis=dict(range=[ALL_DATA['thermocouple_temp']['x'][0], - ALL_DATA['thermocouple_temp']['x'][-1]]), - yaxis=dict(exponentformat='e', type='log'), + xaxis=dict( + range=[ + ALL_DATA["thermocouple_temp"]["x"][0], + ALL_DATA["thermocouple_temp"]["x"][-1], + ] + ), + yaxis=dict(exponentformat="e", type="log"), height=300, - margin={'l':35, 'r':35, 'b':35, 't':45}, - hovermode='closest', - legend={'orientation':'h'}, - title='Main Chamber Pressure', - plot_bgcolor='#191A1A', - paper_bgcolor='#020202', + margin={"l": 35, "r": 35, "b": 35, "t": 45}, + hovermode="closest", + legend={"orientation": "h"}, + title="Main Chamber Pressure", + plot_bgcolor="#191A1A", + paper_bgcolor="#020202", showlegend=False, - font=dict(color=COLOURS['text1']) - ) - return {'data':data, 'layout':layout} + font=dict(color=COLOURS["text1"]), + ) + return {"data": data, "layout": layout} + @APP.callback( - Output(component_id='table_pressure', component_property='children'), - [Input('intermediate-values', 'children')]) + Output(component_id="table_pressure", component_property="children"), + [Input("intermediate-values", "children")], +) def update_table(n): - """"Update table values""" + """ "Update table values""" ### Pressure values ### - chamber_pressure = ALL_DATA['chamber_pressure'] - reactor_pressure = ALL_DATA['reactor_pressure'] - buffer_pressure = ALL_DATA['buffer_pressure'] - containment_pressure = ALL_DATA['containment_pressure'] - thermocouple_temp = ALL_DATA['thermocouple_temp'] - rtd_temp = ALL_DATA['rtd_temp'] + chamber_pressure = ALL_DATA["chamber_pressure"] + reactor_pressure = ALL_DATA["reactor_pressure"] + buffer_pressure = ALL_DATA["buffer_pressure"] + containment_pressure = ALL_DATA["containment_pressure"] + thermocouple_temp = ALL_DATA["thermocouple_temp"] + rtd_temp = ALL_DATA["rtd_temp"] ## Flows Values ### - flow1 = ALL_DATA['flow1'] - flow2 = ALL_DATA['flow2'] - flow3 = ALL_DATA['flow3'] - flow4 = ALL_DATA['flow4'] - flow5 = ALL_DATA['flow5'] - flow6 = ALL_DATA['flow6'] - - if flow5['y'] is None: - flow5['y'] = -1 - bgstyle = {'textAlign':'center', - 'color': COLOURS['text'], - 'backgroundColor':COLOURS['plot_bgcolor'] - } - bgstyle_header = {'color':COLOURS['text1']} - #Table + flow1 = ALL_DATA["flow1"] + flow2 = ALL_DATA["flow2"] + flow3 = ALL_DATA["flow3"] + flow4 = ALL_DATA["flow4"] + flow5 = ALL_DATA["flow5"] + flow6 = ALL_DATA["flow6"] + + if flow5["y"] is None: + flow5["y"] = -1 + bgstyle = { + "textAlign": "center", + "color": COLOURS["text"], + "backgroundColor": COLOURS["plot_bgcolor"], + } + bgstyle_header = {"color": COLOURS["text1"]} + # Table out = ( [ html.Tr( - [html.Th('#'), html.Th('Name'), html.Th('Time'), html.Th('Value')], - style=bgstyle_header) - ]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['main_chamber_pressure']}), - html.Td('Main Chamber Pressure'), - html.Td(chamber_pressure['x'][-1].strftime("%H:%M:%S")), - html.Td(format(chamber_pressure['y'][-1], '0.2e'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['reactor_pressure']}), - html.Td('Reactor Pressure'), - html.Td(reactor_pressure['x'][-1].strftime("%H:%M:%S")), - html.Td(format(reactor_pressure['y'][-1], '0.2e'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['buffer_pressure']}), - html.Td('Buffer Pressure'), - html.Td(buffer_pressure['x'][-1].strftime("%H:%M:%S")), - html.Td(format(buffer_pressure['y'][-1], '0.2e'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['containment_pressure']}), - html.Td('Containment Pressure'), - html.Td(containment_pressure['x'][-1].strftime("%H:%M:%S")), - html.Td(format(containment_pressure['y'][-1], '0.2e'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['thermocouple_temp']}), - html.Td('Temperature TC'), - html.Td(thermocouple_temp['x'][-1].strftime("%H:%M:%S")), - html.Td(format(thermocouple_temp['y'][-1], '0.3'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['rtd_temp']}), - html.Td('Temperature RTD'), - html.Td(rtd_temp['x'][-1].strftime("%H:%M:%S")), - html.Td(format(rtd_temp['y'][-1], '0.3'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow1']}), - html.Td(FLOWNAMES['flow1']), - html.Td(flow1['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow1['y'][-1], '0.2'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow2']}), - html.Td(FLOWNAMES['flow2']), - html.Td(flow2['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow2['y'][-1], '0.2'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow3']}), - html.Td(FLOWNAMES['flow3']), - html.Td(flow3['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow3['y'][-1], '0.2'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow4']}), - html.Td(FLOWNAMES['flow4']), - html.Td(flow4['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow4['y'][-1], '0.2'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow5']}), - html.Td(FLOWNAMES['flow5']), - html.Td(flow5['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow5['y'][-1], '0.2'))], - style=bgstyle)]+ - [html.Tr([ - html.Td(style={'backgroundColor':COLOURS['flow6']}), - html.Td(FLOWNAMES['flow6']), - html.Td(flow6['x'][-1].strftime("%H:%M:%S")), - html.Td(format(flow6['y'][-1], '0.2'))], - style=bgstyle)]) + [html.Th("#"), html.Th("Name"), html.Th("Time"), html.Th("Value")], + style=bgstyle_header, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["main_chamber_pressure"]}), + html.Td("Main Chamber Pressure"), + html.Td(chamber_pressure["x"][-1].strftime("%H:%M:%S")), + html.Td(format(chamber_pressure["y"][-1], "0.2e")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["reactor_pressure"]}), + html.Td("Reactor Pressure"), + html.Td(reactor_pressure["x"][-1].strftime("%H:%M:%S")), + html.Td(format(reactor_pressure["y"][-1], "0.2e")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["buffer_pressure"]}), + html.Td("Buffer Pressure"), + html.Td(buffer_pressure["x"][-1].strftime("%H:%M:%S")), + html.Td(format(buffer_pressure["y"][-1], "0.2e")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["containment_pressure"]}), + html.Td("Containment Pressure"), + html.Td(containment_pressure["x"][-1].strftime("%H:%M:%S")), + html.Td(format(containment_pressure["y"][-1], "0.2e")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["thermocouple_temp"]}), + html.Td("Temperature TC"), + html.Td(thermocouple_temp["x"][-1].strftime("%H:%M:%S")), + html.Td(format(thermocouple_temp["y"][-1], "0.3")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["rtd_temp"]}), + html.Td("Temperature RTD"), + html.Td(rtd_temp["x"][-1].strftime("%H:%M:%S")), + html.Td(format(rtd_temp["y"][-1], "0.3")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow1"]}), + html.Td(FLOWNAMES["flow1"]), + html.Td(flow1["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow1["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow2"]}), + html.Td(FLOWNAMES["flow2"]), + html.Td(flow2["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow2["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow3"]}), + html.Td(FLOWNAMES["flow3"]), + html.Td(flow3["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow3["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow4"]}), + html.Td(FLOWNAMES["flow4"]), + html.Td(flow4["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow4["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow5"]}), + html.Td(FLOWNAMES["flow5"]), + html.Td(flow5["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow5["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + + [ + html.Tr( + [ + html.Td(style={"backgroundColor": COLOURS["flow6"]}), + html.Td(FLOWNAMES["flow6"]), + html.Td(flow6["x"][-1].strftime("%H:%M:%S")), + html.Td(format(flow6["y"][-1], "0.2")), + ], + style=bgstyle, + ) + ] + ) return out -if __name__ == '__main__': - APP.run_server(host='0.0.0.0', debug=True, port=8050) +if __name__ == "__main__": + APP.run_server(host="0.0.0.0", debug=True, port=8050) diff --git a/PyExpLabSys/apps/qms/mass_spec.py b/PyExpLabSys/apps/qms/mass_spec.py index 212c1c85..47c58840 100644 --- a/PyExpLabSys/apps/qms/mass_spec.py +++ b/PyExpLabSys/apps/qms/mass_spec.py @@ -5,6 +5,7 @@ import sys import time import datetime + try: import Queue as queue except ImportError: @@ -19,27 +20,37 @@ from PyExpLabSys.common.utilities import get_logger from PyExpLabSys.common.utilities import activate_library_logging from PyExpLabSys.common.supported_versions import python2_and_3 -BASEPATH = os.path.abspath(__file__)[:os.path.abspath(__file__).find('PyExpLabSys')] -sys.path.append(BASEPATH + '/PyExpLabSys/machines/' + sys.argv[1]) -import settings # pylint: disable=wrong-import-position -python2_and_3(__file__) -LOGGER = get_logger('Mass Spec', level='warning', file_log=True, - file_name='qms.txt', terminal_log=False, - email_on_warnings=False, email_on_errors=False, - file_max_bytes=104857600, file_backup_count=5) +BASEPATH = os.path.abspath(__file__)[: os.path.abspath(__file__).find("PyExpLabSys")] +sys.path.append(BASEPATH + "/PyExpLabSys/machines/" + sys.argv[1]) +import settings # pylint: disable=wrong-import-position + +python2_and_3(__file__) -activate_library_logging('PyExpLabSys.drivers.pfeiffer_qmg422', - logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qmg_status_output', - logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qmg_meta_channels', - logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qms', - logger_to_inherit_from=LOGGER) +LOGGER = get_logger( + "Mass Spec", + level="warning", + file_log=True, + file_name="qms.txt", + terminal_log=False, + email_on_warnings=False, + email_on_errors=False, + file_max_bytes=104857600, + file_backup_count=5, +) + +activate_library_logging("PyExpLabSys.drivers.pfeiffer_qmg422", logger_to_inherit_from=LOGGER) +activate_library_logging( + "PyExpLabSys.apps.qms.qmg_status_output", logger_to_inherit_from=LOGGER +) +activate_library_logging( + "PyExpLabSys.apps.qms.qmg_meta_channels", logger_to_inherit_from=LOGGER +) +activate_library_logging("PyExpLabSys.apps.qms.qms", logger_to_inherit_from=LOGGER) try: from local_channels import Local + LOCAL_READER = Local() LOCAL_READER.daemon = True LOCAL_READER.start() @@ -48,39 +59,47 @@ class MassSpec(object): - """ User interface to mass spec code """ + """User interface to mass spec code""" + def __init__(self): sql_queue = queue.Queue() - self.data_saver = database_saver.SqlSaver(settings.username, - settings.username, sql_queue) + self.data_saver = database_saver.SqlSaver( + settings.username, settings.username, sql_queue + ) self.data_saver.start() - if settings.qmg == '420': + if settings.qmg == "420": self.qmg = qmg420.qmg_420(settings.port) - if settings.qmg == '422': + if settings.qmg == "422": print(settings.port) self.qmg = qmg422.qmg_422(port=settings.port, speed=settings.speed) if not settings.qmg: self.qmg = None try: - livesocket = LiveSocket(settings.name + '-mass-spec', ['qms-value']) + livesocket = LiveSocket(settings.name + "-mass-spec", ["qms-value"]) livesocket.start() except: livesocket = None - pullsocket = DateDataPullSocket(settings.name + '-mass-spec', ['qms-value']) + pullsocket = DateDataPullSocket(settings.name + "-mass-spec", ["qms-value"]) pullsocket.start() - self.qms = ms.QMS(self.qmg, sql_queue, chamber=settings.chamber, - credentials=settings.username, livesocket=livesocket, - pullsocket=pullsocket) + self.qms = ms.QMS( + self.qmg, + sql_queue, + chamber=settings.chamber, + credentials=settings.username, + livesocket=livesocket, + pullsocket=pullsocket, + ) self.qmg.reverse_range = settings.reverse_range - self.printer = qmg_status_output.QmsStatusOutput(self.qms, - sql_saver_instance=self.data_saver) + self.printer = qmg_status_output.QmsStatusOutput( + self.qms, sql_saver_instance=self.data_saver + ) self.printer.start() def sem_and_filament(self, turn_on=False, voltage=1800): - """ Turn on and off the mas spec """ + """Turn on and off the mas spec""" if turn_on is True: self.qmg.sem_status(voltage=voltage, turn_on=True) self.qmg.emission_status(current=0.1, turn_on=True) @@ -89,41 +108,55 @@ def sem_and_filament(self, turn_on=False, voltage=1800): self.qmg.emission_status(current=0.1, turn_off=True) def leak_search(self, speed=10): - """ Do a mass time scan on mass 4 """ + """Do a mass time scan on mass 4""" timestamp = datetime.datetime.now() channel_list = {} - channel_list['ms'] = {} - channel_list['ms'][0] = {'comment': 'Leak Search', 'autorange':False, - 'mass-scan-interval':999999999} - channel_list['ms'][1] = {'masslabel': 'He', 'speed':speed, 'mass':4, 'amp_range':9} - self.qms.mass_time(channel_list['ms'], timestamp, no_save=True) - - def mass_time_scan(self, channel_list='channel_list'): - """ Perform a mass-time scan """ + channel_list["ms"] = {} + channel_list["ms"][0] = { + "comment": "Leak Search", + "autorange": False, + "mass-scan-interval": 999999999, + } + channel_list["ms"][1] = { + "masslabel": "He", + "speed": speed, + "mass": 4, + "amp_range": 9, + } + self.qms.mass_time(channel_list["ms"], timestamp, no_save=True) + + def mass_time_scan(self, channel_list="channel_list"): + """Perform a mass-time scan""" timestamp = datetime.datetime.now() - qms_channel_list = self.qms.read_ms_channel_list(BASEPATH + '/PyExpLabSys/machines/' + - sys.argv[1] + '/channel_lists/' + - channel_list + '.txt') + qms_channel_list = self.qms.read_ms_channel_list( + BASEPATH + + "/PyExpLabSys/machines/" + + sys.argv[1] + + "/channel_lists/" + + channel_list + + ".txt" + ) meta_udp = qmg_meta_channels.MetaChannels(self.qms, timestamp, qms_channel_list) meta_udp.daemon = True meta_udp.start() self.printer.meta_channels = meta_udp - self.qms.mass_time(qms_channel_list['ms'], timestamp) + self.qms.mass_time(qms_channel_list["ms"], timestamp) - def mass_scan(self, start_mass=0, scan_width=100, comment='bg_scan', amp_range=0): - """ Perform mass scan """ + def mass_scan(self, start_mass=0, scan_width=100, comment="bg_scan", amp_range=0): + """Perform mass scan""" self.qms.mass_scan(start_mass, scan_width, comment, amp_range) time.sleep(1) def sleep(self, duration): - """ Sleep for a while and print output """ - msg = 'Sleeping for {} seconds..' + """Sleep for a while and print output""" + msg = "Sleeping for {} seconds.." for i in range(duration, 0, -1): self.qms.operating_mode = msg.format(i) time.sleep(1) - self.qms.operating_mode = 'Idling' + self.qms.operating_mode = "Idling" + -if __name__ == '__main__': +if __name__ == "__main__": try: # Initialize QMS MS = MassSpec() @@ -131,16 +164,16 @@ def sleep(self, duration): MS.sleep(10) # Choose and start measurement(s) - #MS.leak_search(speed=8) + # MS.leak_search(speed=8) MS.mass_time_scan() - #MS.mass_scan(0, 50, 'flow6', amp_range=-11) - #MS.mass_scan(0, 50, 'After power line cleanup', amp_range=-11) + # MS.mass_scan(0, 50, 'flow6', amp_range=-11) + # MS.mass_scan(0, 50, 'After power line cleanup', amp_range=-11) - #MS.mass_scan(0, 50, 'Background scan -11', amp_range=-11) - #MS.mass_scan(0, 50, 'Background scan -9', amp_range=-9) - #MS.mass_scan(0, 50, 'Background scan -7', amp_range=-7) + # MS.mass_scan(0, 50, 'Background scan -11', amp_range=-11) + # MS.mass_scan(0, 50, 'Background scan -9', amp_range=-9) + # MS.mass_scan(0, 50, 'Background scan -7', amp_range=-7) except: MS.printer.stop() raise diff --git a/PyExpLabSys/apps/qms/meta_channel_logger.py b/PyExpLabSys/apps/qms/meta_channel_logger.py index bea87252..4f8f2ebe 100644 --- a/PyExpLabSys/apps/qms/meta_channel_logger.py +++ b/PyExpLabSys/apps/qms/meta_channel_logger.py @@ -5,42 +5,55 @@ import sys import time import datetime + try: import Queue as queue except ImportError: import queue import PyExpLabSys.common.database_saver as database_saver -#import PyExpLabSys.drivers.pfeiffer_qmg420 as qmg420 -#import PyExpLabSys.drivers.pfeiffer_qmg422 as qmg422 + +# import PyExpLabSys.drivers.pfeiffer_qmg420 as qmg420 +# import PyExpLabSys.drivers.pfeiffer_qmg422 as qmg422 import PyExpLabSys.apps.qms.qms as ms import PyExpLabSys.apps.qms.qmg_status_output as qmg_status_output import PyExpLabSys.apps.qms.qmg_meta_channels as qmg_meta_channels -from PyExpLabSys.common.sockets import DateDataPullSocket#, LiveSocket +from PyExpLabSys.common.sockets import DateDataPullSocket # , LiveSocket from PyExpLabSys.common.utilities import get_logger from PyExpLabSys.common.utilities import activate_library_logging from PyExpLabSys.common.supported_versions import python2_and_3 -BASEPATH = os.path.abspath(__file__)[:os.path.abspath(__file__).find('PyExpLabSys')] -sys.path.append(BASEPATH + '/PyExpLabSys/machines/' + sys.argv[1]) + +BASEPATH = os.path.abspath(__file__)[: os.path.abspath(__file__).find("PyExpLabSys")] +sys.path.append(BASEPATH + "/PyExpLabSys/machines/" + sys.argv[1]) time.sleep(2) -import settings # pylint: disable=wrong-import-position +import settings # pylint: disable=wrong-import-position + python2_and_3(__file__) -LOGGER = get_logger('MetaChannelLogger', level='warning', file_log=True, - file_name='MetaChannelLogger.txt', terminal_log=False, - email_on_warnings=False, email_on_errors=False, - file_max_bytes=104857600, file_backup_count=5) +LOGGER = get_logger( + "MetaChannelLogger", + level="warning", + file_log=True, + file_name="MetaChannelLogger.txt", + terminal_log=False, + email_on_warnings=False, + email_on_errors=False, + file_max_bytes=104857600, + file_backup_count=5, +) -#activate_library_logging('PyExpLabSys.drivers.pfeiffer_qmg422', +# activate_library_logging('PyExpLabSys.drivers.pfeiffer_qmg422', # logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qmg_status_output', - logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qmg_meta_channels', - logger_to_inherit_from=LOGGER) -activate_library_logging('PyExpLabSys.apps.qms.qms', - logger_to_inherit_from=LOGGER) +activate_library_logging( + "PyExpLabSys.apps.qms.qmg_status_output", logger_to_inherit_from=LOGGER +) +activate_library_logging( + "PyExpLabSys.apps.qms.qmg_meta_channels", logger_to_inherit_from=LOGGER +) +activate_library_logging("PyExpLabSys.apps.qms.qms", logger_to_inherit_from=LOGGER) try: from local_channels import Local + LOCAL_READER = Local() LOCAL_READER.daemon = True LOCAL_READER.start() @@ -49,21 +62,26 @@ class MetaLogger(object): - """ User interface to mass spec code """ + """User interface to mass spec code""" + def __init__(self): sql_queue = queue.Queue() - self.data_saver = database_saver.SqlSaver(settings.username, - settings.username, sql_queue) + self.data_saver = database_saver.SqlSaver( + settings.username, settings.username, sql_queue + ) self.data_saver.start() - self.qmg = None #qmg422.qmg_422(port=settings.port, speed=settings.speed) + self.qmg = None # qmg422.qmg_422(port=settings.port, speed=settings.speed) - - self.qms = ms.QMS(self.qmg, sql_queue, chamber=settings.chamber, - credentials=settings.username,# livesocket=livesocket, - )#pullsocket=pullsocket) + self.qms = ms.QMS( + self.qmg, + sql_queue, + chamber=settings.chamber, + credentials=settings.username, # livesocket=livesocket, + ) # pullsocket=pullsocket) try: - self.printer = qmg_status_output.QmsStatusOutput(self.qms, - sql_saver_instance=self.data_saver) + self.printer = qmg_status_output.QmsStatusOutput( + self.qms, sql_saver_instance=self.data_saver + ) except KeyError as e: print(e) self.printer.start() @@ -71,12 +89,17 @@ def __init__(self): def __del__(self): self.printer.stop() - def meta_channels_logger(self, channel_list='channel_list'): - """ start logging of meta data """ + def meta_channels_logger(self, channel_list="channel_list"): + """start logging of meta data""" timestamp = datetime.datetime.now() - qms_channel_list = self.qms.read_ms_channel_list(BASEPATH + '/PyExpLabSys/machines/' + - sys.argv[1] + '/channel_lists/' + - channel_list + '.txt') + qms_channel_list = self.qms.read_ms_channel_list( + BASEPATH + + "/PyExpLabSys/machines/" + + sys.argv[1] + + "/channel_lists/" + + channel_list + + ".txt" + ) meta_udp = qmg_meta_channels.MetaChannels(self.qms, timestamp, qms_channel_list) meta_udp.daemon = True @@ -86,14 +109,15 @@ def meta_channels_logger(self, channel_list='channel_list'): self.qms.meta_channels_only(timestamp) def sleep(self, duration): - """ Sleep for a while and print output """ - msg = 'Sleeping for {} seconds..' + """Sleep for a while and print output""" + msg = "Sleeping for {} seconds.." for i in range(duration, 0, -1): self.qms.operating_mode = msg.format(i) time.sleep(1) - self.qms.operating_mode = 'Idling' + self.qms.operating_mode = "Idling" + -if __name__ == '__main__': +if __name__ == "__main__": ML = MetaLogger() ML.sleep(2) diff --git a/PyExpLabSys/apps/qms/qmg_meta_channels.py b/PyExpLabSys/apps/qms/qmg_meta_channels.py index ff5c87b5..3b17296a 100644 --- a/PyExpLabSys/apps/qms/qmg_meta_channels.py +++ b/PyExpLabSys/apps/qms/qmg_meta_channels.py @@ -4,59 +4,65 @@ import socket import logging from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) + class MetaChannels(threading.Thread): - """ A class to handle meta data for the QMS program """ + """A class to handle meta data for the QMS program""" def __init__(self, qms, timestamp, channel_list): - """ Initalize the instance of the class """ + """Initalize the instance of the class""" threading.Thread.__init__(self) self.timestamp = timestamp - self.comment = channel_list['ms'][0]['comment'] + self.comment = channel_list["ms"][0]["comment"] self.qms = qms self.channels = [] channel_data = {} - for i in range(1, len(channel_list['meta']) + 1): - channel_data['repeat_interval'] = channel_list['meta'][i]['repeat_interval'] - channel_data['label'] = channel_list['meta'][i]['label'] - channel_data['host'] = channel_list['meta'][i]['host'] - channel_data['port'] = channel_list['meta'][i]['port'] - channel_data['cmd'] = channel_list['meta'][i]['command'] - channel_data['measurement_type'] = channel_list['meta'][i]['measurement_type'] + for i in range(1, len(channel_list["meta"]) + 1): + channel_data["repeat_interval"] = channel_list["meta"][i]["repeat_interval"] + channel_data["label"] = channel_list["meta"][i]["label"] + channel_data["host"] = channel_list["meta"][i]["host"] + channel_data["port"] = channel_list["meta"][i]["port"] + channel_data["cmd"] = channel_list["meta"][i]["command"] + channel_data["measurement_type"] = channel_list["meta"][i]["measurement_type"] self.create_channel(channel_data) def create_channel(self, channel_data): - """ Create a meta channel. + """Create a meta channel. Uses the SQL-communication function of the qms class to create a SQL-entry for the meta-channel. """ - sql_id = self.qms.create_mysql_measurement(0, self.timestamp, - masslabel=channel_data['label'], - amp_range=-1, comment=self.comment, - metachannel=True, - measurement_type=channel_data['measurement_type']) - channel_data['id'] = sql_id - channel_data['value'] = -1 + sql_id = self.qms.create_mysql_measurement( + 0, + self.timestamp, + masslabel=channel_data["label"], + amp_range=-1, + comment=self.comment, + metachannel=True, + measurement_type=channel_data["measurement_type"], + ) + channel_data["id"] = sql_id + channel_data["value"] = -1 reader = UdpChannel(channel_data, self.qms, self.timestamp) reader.start() self.channels.append(reader) class UdpChannel(threading.Thread): - """ A class to handle meta data for the QMS program. + """A class to handle meta data for the QMS program. Each instance of this class will communicate with a hosts via udp. """ def __init__(self, channel_data, qms, timestamp): - """ Initalize the instance of the class """ + """Initalize the instance of the class""" threading.Thread.__init__(self) self.channel_data = channel_data.copy() self.timestamp = timestamp @@ -65,29 +71,32 @@ def __init__(self, channel_data, qms, timestamp): self.daemon = True def run(self): - start_time = (time.mktime(self.timestamp.timetuple()) + - self.timestamp.microsecond / 1000000.0) + start_time = ( + time.mktime(self.timestamp.timetuple()) + self.timestamp.microsecond / 1000000.0 + ) while True: t_channel_start = time.time() sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(0.2) - LOGGER.debug('Meta command: %s', self.channel_data['cmd']) + LOGGER.debug("Meta command: %s", self.channel_data["cmd"]) try: - sock.sendto(self.channel_data['cmd'].encode('ascii'), - (self.channel_data['host'], self.channel_data['port'])) + sock.sendto( + self.channel_data["cmd"].encode("ascii"), + (self.channel_data["host"], self.channel_data["port"]), + ) received = sock.recv(1024) except socket.timeout: received = b"" - LOGGER.warning('Socket timeout: %s', self.channel_data['cmd']) + LOGGER.warning("Socket timeout: %s", self.channel_data["cmd"]) try: received = received.strip().decode() except UnicodeDecodeError: received = "" - LOGGER.warning('Unicode Decode Error: %s', received) - LOGGER.debug('Meta recieve: %s', received) - if self.channel_data['cmd'][-4:] == '#raw': - received = received[received.find(',')+1:] + LOGGER.warning("Unicode Decode Error: %s", received) + LOGGER.debug("Meta recieve: %s", received) + if self.channel_data["cmd"][-4:] == "#raw": + received = received[received.find(",") + 1 :] sock.close() try: @@ -95,24 +104,23 @@ def run(self): LOGGER.debug(str(value)) sqltime = str((time.time() - start_time) * 1000) except ValueError: - LOGGER.warning('Meta-channel, could not convert to float: %s', received) + LOGGER.warning("Meta-channel, could not convert to float: %s", received) value = None except TypeError: - LOGGER.warning('Type error from meta channel, most likely during shutdown') + LOGGER.warning("Type error from meta channel, most likely during shutdown") value = None - except Exception as e: # pylint: disable=broad-except - LOGGER.error('Unknown error: %s', e) + except Exception as e: # pylint: disable=broad-except + LOGGER.error("Unknown error: %s", e) value = None - self.channel_data['value'] = value + self.channel_data["value"] = value if not value is None: - query = 'insert into xy_values_' + self.qms.chamber + ' ' + query = "insert into xy_values_" + self.qms.chamber + " " query += 'set measurement="' - query += str(self.channel_data['id']) + '", x="' + sqltime + query += str(self.channel_data["id"]) + '", x="' + sqltime query += '", y="' + str(value) + '"' self.qms.sqlqueue.put((query, None)) - time_spend = time.time() - t_channel_start - if time_spend < self.channel_data['repeat_interval']: - time.sleep(self.channel_data['repeat_interval'] - time_spend) + if time_spend < self.channel_data["repeat_interval"]: + time.sleep(self.channel_data["repeat_interval"] - time_spend) diff --git a/PyExpLabSys/apps/qms/qmg_status_output.py b/PyExpLabSys/apps/qms/qmg_status_output.py index 0e1f8550..70a15299 100644 --- a/PyExpLabSys/apps/qms/qmg_status_output.py +++ b/PyExpLabSys/apps/qms/qmg_status_output.py @@ -4,14 +4,17 @@ import curses import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) + class QmsStatusOutput(threading.Thread): - """ Text UI for mass spec program """ + """Text UI for mass spec program""" + def __init__(self, qms_instance, sql_saver_instance=None, meta_channel_instance=None): threading.Thread.__init__(self) self.daemon = True @@ -36,57 +39,68 @@ def run(self): if self.qms.operating_mode == "Mass Time": try: - timestamp = ("Timestamp: " + - self.qms.current_timestamp.strftime("%Y-%m-%d %H:%M:%S")) + timestamp = "Timestamp: " + self.qms.current_timestamp.strftime( + "%Y-%m-%d %H:%M:%S" + ) except AttributeError: - timestamp = 'Timestamp: None' + timestamp = "Timestamp: None" self.screen.addstr(3, 1, timestamp) runtime = "Experiment runtime: {0:.1f}s ".format(self.qms.measurement_runtime) self.screen.addstr(4, 1, runtime) qsize = "Queue length: {0:.0f} items".format(self.sql.queue.qsize()) self.screen.addstr(5, 1, qsize) self.screen.clrtoeol() - self.screen.addstr(6, 1, 'Emission: {}'.format(self.qms.qmg.state['emission'])) + self.screen.addstr(6, 1, "Emission: {}".format(self.qms.qmg.state["emission"])) self.screen.clrtoeol() - self.screen.addstr(6, 40, 'SEM status: {}'.format(self.qms.qmg.state['sem'])) + self.screen.addstr(6, 40, "SEM status: {}".format(self.qms.qmg.state["sem"])) self.screen.clrtoeol() - #self.screen.addstr(5,20, self.qms.channel_list[0]['comment']) - self.screen.addstr(9, 1, 'QMS-channels') + # self.screen.addstr(5,20, self.qms.channel_list[0]['comment']) + self.screen.addstr(9, 1, "QMS-channels") for i in range(1, len(self.qms.channel_list) + 1): channel = self.qms.channel_list[i] - self.screen.addstr(10+i, 1, channel['masslabel'] + ': ' + - channel['value'] + ' ') + self.screen.addstr( + 10 + i, + 1, + channel["masslabel"] + ": " + channel["value"] + " ", + ) - self.screen.addstr(9, 30, 'Meta-channels') + self.screen.addstr(9, 30, "Meta-channels") if self.meta_channels is None: - self.screen.addstr(11, 30, 'No access to meta-channels') + self.screen.addstr(11, 30, "No access to meta-channels") else: for i in range(0, len(self.meta_channels.channels)): channel = self.meta_channels.channels[i] - self.screen.addstr(11 + i, 30, channel.channel_data['label'] + - ': ' + str(channel.channel_data['value']) + - ' ') + self.screen.addstr( + 11 + i, + 30, + channel.channel_data["label"] + + ": " + + str(channel.channel_data["value"]) + + " ", + ) - if self.qms.operating_mode == 'Mass-scan': + if self.qms.operating_mode == "Mass-scan": self.screen.addstr(2, 1, self.qms.message) try: - timestamp = ("Timestamp: " + - self.qms.current_timestamp.strftime("%Y-%m-%d %H:%M:%S")) + timestamp = "Timestamp: " + self.qms.current_timestamp.strftime( + "%Y-%m-%d %H:%M:%S" + ) except AttributeError: - timestamp = 'Timestamp: None' + timestamp = "Timestamp: None" self.screen.addstr(3, 1, timestamp) runtime = "Experiment runtime: {0:.1f}s ".format(self.qms.measurement_runtime) self.screen.addstr(4, 1, runtime) - self.screen.addstr(5, 1, 'Current action: ' + self.qms.current_action) + self.screen.addstr(5, 1, "Current action: " + self.qms.current_action) self.screen.clrtoeol() if self.qms.operating_mode == "Meta Channels Only": try: - timestamp = ("Timestamp: " + - self.qms.current_timestamp.strftime("%Y-%m-%d %H:%M:%S")) + timestamp = "Timestamp: " + self.qms.current_timestamp.strftime( + "%Y-%m-%d %H:%M:%S" + ) except AttributeError: - timestamp = 'Timestamp: None' + timestamp = "Timestamp: None" self.screen.addstr(3, 1, timestamp) runtime = "Experiment runtime: {0:.1f}s ".format(self.qms.measurement_runtime) self.screen.addstr(4, 1, runtime) @@ -94,16 +108,20 @@ def run(self): self.screen.addstr(5, 1, qsize) self.screen.clrtoeol() - self.screen.addstr(9, 30, 'Meta-channels') + self.screen.addstr(9, 30, "Meta-channels") if self.meta_channels is None: - self.screen.addstr(11, 30, 'No access to meta-channels') + self.screen.addstr(11, 30, "No access to meta-channels") else: for i in range(0, len(self.meta_channels.channels)): channel = self.meta_channels.channels[i] - self.screen.addstr(11 + i, 30, channel.channel_data['label'] + - ': ' + str(channel.channel_data['value']) + - ' ') - + self.screen.addstr( + 11 + i, + 30, + channel.channel_data["label"] + + ": " + + str(channel.channel_data["value"]) + + " ", + ) if not self.sql is None: commits = "SQL commits: {0:.0f}".format(self.sql.commits) @@ -112,14 +130,14 @@ def run(self): self.screen.addstr(4, 40, commit_time) key_value = self.screen.getch() - if key_value == ord('q'): + if key_value == ord("q"): self.qms.stop = True self.screen.refresh() time.sleep(0.25) def stop(self): - """ Stop and cleanup """ + """Stop and cleanup""" curses.nocbreak() self.screen.keypad(0) curses.echo() diff --git a/PyExpLabSys/apps/qms/qms.py b/PyExpLabSys/apps/qms/qms.py index 97dea980..b1825865 100755 --- a/PyExpLabSys/apps/qms/qms.py +++ b/PyExpLabSys/apps/qms/qms.py @@ -9,67 +9,90 @@ import logging import MySQLdb from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) + class QMS(object): - """ Complete mass spectrometer """ - def __init__(self, qmg, sqlqueue=None, chamber='dummy', credentials='dummy', - livesocket=None, pullsocket=None): + """Complete mass spectrometer""" + + def __init__( + self, + qmg, + sqlqueue=None, + chamber="dummy", + credentials="dummy", + livesocket=None, + pullsocket=None, + ): self.qmg = qmg if not sqlqueue is None: self.sqlqueue = sqlqueue - else: #We make a dummy queue to make the program work + else: # We make a dummy queue to make the program work self.sqlqueue = queue.Queue() - self.operating_mode = 'Idling' - self.current_action = 'Idling' - self.message = '' + self.operating_mode = "Idling" + self.current_action = "Idling" + self.message = "" self.livesocket = livesocket self.pullsocket = pullsocket - self.current_timestamp = 'None' + self.current_timestamp = "None" self.measurement_runtime = 0 self.stop = False self.chamber = chamber self.credentials = credentials self.channel_list = {} - LOGGER.info('Program started. Log level: %s', LOGGER.getEffectiveLevel()) + LOGGER.info("Program started. Log level: %s", LOGGER.getEffectiveLevel()) def communication_mode(self, computer_control=False): - """ Set communication for computer control """ + """Set communication for computer control""" return self.qmg.communication_mode(computer_control) def emission_status(self, current=-1, turn_off=False, turn_on=False): - """ Return emission status """ + """Return emission status""" return self.qmg.emission_status(current, turn_off, turn_on) def sem_status(self, voltage=-1, turn_off=False, turn_on=False): - """ Return the status of SEM """ + """Return the status of SEM""" return self.qmg.sem_status(voltage, turn_off, turn_on) def detector_status(self, sem=False, faraday_cup=False): - """ Report detector status """ + """Report detector status""" return self.qmg.detector_status(sem, faraday_cup) def read_voltages(self): - """ Read the voltage of the lens system """ + """Read the voltage of the lens system""" self.qmg.read_voltages() def simulation(self): - """ Chekcs wheter the instruments returns real or simulated data """ + """Chekcs wheter the instruments returns real or simulated data""" self.qmg.simulation() def config_channel(self, channel, params, enable=""): - """ Setup a channel for measurement """ - self.qmg.config_channel(channel, mass=params['mass'], speed=params['speed'], - amp_range=params['amp_range'], enable=enable) - - def create_mysql_measurement(self, channel, timestamp, masslabel, amp_range, - comment, mass=0, metachannel=False, - measurement_type=5): - """ Creates a MySQL row for a channel. + """Setup a channel for measurement""" + self.qmg.config_channel( + channel, + mass=params["mass"], + speed=params["speed"], + amp_range=params["amp_range"], + enable=enable, + ) + + def create_mysql_measurement( + self, + channel, + timestamp, + masslabel, + amp_range, + comment, + mass=0, + metachannel=False, + measurement_type=5, + ): + """Creates a MySQL row for a channel. Create a row in the measurements table and populates it with the information from the arguments as well as what can be auto-generated. @@ -88,22 +111,32 @@ def create_mysql_measurement(self, channel, timestamp, masslabel, amp_range, sem_voltage = self.qmg.read_sem_voltage() preamp_range = str(amp_range) timestep = self.qmg.read_timestep() - #TODO: We need a look-up table, this number is not physical + # TODO: We need a look-up table, this number is not physical else: sem_voltage = "-1" preamp_range = "-1" timestep = "-1" - query = ('insert into measurements_{} set mass_label="{}", sem_voltage="{}",' - 'preamp_range="{}", time="{}", type="{}", comment="{}", timestep={},' - 'actual_mass={}').format(self.chamber, masslabel, sem_voltage, preamp_range, - timestamp.strftime("%Y-%m-%d %H:%M:%S"), - measurement_type, comment, timestep, mass) + query = ( + 'insert into measurements_{} set mass_label="{}", sem_voltage="{}",' + 'preamp_range="{}", time="{}", type="{}", comment="{}", timestep={},' + "actual_mass={}" + ).format( + self.chamber, + masslabel, + sem_voltage, + preamp_range, + timestamp.strftime("%Y-%m-%d %H:%M:%S"), + measurement_type, + comment, + timestep, + mass, + ) LOGGER.info(query) cursor.execute(query) cnxn.commit() - query = 'select id from measurements_{} order by id desc limit 1'.format(self.chamber) + query = "select id from measurements_{} order by id desc limit 1".format(self.chamber) LOGGER.info(query) cursor.execute(query) id_number = cursor.fetchone() @@ -111,92 +144,104 @@ def create_mysql_measurement(self, channel, timestamp, masslabel, amp_range, cnxn.close() return id_number - def read_ms_channel_list(self, filename='channel_list.txt'): - """ Read and parse channel list """ + def read_ms_channel_list(self, filename="channel_list.txt"): + """Read and parse channel list""" channel_list = {} - channel_list['ms'] = {} - channel_list['meta'] = {} + channel_list["ms"] = {} + channel_list["meta"] = {} - channel_file = open(filename, 'r') + channel_file = open(filename, "r") datafile = channel_file.read() - lines = datafile.split('\n') + lines = datafile.split("\n") data_lines = [] for line in lines: - if (len(line) > 0) and (not line[0] == '#'): + if (len(line) > 0) and (not line[0] == "#"): data_lines.append(line) ms_count = 1 meta = 1 for line in data_lines: - items = line.split(':') + items = line.split(":") key = items[0].lower().strip() - if key == 'comment': + if key == "comment": comment = items[1].strip() - if key == 'mass-scan-interval': + if key == "mass-scan-interval": msi = float(items[1].strip()) - if key == 'ms_channel': - params = items[1].split(',') + if key == "ms_channel": + params = items[1].split(",") params = [param.strip() for param in params] - label = params[params.index('masslabel') + 1] - speed = int(params[params.index('speed') + 1]) - mass = float(params[params.index('mass') + 1]) - amp_range = int(params[params.index('amp_range') + 1]) - channel_list['ms'][ms_count] = {'masslabel':label, 'speed':speed, - 'mass':mass, 'amp_range':amp_range} + label = params[params.index("masslabel") + 1] + speed = int(params[params.index("speed") + 1]) + mass = float(params[params.index("mass") + 1]) + amp_range = int(params[params.index("amp_range") + 1]) + channel_list["ms"][ms_count] = { + "masslabel": label, + "speed": speed, + "mass": mass, + "amp_range": amp_range, + } ms_count += 1 - - if key == 'meta_channel': - params = items[1].split(',') + if key == "meta_channel": + params = items[1].split(",") params = [param.strip() for param in params] - host = params[params.index('host')+1] - port = int(params[params.index('port')+1]) - label = params[params.index('label')+1] - repeat_interval = float(params[params.index('repeat_interval')+1]) - command = params[params.index('command')+1] - channel_list['meta'][meta] = {'host':host, 'port':port, - 'repeat_interval':repeat_interval, - 'label':label, 'command':command} + host = params[params.index("host") + 1] + port = int(params[params.index("port") + 1]) + label = params[params.index("label") + 1] + repeat_interval = float(params[params.index("repeat_interval") + 1]) + command = params[params.index("command") + 1] + channel_list["meta"][meta] = { + "host": host, + "port": port, + "repeat_interval": repeat_interval, + "label": label, + "command": command, + } try: - meas_type = int(params[params.index('measurement_type')+1]) - channel_list['meta'][meta]['measurement_type'] = meas_type + meas_type = int(params[params.index("measurement_type") + 1]) + channel_list["meta"][meta]["measurement_type"] = meas_type except ValueError: - channel_list['meta'][meta]['measurement_type'] = 5 - #print(channel_list['meta'][meta]) - LOGGER.warning(f'No measurement type was giving in channel list for {label}' - 'Revert to default 5 (mass_scan)') + channel_list["meta"][meta]["measurement_type"] = 5 + # print(channel_list['meta'][meta]) + LOGGER.warning( + f"No measurement type was giving in channel list for {label}" + "Revert to default 5 (mass_scan)" + ) meta += 1 # Index 0 is used to hold general parameters - channel_list['ms'][0] = {'comment':comment, 'mass-scan-interval':msi} + channel_list["ms"][0] = {"comment": comment, "mass-scan-interval": msi} return channel_list - def create_ms_channellist(self, channel_list, timestamp, no_save=False): - """ This function creates the channel-list and the - associated mysql-entries """ + """This function creates the channel-list and the + associated mysql-entries""" ids = {} - params = {'mass':99, 'speed':1, 'amp_range':-5} + params = {"mass": 99, "speed": 1, "amp_range": -5} for i in range(0, 16): - self.config_channel(i, params, enable='no') + self.config_channel(i, params, enable="no") - comment = channel_list[0]['comment'] + comment = channel_list[0]["comment"] for i in range(1, len(channel_list)): channel = channel_list[i] self.config_channel(channel=i, params=channel, enable="yes") - self.channel_list[i] = {'masslabel':channel['masslabel'], 'value':'-'} + self.channel_list[i] = {"masslabel": channel["masslabel"], "value": "-"} if no_save is False: - ids[i] = self.create_mysql_measurement(i, timestamp, mass=channel['mass'], - masslabel=channel['masslabel'], - amp_range=channel['amp_range'], - comment=comment) + ids[i] = self.create_mysql_measurement( + i, + timestamp, + mass=channel["mass"], + masslabel=channel["masslabel"], + amp_range=channel["amp_range"], + comment=comment, + ) else: ids[i] = i ids[0] = timestamp @@ -204,71 +249,74 @@ def create_ms_channellist(self, channel_list, timestamp, no_save=False): return ids def check_reverse(self, value, ms_channel_list, channel): - """ Fix the value according to pre-amplifier and qmg-type """ - if self.qmg.type == '422' and self.qmg.reverse_range is True: - amp_range = ms_channel_list[channel]['amp_range'] + """Fix the value according to pre-amplifier and qmg-type""" + if self.qmg.type == "422" and self.qmg.reverse_range is True: + amp_range = ms_channel_list[channel]["amp_range"] if amp_range in (-9, -10): value = value * 100.0 if amp_range in (-11, -12): value = value / 100.0 - if self.qmg.type == '420': - LOGGER.error('Value: %f', value) - LOGGER.error(ms_channel_list[channel]['amp_range']) - range_val = 10**ms_channel_list[channel]['amp_range'] + if self.qmg.type == "420": + LOGGER.error("Value: %f", value) + LOGGER.error(ms_channel_list[channel]["amp_range"]) + range_val = 10 ** ms_channel_list[channel]["amp_range"] value = value * range_val - LOGGER.error('Range-value: %f', value) + LOGGER.error("Range-value: %f", value) return value def meta_channels_only(self, timestamp, no_save=False): - """ Start meta channel only measurement """ + """Start meta channel only measurement""" self.operating_mode = "Meta Channels Only" self.stop = False - start_time = (time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1000000.0) + start_time = time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1000000.0 self.current_timestamp = timestamp while self.stop is False: - LOGGER.info('start meta channels only measurement run') + LOGGER.info("start meta channels only measurement run") time.sleep(0.01) scan_start_time = time.time() - self.measurement_runtime = time.time()-start_time - #LOGGER.error('Scan time: %f', time.time() - scan_start_time) + self.measurement_runtime = time.time() - start_time + # LOGGER.error('Scan time: %f', time.time() - scan_start_time) self.operating_mode = "Idling" def mass_time(self, ms_channel_list, timestamp, no_save=False): - """ Perfom a mass-time scan """ + """Perfom a mass-time scan""" self.operating_mode = "Mass Time" self.stop = False number_of_channels = len(ms_channel_list) - 1 self.qmg.mass_time(number_of_channels) - start_time = (time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1000000.0) + start_time = time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1000000.0 ids = self.create_ms_channellist(ms_channel_list, timestamp, no_save=no_save) self.current_timestamp = timestamp last_mass_scan_time = time.time() while self.stop is False: - if time.time() - last_mass_scan_time > ms_channel_list[0]['mass-scan-interval']: - LOGGER.info('start mass scan') + if time.time() - last_mass_scan_time > ms_channel_list[0]["mass-scan-interval"]: + LOGGER.info("start mass scan") last_mass_scan_time = time.time() - self.mass_scan(comment=ms_channel_list[0]['comment'], amp_range=-11, - update_current_timestamp=False) + self.mass_scan( + comment=ms_channel_list[0]["comment"], + amp_range=-11, + update_current_timestamp=False, + ) self.qmg.mass_time(number_of_channels) self.operating_mode = "Mass Time" - LOGGER.info('start measurement run') + LOGGER.info("start measurement run") self.qmg.set_channel(1) scan_start_time = time.time() self.qmg.start_measurement() - #time.sleep(0.01) - save_values = True # Will be set to false if we do not trust values for this scan + # time.sleep(0.01) + save_values = True # Will be set to false if we do not trust values for this scan for channel in range(1, number_of_channels + 1): - self.measurement_runtime = time.time()-start_time + self.measurement_runtime = time.time() - start_time value, usefull = self.qmg.get_single_sample() - LOGGER.debug('Value: {}\tUsefull: {}'.format(value, usefull)) + LOGGER.debug("Value: {}\tUsefull: {}".format(value, usefull)) if usefull is False: save_values = False - #self.channel_list[channel]['value'] = value - #sqltime = str((time.time() - start_time) * 1000) + # self.channel_list[channel]['value'] = value + # sqltime = str((time.time() - start_time) * 1000) if value == "": break else: @@ -276,99 +324,114 @@ def mass_time(self, ms_channel_list, timestamp, no_save=False): value = float(value) except ValueError: value = -1 - LOGGER.error('Value error, could not convert to float') + LOGGER.error("Value error, could not convert to float") value = self.check_reverse(value, ms_channel_list, channel) - query = ('insert into xy_values_{} set measurement = "{}", ' + - 'x="{}", y="{}"').format(self.chamber, - ids[channel], - (time.time() - start_time) * 1000, - value) - self.channel_list[channel]['value'] = str(value) + query = ( + 'insert into xy_values_{} set measurement = "{}", ' + 'x="{}", y="{}"' + ).format( + self.chamber, + ids[channel], + (time.time() - start_time) * 1000, + value, + ) + self.channel_list[channel]["value"] = str(value) if self.livesocket is not None and usefull: - self.livesocket.set_point_now('qms-value', value) + self.livesocket.set_point_now("qms-value", value) if self.pullsocket is not None and usefull: - self.pullsocket.set_point_now('qms-value', value) + self.pullsocket.set_point_now("qms-value", value) if no_save is False and save_values is True: self.sqlqueue.put((query, None)) - #time.sleep(0.25) - #time.sleep(0.05) - LOGGER.error('Scan time: %f', time.time() - scan_start_time) + # time.sleep(0.25) + # time.sleep(0.05) + LOGGER.error("Scan time: %f", time.time() - scan_start_time) self.operating_mode = "Idling" - - def mass_scan(self, first_mass=0, scan_width=50, comment='Mass-scan', amp_range=-7, - update_current_timestamp=True): - """ Perform a mass scan """ + def mass_scan( + self, + first_mass=0, + scan_width=50, + comment="Mass-scan", + amp_range=-7, + update_current_timestamp=True, + ): + """Perform a mass scan""" timestamp = datetime.datetime.now() if update_current_timestamp: - start_time = (time.mktime(timestamp.timetuple()) + - timestamp.microsecond / 1000000.0) + start_time = time.mktime(timestamp.timetuple()) + timestamp.microsecond / 1000000.0 self.current_timestamp = timestamp else: - start_time = (time.mktime(self.current_timestamp.timetuple()) + - timestamp.microsecond / 1000000.0) - - self.operating_mode = 'Mass-scan' - sql_id = self.create_mysql_measurement(0, timestamp, - 'Mass Scan', comment=comment, - amp_range=amp_range, measurement_type=4) - self.message = 'ID number: ' + str(sql_id) + '. Scanning from ' - self.message += str(first_mass) + ' to ' - self.message += str(first_mass+scan_width) + 'amu' + start_time = ( + time.mktime(self.current_timestamp.timetuple()) + + timestamp.microsecond / 1000000.0 + ) + + self.operating_mode = "Mass-scan" + sql_id = self.create_mysql_measurement( + 0, + timestamp, + "Mass Scan", + comment=comment, + amp_range=amp_range, + measurement_type=4, + ) + self.message = "ID number: " + str(sql_id) + ". Scanning from " + self.message += str(first_mass) + " to " + self.message += str(first_mass + scan_width) + "amu" self.qmg.mass_scan(first_mass, scan_width, amp_range) - self.measurement_runtime = time.time()-start_time + self.measurement_runtime = time.time() - start_time self.qmg.start_measurement() - self.current_action = 'Performing scan' - time.sleep(0.1) #Allow slow qmg models time to start measurement + self.current_action = "Performing scan" + time.sleep(0.1) # Allow slow qmg models time to start measurement while self.qmg.measurement_running(): - self.measurement_runtime = time.time()-start_time + self.measurement_runtime = time.time() - start_time time.sleep(1) number_of_samples = self.qmg.waiting_samples() - samples_pr_unit = 1.0 / (scan_width/float(number_of_samples)) + samples_pr_unit = 1.0 / (scan_width / float(number_of_samples)) - query = '' - query += 'insert into xy_values_' + self.chamber - query += ' set measurement = ' + str(sql_id) + ', x = ' - self.current_action = 'Downloading samples from device' + query = "" + query += "insert into xy_values_" + self.chamber + query += " set measurement = " + str(sql_id) + ", x = " + self.current_action = "Downloading samples from device" j = 0 for i in range(0, int(number_of_samples / 100)): - self.measurement_runtime = time.time()-start_time + self.measurement_runtime = time.time() - start_time samples = self.qmg.get_multiple_samples(100) for i in range(0, len(samples)): j += 1 new_query = query + str(first_mass + j / samples_pr_unit) - if self.qmg.type == '420': - new_query += ', y = ' + str(float(samples[i]) * - (10**amp_range)) - if self.qmg.type == '422': + if self.qmg.type == "420": + new_query += ", y = " + str(float(samples[i]) * (10**amp_range)) + if self.qmg.type == "422": if amp_range == 0: - new_query += ', y = ' + samples[i] + new_query += ", y = " + samples[i] else: - new_query += ', y = ' + str((int(samples[i])/10000.0) * - (10**amp_range)) + new_query += ", y = " + str( + (int(samples[i]) / 10000.0) * (10**amp_range) + ) LOGGER.debug(new_query) self.sqlqueue.put((new_query, None)) - samples = self.qmg.get_multiple_samples(number_of_samples%100) + samples = self.qmg.get_multiple_samples(number_of_samples % 100) for i in range(0, len(samples)): j += 1 new_query = query + str(first_mass + j / samples_pr_unit) - if self.qmg.type == '420': - new_query += ', y = ' + str(float(samples[i])*(10**amp_range)) - if self.qmg.type == '422': + if self.qmg.type == "420": + new_query += ", y = " + str(float(samples[i]) * (10**amp_range)) + if self.qmg.type == "422": if amp_range == 0: - new_query += ', y = ' + samples[i] + new_query += ", y = " + samples[i] else: - new_query += ', y = ' + str((int(samples[i])/10000.0) * - (10**amp_range)) + new_query += ", y = " + str( + (int(samples[i]) / 10000.0) * (10**amp_range) + ) LOGGER.debug(new_query) self.sqlqueue.put((new_query, None)) - self.current_action = 'Emptying Queue' + self.current_action = "Emptying Queue" while not self.sqlqueue.empty(): - self.measurement_runtime = time.time()-start_time + self.measurement_runtime = time.time() - start_time time.sleep(0.1) time.sleep(0.5) diff --git a/PyExpLabSys/apps/socket_logger.py b/PyExpLabSys/apps/socket_logger.py index d048d9b4..002b15c3 100644 --- a/PyExpLabSys/apps/socket_logger.py +++ b/PyExpLabSys/apps/socket_logger.py @@ -10,32 +10,41 @@ from PyExpLabSys.common.database_saver import ContinuousDataSaver from PyExpLabSys.common.sockets import DateDataPullSocket from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) try: - sys.path.append('/home/pi/PyExpLabSys/machines/' + sys.argv[1]) + sys.path.append("/home/pi/PyExpLabSys/machines/" + sys.argv[1]) except IndexError: - print('You need to give the name of the raspberry pi as an argument') - print('This will ensure that the correct settings file will be used') + print("You need to give the name of the raspberry pi as an argument") + print("This will ensure that the correct settings file will be used") exit() -import settings # pylint: disable=F0401 +import settings # pylint: disable=F0401 + +LOGGER = get_logger( + "Socket Dataplot Logger", + level="ERROR", + file_log=True, + file_name="errors.log", + terminal_log=False, + email_on_warnings=False, +) -LOGGER = get_logger('Socket Dataplot Logger', level='ERROR', file_log=True, - file_name='errors.log', terminal_log=False, email_on_warnings=False) class SocketReaderClass(threading.Thread): - """ Read the wanted socket """ + """Read the wanted socket""" + def __init__(self, host, port, command): threading.Thread.__init__(self) self.host = host self.port = port - self.command = command + '#raw' + self.command = command + "#raw" self.command = self.command.encode() self.current_value = None self.quit = False self.ttl = 10 def value(self): - """ return current value """ + """return current value""" self.ttl = self.ttl - 1 print(self.ttl) if self.ttl > 0: @@ -45,7 +54,6 @@ def value(self): # Consider to keep program running even in case of # socket failures - def run(self): while not self.quit: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -53,39 +61,43 @@ def run(self): try: sock.sendto(self.command, (self.host, self.port)) received = sock.recv(1024) - received = received.decode('ascii') - self.current_value = float(received[received.find(',') + 1:]) + received = received.decode("ascii") + self.current_value = float(received[received.find(",") + 1 :]) self.ttl = 20 except (socket.timeout, ValueError) as e: - print(e) # LOG THIS + print(e) # LOG THIS time.sleep(1) + def main(): - """ Main function """ + """Main function""" codenames = [] for channel in settings.channels.values(): - channel['reader'] = SocketReaderClass(channel['host'], channel['port'], - channel['command']) - channel['reader'].start() - channel['logger'] = ValueLogger(channel['reader'], comp_val=channel['comp_value']) - channel['logger'].daemon = True - channel['logger'].start() - codenames.append(channel['codename']) + channel["reader"] = SocketReaderClass( + channel["host"], channel["port"], channel["command"] + ) + channel["reader"].start() + channel["logger"] = ValueLogger(channel["reader"], comp_val=channel["comp_value"]) + channel["logger"].daemon = True + channel["logger"].start() + codenames.append(channel["codename"]) try: port = settings.port_number except AttributeError: port = 9000 - pullsocket = DateDataPullSocket(settings.user + '-socket_logger', - codenames, timeouts=5, port=port) + pullsocket = DateDataPullSocket( + settings.user + "-socket_logger", codenames, timeouts=5, port=port + ) pullsocket.start() - - db_logger = ContinuousDataSaver(continuous_data_table=settings.dateplot_table, - username=settings.user, - password=settings.passwd, - measurement_codenames=codenames) + db_logger = ContinuousDataSaver( + continuous_data_table=settings.dateplot_table, + username=settings.user, + password=settings.passwd, + measurement_codenames=codenames, + ) db_logger.start() time.sleep(2) @@ -94,17 +106,18 @@ def main(): while everything_ok: time.sleep(0.25) for channel in settings.channels.values(): - if not channel['reader'].is_alive(): + if not channel["reader"].is_alive(): everything_ok = False # Report error here!!! # Consider to keep program running even in case of # socket failures - value = channel['logger'].read_value() - pullsocket.set_point_now(channel['codename'], value) - if channel['logger'].read_trigged(): + value = channel["logger"].read_value() + pullsocket.set_point_now(channel["codename"], value) + if channel["logger"].read_trigged(): print(value) - db_logger.save_point_now(channel['codename'], value) - channel['logger'].clear_trigged() + db_logger.save_point_now(channel["codename"], value) + channel["logger"].clear_trigged() + -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/apps/socket_supervisor.py b/PyExpLabSys/apps/socket_supervisor.py index ffbc7f0b..46616af2 100644 --- a/PyExpLabSys/apps/socket_supervisor.py +++ b/PyExpLabSys/apps/socket_supervisor.py @@ -5,31 +5,37 @@ import threading import socket import sys -sys.path.append('/home/pi/PyExpLabSys/machines/' + sys.argv[1]) -import settings # pylint: disable=F0401 + +sys.path.append("/home/pi/PyExpLabSys/machines/" + sys.argv[1]) +import settings # pylint: disable=F0401 from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) -LOGGER = get_logger('Socket Supervisor') +LOGGER = get_logger("Socket Supervisor") + class SocketSupervisor(threading.Thread): - """ Supervisor will check that a list of ports are still open """ + """Supervisor will check that a list of ports are still open""" + def __init__(self): threading.Thread.__init__(self) self.quit = False self.ports = settings.ports self.setup = settings.setup - self.pullsocket = DateDataPullSocket(self.setup + '-socket supervisor', - [str(port) for port in self.ports], - timeouts=len(self.ports)*[5]) + self.pullsocket = DateDataPullSocket( + self.setup + "-socket supervisor", + [str(port) for port in self.ports], + timeouts=len(self.ports) * [5], + ) self.pullsocket.start() def run(self): - """ Main loop """ + """Main loop""" while not self.quit: for port in self.ports: time.sleep(1) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - result = sock.connect_ex(('127.0.0.1', port)) + result = sock.connect_ex(("127.0.0.1", port)) if result == 0: self.pullsocket.set_point_now(str(port), True) print(port, True) @@ -37,6 +43,7 @@ def run(self): self.pullsocket.set_point_now(str(port), False) print(port, False) -if __name__ == '__main__': + +if __name__ == "__main__": SP = SocketSupervisor() SP.start() diff --git a/PyExpLabSys/apps/socket_tester.py b/PyExpLabSys/apps/socket_tester.py index e702a54d..9cc8a0d6 100644 --- a/PyExpLabSys/apps/socket_tester.py +++ b/PyExpLabSys/apps/socket_tester.py @@ -12,6 +12,7 @@ import textwrap from pprint import pprint from PyExpLabSys.common.sockets import PUSH_ERROR, PUSH_RET, UNKNOWN_COMMAND + SOCK = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) SOCK.setblocking(0) @@ -28,29 +29,29 @@ def clear(): """Clear the screen""" - os.system('cls' if os.name == 'nt' else 'clear') + os.system("cls" if os.name == "nt" else "clear") ANSI_FORMATTERS = { - 'bold': '\033[1m', - 'green': '\033[32m', - 'red': '\033[31m', - 'yellow': '\033[33m', + "bold": "\033[1m", + "green": "\033[32m", + "red": "\033[31m", + "yellow": "\033[33m", } def ansi_color(text, format_codes=None, pad=None): """A colorizer function""" if pad is not None: - text = '{{: <{}}}'.format(pad).format(text) + text = "{{: <{}}}".format(pad).format(text) if colorama is None: return text if format_codes is None: - format_str = ANSI_FORMATTERS['bold'] + format_str = ANSI_FORMATTERS["bold"] else: - format_str = '' + format_str = "" for format_ in format_codes: for format_container in [colorama.Fore, colorama.Style]: try: @@ -62,11 +63,11 @@ def ansi_color(text, format_codes=None, pad=None): message = 'Format "{}" is unknown'.format(format_) raise ValueError(message) - return '{}{}\033[0m'.format(format_str, text) + return "{}{}\033[0m".format(format_str, text) -HEADING = ansi_color('===', ['bright', 'cyan']) -IPPORT = ansi_color('IPADDRESS:PORT', ['bright']) +HEADING = ansi_color("===", ["bright", "cyan"]) +IPPORT = ansi_color("IPADDRESS:PORT", ["bright"]) WELCOME = """ {heading} {greeting} {heading} @@ -74,9 +75,8 @@ def ansi_color(text, format_codes=None, pad=None): This is a basic command-and-reply program. See {help} for more details. """.format( heading=HEADING, - greeting=ansi_color('Welcome to the socket tester program!', - ['bright', 'magenta']), - help=ansi_color('help', ['bright', 'green']), + greeting=ansi_color("Welcome to the socket tester program!", ["bright", "magenta"]), + help=ansi_color("help", ["bright", "green"]), ) HELP_BASE = """ @@ -117,13 +117,12 @@ def ansi_color(text, format_codes=None, pad=None): * data, codenames, sane_interval """.format( heading=HEADING, - greeting=ansi_color('Help', - ['bright', 'magenta']), - set_=ansi_color('set ', ['bright', 'green']) + IPPORT, - set_noauto=ansi_color('set_noauto ', ['bright', 'green']) + IPPORT, - help_=ansi_color('help', ['bright', 'green'], pad=16), - quit_=ansi_color('quit', ['bright', 'green']), - exit_=ansi_color('exit', ['bright', 'green'], pad=8), + greeting=ansi_color("Help", ["bright", "magenta"]), + set_=ansi_color("set ", ["bright", "green"]) + IPPORT, + set_noauto=ansi_color("set_noauto ", ["bright", "green"]) + IPPORT, + help_=ansi_color("help", ["bright", "green"], pad=16), + quit_=ansi_color("quit", ["bright", "green"]), + exit_=ansi_color("exit", ["bright", "green"], pad=8), ) @@ -135,7 +134,7 @@ class SocketCompleter(object): # pylint: disable=too-few-public-methods """A command completer for a socket connection""" def __init__(self, commands=None): - self.options = ['quit', 'exit', 'help', 'set', 'set_noauto', 'unset'] + self.options = ["quit", "exit", "help", "set", "set_noauto", "unset"] if commands: self.options += commands @@ -145,8 +144,7 @@ def complete(self, text, state): if state == 0: # This is the first time for this text, so build a match list. if text: - self.matches = [s for s in self.options - if s and s.startswith(text)] + self.matches = [s for s in self.options if s and s.startswith(text)] else: self.matches = self.options[:] @@ -161,7 +159,7 @@ def complete(self, text, state): def commands_and_codenames(ip_port, timeout=1): """Return information from a socket about commands and codenames""" out = [] - for command in ['name', 'commands', 'codenames']: + for command in ["name", "commands", "codenames"]: SOCK.sendto(command, ip_port) ready = select.select([SOCK], [], [], timeout) @@ -170,14 +168,14 @@ def commands_and_codenames(ip_port, timeout=1): else: raise SocketTimeout() - if recv in [UNKNOWN_COMMAND, PUSH_ERROR + '#' + UNKNOWN_COMMAND]: + if recv in [UNKNOWN_COMMAND, PUSH_ERROR + "#" + UNKNOWN_COMMAND]: out.append(None) continue - push_ret_prefix = PUSH_RET + '#' + push_ret_prefix = PUSH_RET + "#" if recv.startswith(push_ret_prefix): - recv = recv[len(push_ret_prefix):] - if command == 'name': + recv = recv[len(push_ret_prefix) :] + if command == "name": out.append(recv) else: out.append(json.loads(recv)) @@ -189,7 +187,7 @@ class SocketTester(object): """The Socket tester class""" def __init__(self): - self.prompt_base = 'Socket ({}) >>> ' + self.prompt_base = "Socket ({}) >>> " self.prompt = self.prompt_base.format(None) self.ip_port = None self.noauto = False @@ -203,49 +201,51 @@ def main(self, line=None): # pylint: disable=too-many-branches colorama.init() if readline is not None: - readline.parse_and_bind('tab: complete') + readline.parse_and_bind("tab: complete") readline.set_completer(SocketCompleter().complete) clear() print(WELCOME) while True: - if line == None or line == '': + if line == None or line == "": pass - elif line in ['quit', 'exit']: + elif line in ["quit", "exit"]: break - elif line == 'help': + elif line == "help": self.help_() - elif line.startswith('set '): - self.set_(line.split('set ')[1]) + elif line.startswith("set "): + self.set_(line.split("set ")[1]) readline.set_completer(SocketCompleter(self.commands).complete) - elif line.startswith('set_noauto '): - self.set_(line.split('set_noauto ')[1], True) - elif line == 'unset': + elif line.startswith("set_noauto "): + self.set_(line.split("set_noauto ")[1], True) + elif line == "unset": self.unset() else: try: self.forward_socket_command(line) except SocketTimeout: - message = 'Got timeout on command. Most likely there is '\ - 'no socket on {}:{}'.format(*self.ip_port) - message = '\n'.join(textwrap.wrap(message, 80)) - print(ansi_color(message, ['bright', 'red'])) + message = ( + "Got timeout on command. Most likely there is " + "no socket on {}:{}".format(*self.ip_port) + ) + message = "\n".join(textwrap.wrap(message, 80)) + print(ansi_color(message, ["bright", "red"])) self.unset() except socket.gaierror: - message = 'Found no host at: {}'.format(self.ip_port[0]) - print(ansi_color(message, ['bright', 'red'])) + message = "Found no host at: {}".format(self.ip_port[0]) + print(ansi_color(message, ["bright", "red"])) self.unset() # Get new line try: line = raw_input(self.prompt) except EOFError: - line = 'quit' + line = "quit" if colorama is not None: colorama.deinit() - print('') + print("") def help_(self): """Print out the help""" @@ -255,45 +255,50 @@ def help_(self): def set_(self, ip_port, noauto=False): """Set ip address and port""" - if not ':' in ip_port: - message = 'The argument to set should be formatted as hostname:port' - print(ansi_color(message, ['bright', 'red'])) + if not ":" in ip_port: + message = "The argument to set should be formatted as hostname:port" + print(ansi_color(message, ["bright", "red"])) self.unset() return - self.ip_port = ip_port.split(':') + self.ip_port = ip_port.split(":") try: self.ip_port[1] = int(self.ip_port[1]) except ValueError: message = 'The port argument "{}" is not cannot be converted to int' - print(ansi_color(message.format(self.ip_port[1]), ['bright', 'red'])) + print(ansi_color(message.format(self.ip_port[1]), ["bright", "red"])) self.unset() return self.ip_port = tuple(self.ip_port) self.noauto = noauto - print('Connected to: {}\n'.format( - ansi_color('{}:{}'.format(*self.ip_port), ['bright', 'blue']) - )) + print( + "Connected to: {}\n".format( + ansi_color("{}:{}".format(*self.ip_port), ["bright", "blue"]) + ) + ) if not noauto: try: - self.name, self.commands, self.codenames = \ - commands_and_codenames(self.ip_port) + self.name, self.commands, self.codenames = commands_and_codenames(self.ip_port) except SocketTimeout: - message = 'Got timeout on asking for name, commands and '\ - 'codenames. Most likely there is no socket on '\ - '{}'.format(ip_port) - message = '\n'.join(textwrap.wrap(message, 80)) - print(ansi_color(message, ['bright', 'red'])) + message = ( + "Got timeout on asking for name, commands and " + "codenames. Most likely there is no socket on " + "{}".format(ip_port) + ) + message = "\n".join(textwrap.wrap(message, 80)) + print(ansi_color(message, ["bright", "red"])) self.unset() return except socket.gaierror: - message = 'Found no host at: {}'.format(self.ip_port[0]) - print(ansi_color(message, ['bright', 'red'])) + message = "Found no host at: {}".format(self.ip_port[0]) + print(ansi_color(message, ["bright", "red"])) self.unset() return if self.name: - print('The name of the socket is: {}\n'.format( - ansi_color(self.name, ['bright', 'blue']) - )) + print( + "The name of the socket is: {}\n".format( + ansi_color(self.name, ["bright", "blue"]) + ) + ) self.print_commands_and_codenames() @@ -311,7 +316,7 @@ def unset(self): def forward_socket_command(self, line, timeout=1): """Forward a socket command""" if self.ip_port is None: - print('Unknown command: {}'.format(line)) + print("Unknown command: {}".format(line)) return SOCK.sendto(line, self.ip_port) @@ -320,7 +325,7 @@ def forward_socket_command(self, line, timeout=1): recv = SOCK.recv(65535) else: raise SocketTimeout() - if 'json' in line or line in ['status']: + if "json" in line or line in ["status"]: try: recv = json.loads(recv) except ValueError: @@ -330,24 +335,24 @@ def forward_socket_command(self, line, timeout=1): def print_commands_and_codenames(self): """Print commands and codenames""" if self.commands: - print('{0} {1} {0}\n'.format( - ansi_color('==', ['bright', 'cyan']), - ansi_color('Known commands', ['bright', 'magenta']), - )) + print( + "{0} {1} {0}\n".format( + ansi_color("==", ["bright", "cyan"]), + ansi_color("Known commands", ["bright", "magenta"]), + ) + ) for command in self.commands: - print(' * {}'.format( - ansi_color(command, ['bright', 'green']) - )) + print(" * {}".format(ansi_color(command, ["bright", "green"]))) print() if self.codenames: - print('{0} {1} {0}\n'.format( - ansi_color('==', ['bright', 'cyan']), - ansi_color('Known codenames:', ['bright', 'magenta']), - )) + print( + "{0} {1} {0}\n".format( + ansi_color("==", ["bright", "cyan"]), + ansi_color("Known codenames:", ["bright", "magenta"]), + ) + ) for codename in self.codenames: - print(' * {}'.format( - ansi_color(codename, ['bright', 'green']) - )) + print(" * {}".format(ansi_color(codename, ["bright", "green"]))) print() @@ -355,12 +360,12 @@ def main(): """Main function""" socket_tester = SocketTester() if len(sys.argv) > 1: - line = ' '.join(sys.argv[1:]) + line = " ".join(sys.argv[1:]) else: line = None socket_tester.main(line) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/apps/stepped_program_runner/stepped_program_runner.py b/PyExpLabSys/apps/stepped_program_runner/stepped_program_runner.py index 0b37f093..64d2bc25 100644 --- a/PyExpLabSys/apps/stepped_program_runner/stepped_program_runner.py +++ b/PyExpLabSys/apps/stepped_program_runner/stepped_program_runner.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*- coding:utf-8 -*- +# -*- coding:utf-8 -*- # pylint: disable=invalid-name, no-name-in-module, broad-except """A general stepped program runner @@ -32,6 +32,7 @@ import traceback import types from functools import partial + try: import Queue except ImportError: @@ -39,9 +40,15 @@ from PyQt4.QtCore import Qt, QTimer, QCoreApplication from PyQt4.QtGui import ( - QApplication, QCompleter, QLineEdit, QStringListModel, QWidget, QLabel, + QApplication, + QCompleter, + QLineEdit, + QStringListModel, + QWidget, + QLabel, ) from PyQt4 import uic + NO_FOCUS = Qt.FocusPolicy(0) # Python 2-3 hacks @@ -55,42 +62,46 @@ class SteppedProgramRunner(QWidget): # pylint: disable=too-many-instance-attrib """Main Window""" help_texts = { - 'start': 'Start the stepped program', - 'stop': 'Stop the stepped program', - 'quit': ('Quit the stepped program. \n' - 'This will ask the core program to stop if\nit "can_stop", wait for it \n' - 'to do so and quit the main GUI \n'), - 'help': 'Display this help', - 'edit': ('Edit the parameters for a single step. \n' - 'The format of the command is: \n' - ' edit step_number field=value \n' - 'e.g: \n' - ' edit 1 duration=3600'), + "start": "Start the stepped program", + "stop": "Stop the stepped program", + "quit": ( + "Quit the stepped program. \n" + 'This will ask the core program to stop if\nit "can_stop", wait for it \n' + "to do so and quit the main GUI \n" + ), + "help": "Display this help", + "edit": ( + "Edit the parameters for a single step. \n" + "The format of the command is: \n" + " edit step_number field=value \n" + "e.g: \n" + " edit 1 duration=3600" + ), } def __init__(self, core, window_title="Stepped Program Runner"): super(SteppedProgramRunner, self).__init__() self.core = core - self.last_text_type = 'none' + self.last_text_type = "none" self.window_title = window_title # Form completions and actions from core capabilities self.completions = [] self.actions = [] - for action in ('can_start', 'can_stop', 'can_edit'): + for action in ("can_start", "can_stop", "can_edit"): if action in self.core.capabilities: - self.completions.append(action.replace('can_', '')) - self.actions.append(action.replace('can_', '')) + self.completions.append(action.replace("can_", "")) + self.actions.append(action.replace("can_", "")) # We can always quit and help - self.completions += ['quit', 'help'] - self.actions += ['quit', 'help'] + self.completions += ["quit", "help"] + self.actions += ["quit", "help"] # Add extra capabilities - if hasattr(self.core, 'extra_capabilities'): + if hasattr(self.core, "extra_capabilities"): for command_name, command_spec in self.core.extra_capabilities.items(): self.actions.append(command_name) - self.completions += command_spec['completions'] - self.__class__.help_texts[command_name] = command_spec['help_text'] + self.completions += command_spec["completions"] + self.__class__.help_texts[command_name] = command_spec["help_text"] # Add completion additions if available try: @@ -98,8 +109,8 @@ def __init__(self, core, window_title="Stepped Program Runner"): except AttributeError: pass - #self.status_widgets = {} - #self.status_formatters = {} + # self.status_widgets = {} + # self.status_formatters = {} self.status_defs = {} self._init_ui() self.process_update_timer = QTimer() @@ -111,8 +122,7 @@ def __init__(self, core, window_title="Stepped Program Runner"): def _init_ui(self): """Setup the UI""" - uifile = path.join(path.dirname(path.realpath(__file__)), - 'stepped_program_runner.ui') + uifile = path.join(path.dirname(path.realpath(__file__)), "stepped_program_runner.ui") uic.loadUi(uifile, self) # Setup command line completion @@ -122,13 +132,13 @@ def _init_ui(self): # Setup status table status = self.status_table status.setRowCount(len(self.core.status_fields)) - status.setHorizontalHeaderLabels(['Name', 'Value']) + status.setHorizontalHeaderLabels(["Name", "Value"]) for row, status_field in enumerate(self.core.status_fields): status_field = dict(status_field) # Make a copy - status.setCellWidget(row, 0, QLabel(status_field.get('title', ''))) + status.setCellWidget(row, 0, QLabel(status_field.get("title", ""))) status.setCellWidget(row, 1, QLabel("N/A")) - status_field['widget'] = status.cellWidget(row, 1) - self.status_defs[status_field.pop('codename')] = status_field + status_field["widget"] = status.cellWidget(row, 1) + self.status_defs[status_field.pop("codename")] = status_field status.resizeColumnsToContents() # HACK to make the table expand to fit the contents, there MUST be a better way @@ -136,9 +146,9 @@ def _init_ui(self): status.setMinimumHeight(height) # Setup step list - self.step_table.setHorizontalHeaderLabels(['Description']) + self.step_table.setHorizontalHeaderLabels(["Description"]) - title = getattr(self.core, 'config', {}).get('program_title', self.window_title) + title = getattr(self.core, "config", {}).get("program_title", self.window_title) if title: self.setWindowTitle(title) self.show() @@ -149,14 +159,14 @@ def process_updates(self): try: update_type, update_content = self.core.message_queue.get(True, 0.001) self.last = time() - if update_type == 'steps': + if update_type == "steps": self.update_step_table(update_content) - elif update_type == 'status': + elif update_type == "status": self.update_status(update_content) - elif update_type == 'message': - self.append_text(update_content, text_type='message') - elif update_type == 'error': - self.append_text(update_content, text_type='error') + elif update_type == "message": + self.append_text(update_content, text_type="message") + elif update_type == "error": + self.append_text(update_content, text_type="error") except Queue.Empty: break self.process_update_timer.start(100) @@ -170,7 +180,7 @@ def update_step_table(self, steps): # Write out the steps for row, (active, step) in enumerate(steps): if active: - step = '' + step + '' + step = "" + step + "" widget = self.step_table.cellWidget(row, 0) if widget: widget.setText(step) @@ -183,15 +193,15 @@ def update_status(self, status): for codename, value in status.items(): status_def = self.status_defs[codename] try: - widget = status_def['widget'] + widget = status_def["widget"] except KeyError: message = 'Unknow field "{}" in status update'.format(codename) - self.append_text(message, text_type='error') + self.append_text(message, text_type="error") continue # Apply formatter, if any try: - formatter = status_def['formatter'] + formatter = status_def["formatter"] if isinstance(formatter, types.FunctionType): to_set = formatter(value) else: @@ -200,70 +210,76 @@ def update_status(self, status): to_set = UNICODE_TYPE(value) # Append unit, if any, after half sized space - if 'unit' in status_def: - to_set += '\u2006' + status_def['unit'] + if "unit" in status_def: + to_set += "\u2006" + status_def["unit"] widget.setText(to_set) except Exception: - text = ('An unknown error accoured during updating of the status table\n' - 'It had the following traceback\n' + traceback.format_exc()) - self.append_text(text, text_type='error') + text = ( + "An unknown error accoured during updating of the status table\n" + "It had the following traceback\n" + traceback.format_exc() + ) + self.append_text(text, text_type="error") def process_command(self, command): """Process a command""" command = UNICODE_TYPE(command).strip() - self.append_text(command, text_type='command') - splitted_command = command.split(' ') - if command == 'help': + self.append_text(command, text_type="command") + splitted_command = command.split(" ") + if command == "help": self.help_text() - elif command == 'quit': + elif command == "quit": self.process_quit(first_call=True) elif splitted_command[0] in self.completions: - args = ' '.join(splitted_command[1:]) + args = " ".join(splitted_command[1:]) try: self.core.command(splitted_command[0], args) except Exception: - text = ('An error occoured during the execution of the command\n' - 'It had the following traceback\n' + traceback.format_exc()) - self.append_text(text, text_type='error') + text = ( + "An error occoured during the execution of the command\n" + "It had the following traceback\n" + traceback.format_exc() + ) + self.append_text(text, text_type="error") else: - self.append_text('Unknown command: ' + command, start='\n') + self.append_text("Unknown command: " + command, start="\n") - def append_text(self, text, start='\n\n', text_type=None): + def append_text(self, text, start="\n\n", text_type=None): """Append text to the text_display""" # Always append text immediately after command - if self.last_text_type == 'command' and text_type != 'command': - start = '\n' + if self.last_text_type == "command" and text_type != "command": + start = "\n" # Format depending on text_type - if text_type == 'error': + if text_type == "error": text = 'Error: {}'.format(text) - elif text_type == 'message': + elif text_type == "message": # Append several message from core right after each other - if self.last_text_type == 'message': - start = '\n' + if self.last_text_type == "message": + start = "\n" text = '{} says: {}'.format( self.core.__class__.__name__, text ) - elif text_type == 'command': - text = '{} $ {}'.format(strftime('%H:%M:%S'), text) + elif text_type == "command": + text = "{} $ {}".format(strftime("%H:%M:%S"), text) # Set last text_type and display self.last_text_type = text_type - self.text_display.append('
' + start + text + '
') + self.text_display.append("
" + start + text + "
") def help_text(self): """Form and display help""" - help_ = ('The stepped program runner is a command driver GUI front for a ' - 'stepped program.\n\n' - 'For this program the following commands have been configured:\n') + help_ = ( + "The stepped program runner is a command driver GUI front for a " + "stepped program.\n\n" + "For this program the following commands have been configured:\n" + ) for action in self.actions: - lines = self.help_texts[action].split('\n') - help_ += '{: <16}{}\n'.format(action, lines[0]) + lines = self.help_texts[action].split("\n") + help_ += "{: <16}{}\n".format(action, lines[0]) for line in lines[1:]: - help_ += '{: <16}{}\n'.format('', line) - help_ = help_.strip('\n') - self.append_text(help_, start='\n') + help_ += "{: <16}{}\n".format("", line) + help_ = help_.strip("\n") + self.append_text(help_, start="\n") def process_quit(self, first_call=False, quit_now=False): """Process the quit command""" @@ -272,17 +288,17 @@ def process_quit(self, first_call=False, quit_now=False): return if first_call: text = "Quitting." - if self.core.is_alive() and 'can_stop' in self.core.capabilities: - text += ' Asking stepped program to stop and wait for it to do so.' + if self.core.is_alive() and "can_stop" in self.core.capabilities: + text += " Asking stepped program to stop and wait for it to do so." self.append_text(text) - self.core.command('stop', '') + self.core.command("stop", "") else: self.append_text(text) if self.core.is_alive(): self.quit_timer.start(100) else: - self.append_text('Bye!') + self.append_text("Bye!") self.quit_timer.timeout.disconnect() self.quit_timer.timeout.connect(partial(self.process_quit, quit_now=True)) self.quit_timer.start(500) @@ -293,7 +309,6 @@ def closeEvent(self, event): self.process_quit(first_call=True) - class LineEdit(QLineEdit): """Cursom QLineEdit with tab completion""" @@ -303,7 +318,7 @@ def __init__(self, parent=None): self.setCompleter(self.completer) self.model = QStringListModel() self.completer.setModel(self.model) - #get_data(model) + # get_data(model) self.completions = None self.parent = parent @@ -312,19 +327,19 @@ def keyPressEvent(self, event): text = self.text() if event.key() == Qt.Key_Tab: current_text = self.text() - if current_text != '': + if current_text != "": for item in self.completions: if item.startswith(current_text): self.setText(item) break event.accept() elif event.key() == Qt.Key_Return: - if text != '': + if text != "": self.window().process_command(text) - self.setText('') + self.setText("") event.accept() else: - QLineEdit.keyPressEvent(self, event) + QLineEdit.keyPressEvent(self, event) def set_completions(self, completions): """Set completions""" @@ -332,9 +347,7 @@ def set_completions(self, completions): self.model.setStringList(completions) - if __name__ == "__main__": - app = QApplication(sys.argv) main_window = SteppedProgramRunner() sys.exit(app.exec_()) diff --git a/PyExpLabSys/apps/terminal_plotter/terminal_plot.py b/PyExpLabSys/apps/terminal_plotter/terminal_plot.py index e78e1970..ed195b2b 100644 --- a/PyExpLabSys/apps/terminal_plotter/terminal_plot.py +++ b/PyExpLabSys/apps/terminal_plotter/terminal_plot.py @@ -24,13 +24,15 @@ else: points = 50 -HOSTNAME = 'rasppi98' -SOCKETNAME = 'omicron_pvci_pull' -CODENAME = 'omicron_prep_pressure' +HOSTNAME = "rasppi98" +SOCKETNAME = "omicron_pvci_pull" +CODENAME = "omicron_prep_pressure" LOGSCALE = False + class DataClient(threading.Thread): """Maintain a numpy queue of newest `size` data points""" + def __init__(self, hostname, socketname, codename, size=100): """Initialize""" threading.Thread.__init__(self) @@ -80,6 +82,7 @@ def get_values(self): status = False return self.values[index], status + # Setup communication with data socket client = DataClient(HOSTNAME, SOCKETNAME, CODENAME, size=100) client.start() @@ -99,21 +102,23 @@ def get_values(self): try: # Create the Curses Ascii Plotter ap = CursesAsciiPlot( - win, title="Logging: {}/{}/{}".format(HOSTNAME, SOCKETNAME, CODENAME), xlabel="Time [s]", + win, + title="Logging: {}/{}/{}".format(HOSTNAME, SOCKETNAME, CODENAME), + xlabel="Time [s]", logscale=LOGSCALE, ) # Plot the sine to time since start and 10 sec a head while True: - #stdscr.clear() # Only neccessary if characters on a line isn't completely overwritten + # stdscr.clear() # Only neccessary if characters on a line isn't completely overwritten t0 = time.time() - t_start # Write the time right now to the main window - stdscr.addstr(1, 3, 'T0: {:.2f} '.format(t0)) + stdscr.addstr(1, 3, "T0: {:.2f} ".format(t0)) stdscr.refresh() values, new = client.get_values() if new: x = values[:, 0] y = values[:, 1] - stdscr.addstr(2, 3, 'T: {:.2f}s Last value: {} '.format(x[-1], y[-1])) + stdscr.addstr(2, 3, "T: {:.2f}s Last value: {} ".format(x[-1], y[-1])) stdscr.refresh() ap.plot(x, y, legend="Data") time.sleep(0.2) diff --git a/PyExpLabSys/apps/tof/fix_mass_axis.py b/PyExpLabSys/apps/tof/fix_mass_axis.py index fa86d784..b86632b3 100644 --- a/PyExpLabSys/apps/tof/fix_mass_axis.py +++ b/PyExpLabSys/apps/tof/fix_mass_axis.py @@ -10,65 +10,75 @@ from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) -#PEAK_FIT_WIDTH = 5 +# PEAK_FIT_WIDTH = 5 def fit_peak(time, mass, data, fit_values): - """ Fit a gaussian peak """ + """Fit a gaussian peak""" values = {} center = np.where(data[:, 0] > fit_values[mass])[0][0] - values['x'] = data[center - 75:center + 75, 0] - values['y'] = data[center - 75:center + 75, 1] - center = np.where(values['y'] == max(values['y']))[0][0] - fitfunc = lambda p, x: p[0]*math.e**(-1*((x-fit_values[mass]-p[2])**2)/p[1]) - errfunc = lambda p, x, y: fitfunc(p, x) - y # Distance to the target function - p0 = [max(values['y']), 0.00001, 0] # Initial guess for the parameters + values["x"] = data[center - 75 : center + 75, 0] + values["y"] = data[center - 75 : center + 75, 1] + center = np.where(values["y"] == max(values["y"]))[0][0] + fitfunc = lambda p, x: p[0] * math.e ** (-1 * ((x - fit_values[mass] - p[2]) ** 2) / p[1]) + errfunc = lambda p, x, y: fitfunc(p, x) - y # Distance to the target function + p0 = [max(values["y"]), 0.00001, 0] # Initial guess for the parameters try: - p1, success = optimize.leastsq(errfunc, p0[:], args=(values['x'], values['y']), - maxfev=10000) - except: # Fit failed + p1, success = optimize.leastsq( + errfunc, p0[:], args=(values["x"], values["y"]), maxfev=10000 + ) + except: # Fit failed p1 = p0 success = 0 # Only use the values if fit succeeded and peak has decent height - usefull = (p1[0] > 15) and (p1[1] < 1e-3) and (success==1) + usefull = (p1[0] > 15) and (p1[1] < 1e-3) and (success == 1) if usefull: print(p1) fig = plt.figure() ax = fig.add_subplot(1, 1, 1) - ax.plot(values['x'], values['y'], 'k-') - ax.plot(values['x'], fitfunc(p1, values['x']), 'r-') - ax.plot(values['x'], fitfunc(p0, values['x']), 'c-') - #ax.axvline(values['x'][center-PEAK_FIT_WIDTH]) - #ax.axvline(values['x'][center+PEAK_FIT_WIDTH]) + ax.plot(values["x"], values["y"], "k-") + ax.plot(values["x"], fitfunc(p1, values["x"]), "r-") + ax.plot(values["x"], fitfunc(p0, values["x"]), "c-") + # ax.axvline(values['x'][center-PEAK_FIT_WIDTH]) + # ax.axvline(values['x'][center+PEAK_FIT_WIDTH]) plt.show() return usefull, p1 + def x_axis_fit_func(p, time): - #mass = p[1]* pow(time-p[0], p[2]) - mass = p[1]* pow(time-p[0], 2) + # mass = p[1]* pow(time-p[0], p[2]) + mass = p[1] * pow(time - p[0], 2) return mass + def fit_x_axis(fit_values): - """ Fit a quadratic dependence for the mass versus time relation """ - errfunc = lambda p, x, y: x_axis_fit_func(p, x) - y # Distance to the target function - fit_params = [0.1, 0.1, 2] # Initial guess for the parameters - fitted_params, success = optimize.leastsq(errfunc, fit_params[:], - args=(list(fit_values.values()), list(fit_values.keys())), - maxfev=10000) + """Fit a quadratic dependence for the mass versus time relation""" + errfunc = lambda p, x, y: x_axis_fit_func(p, x) - y # Distance to the target function + fit_params = [0.1, 0.1, 2] # Initial guess for the parameters + fitted_params, success = optimize.leastsq( + errfunc, + fit_params[:], + args=(list(fit_values.values()), list(fit_values.keys())), + maxfev=10000, + ) return fitted_params + def main(): - """Main function """ + """Main function""" spectrum_number = sys.argv[1] print(spectrum_number) - database = mysql.connector.connect(host="servcinf-sql.fysik.dtu.dk", user="tof", - passwd="tof", db="cinfdata") + database = mysql.connector.connect( + host="servcinf-sql.fysik.dtu.dk", user="tof", passwd="tof", db="cinfdata" + ) cursor = database.cursor() - cursor.execute("SELECT x * 1000000,y FROM xy_values_tof where measurement = " + - str(spectrum_number)) + cursor.execute( + "SELECT x * 1000000,y FROM xy_values_tof where measurement = " + str(spectrum_number) + ) data = np.array(cursor.fetchall()) fit_values = {} @@ -109,28 +119,38 @@ def main(): """ fig = plt.figure() axis = fig.add_subplot(2, 1, 1) - axis.plot(list(fit_values.values()), list(fit_values.keys()) - - x_axis_fit_func(p1_x_axis, list(fit_values.values())), 'bo') + axis.plot( + list(fit_values.values()), + list(fit_values.keys()) - x_axis_fit_func(p1_x_axis, list(fit_values.values())), + "bo", + ) axis = fig.add_subplot(2, 1, 2) x_fit = np.arange(0, 45, 0.01) - axis.plot(list(fit_values.values()), list(fit_values.keys()), 'bo') - axis.plot(x_fit, x_axis_fit_func(p1_x_axis, x_fit), 'k-') + axis.plot(list(fit_values.values()), list(fit_values.keys()), "bo") + axis.plot(x_fit, x_axis_fit_func(p1_x_axis, x_fit), "k-") plt.show() fig = plt.figure() axis = fig.add_subplot(1, 1, 1) - axis.plot(x_axis_fit_func(p1_x_axis, data[:, 0]), data[:, 1], 'k-') + axis.plot(x_axis_fit_func(p1_x_axis, data[:, 0]), data[:, 1], "k-") axis.set_xlim(1, 300) print(p1_x_axis) plt.show() - query = ('update measurements_tof set time=time, tof_p1_0=' + str(p1_x_axis[0]) + - ', tof_p1_1=' + str(p1_x_axis[1]) + ', tof_p1_2=' + str(p1_x_axis[2]) + - ' where id = ' + str(spectrum_number)) - if sys.argv[2] == 'yes': + query = ( + "update measurements_tof set time=time, tof_p1_0=" + + str(p1_x_axis[0]) + + ", tof_p1_1=" + + str(p1_x_axis[1]) + + ", tof_p1_2=" + + str(p1_x_axis[2]) + + " where id = " + + str(spectrum_number) + ) + if sys.argv[2] == "yes": print(query) cursor.execute(query) - -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/PyExpLabSys/apps/tof/helper_scripts/lm_test.py b/PyExpLabSys/apps/tof/helper_scripts/lm_test.py index 10ed4315..30ff441b 100644 --- a/PyExpLabSys/apps/tof/helper_scripts/lm_test.py +++ b/PyExpLabSys/apps/tof/helper_scripts/lm_test.py @@ -1,49 +1,52 @@ import pickle import numpy as np import math -#from numpy import sqrt, pi, exp, linspace, loadtxt -from lmfit import Model + +# from numpy import sqrt, pi, exp, linspace, loadtxt +from lmfit import Model import matplotlib.pyplot as plt -Data = pickle.load(open('4160.p', 'rb'), encoding='latin1') +Data = pickle.load(open("4160.p", "rb"), encoding="latin1") times = [5.53] times = [11.455, 11.468] -center = np.where(Data[:,0] > np.mean(times))[0][0] +center = np.where(Data[:, 0] > np.mean(times))[0][0] -Start = center - 125 #Display range +Start = center - 125 # Display range End = center + 125 -x = Data[Start:End,0] -y = Data[Start:End,1] +x = Data[Start:End, 0] +y = Data[Start:End, 1] + def gaussian(x, amp, cen, wid): return amp * math.e ** (-1 * ((x - cen) ** 2) / wid) + def double_gaussian(x, amp, cen, wid, amp2, cen2): peak1 = gaussian(x, amp, cen, wid) peak2 = gaussian(x, amp2, cen2, wid) return peak1 + peak2 + if len(times) == 1: gmod = Model(gaussian) result = gmod.fit(y, x=x, amp=max(y), cen=times[0], wid=0.000001) if len(times) == 2: - center1 = np.where(Data[:,0] > times[0])[0][0] - max1 = max(Data[center1-10:center1+10, 1]) + center1 = np.where(Data[:, 0] > times[0])[0][0] + max1 = max(Data[center1 - 10 : center1 + 10, 1]) + + center2 = np.where(Data[:, 0] > times[1])[0][0] + max2 = max(Data[center2 - 10 : center2 + 10, 1]) - center2 = np.where(Data[:,0] > times[1])[0][0] - max2 = max(Data[center2-10:center2+10, 1]) - gmod = Model(double_gaussian) - result = gmod.fit(y, x=x, amp=max1, cen=times[0], - amp2=max2, cen2=times[1], wid=0.000002) + result = gmod.fit(y, x=x, amp=max1, cen=times[0], amp2=max2, cen2=times[1], wid=0.000002) -print(result.params['amp'].value) +print(result.params["amp"].value) print(result.fit_report()) print(result.success) -plt.plot(x, y, 'bo') -plt.plot(x, result.init_fit, 'k--') -plt.plot(x, result.best_fit, 'r-') +plt.plot(x, y, "bo") +plt.plot(x, result.init_fit, "k--") +plt.plot(x, result.best_fit, "r-") plt.show() diff --git a/PyExpLabSys/apps/tof/spectrum_plotter.py b/PyExpLabSys/apps/tof/spectrum_plotter.py index 1c6710e9..05b8bdc0 100644 --- a/PyExpLabSys/apps/tof/spectrum_plotter.py +++ b/PyExpLabSys/apps/tof/spectrum_plotter.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt import numpy as np import scipy + try: import pymysql except ImportError: @@ -14,180 +15,245 @@ from matplotlib.backends.backend_pdf import PdfPages PEAK_FIT_WIDTH = 25 -DATEPLOT_TABLE = 'dateplots_mgw' -#DATEPLOT_TABLE = 'dateplots_hall' -#DATEPLOT_TYPE = 166 # Hall -#DATEPLOT_TYPE = 273 # Bubbler -DATEPLOT_TYPE = 140 # TC in containment volume -#DATEPLOT_TYPE = 61 # Buffer -#DATEPLOT_TYPE = 141 # RTD -#DATEPLOT_TYPE = 217 #Containment volume -#DATEPLOT_TYPE = 270 #Capillary -#DATEPLOT_TYPE = 271 #DBT bubbler valve - -MEASUREMENT_TABLE = 'measurements_tof' -XY_VALUES_TABLE = 'xy_values_tof' -NORMALISATION_FIELD = 'tof_iterations' +DATEPLOT_TABLE = "dateplots_mgw" +# DATEPLOT_TABLE = 'dateplots_hall' +# DATEPLOT_TYPE = 166 # Hall +# DATEPLOT_TYPE = 273 # Bubbler +DATEPLOT_TYPE = 140 # TC in containment volume +# DATEPLOT_TYPE = 61 # Buffer +# DATEPLOT_TYPE = 141 # RTD +# DATEPLOT_TYPE = 217 #Containment volume +# DATEPLOT_TYPE = 270 #Capillary +# DATEPLOT_TYPE = 271 #DBT bubbler valve + +MEASUREMENT_TABLE = "measurements_tof" +XY_VALUES_TABLE = "xy_values_tof" +NORMALISATION_FIELD = "tof_iterations" + def gaussian(x, amp, cen, wid): - """ Gaussian function for fitting """ + """Gaussian function for fitting""" return amp * math.e ** (-1 * ((x - cen) ** 2) / wid) - + + def double_gaussian(x, amp, cen, wid, amp2, cen2, wid2): - """ Double Gaussian function for fitting """ + """Double Gaussian function for fitting""" peak1 = gaussian(x, amp, cen, wid) peak2 = gaussian(x, amp2, cen2, wid2) return peak1 + peak2 + def fit_peak(flight_times, data, axis=None): - """ Fit a peak using lmfit """ + """Fit a peak using lmfit""" index = {} - #Find index for 'center' time - index['center'] = np.where(data[:, 0] > np.mean(flight_times))[0][0] - index['start'] = index['center'] - 100 #Display range - index['end'] = index['center'] + 100 + # Find index for 'center' time + index["center"] = np.where(data[:, 0] > np.mean(flight_times))[0][0] + index["start"] = index["center"] - 100 # Display range + index["end"] = index["center"] + 100 values = {} - values['x'] = data[index['start']:index['end'], 0] - values['y'] = data[index['start']:index['end'], 1] - center = np.where(values['y'] == max(values['y']))[0][0] - - background = np.mean(values['y'][center-3*PEAK_FIT_WIDTH:center-2*PEAK_FIT_WIDTH]) - print('Background: ' + str(background)) - #TODO: Background should be fitted, lmfit can do this - - fit_width = PEAK_FIT_WIDTH + PEAK_FIT_WIDTH * (len(flight_times)-1) * 0.45 - #Fitting range - values['x_fit'] = values['x'][center-fit_width:center+fit_width] - values['y_fit'] = values['y'][center-fit_width:center+fit_width] - background - if len(values['y_fit']) == 0: - values['x_fit'] = values['x'] - values['y_fit'] = values['y'] - background + values["x"] = data[index["start"] : index["end"], 0] + values["y"] = data[index["start"] : index["end"], 1] + center = np.where(values["y"] == max(values["y"]))[0][0] + + background = np.mean( + values["y"][center - 3 * PEAK_FIT_WIDTH : center - 2 * PEAK_FIT_WIDTH] + ) + print("Background: " + str(background)) + # TODO: Background should be fitted, lmfit can do this + + fit_width = PEAK_FIT_WIDTH + PEAK_FIT_WIDTH * (len(flight_times) - 1) * 0.45 + # Fitting range + values["x_fit"] = values["x"][center - fit_width : center + fit_width] + values["y_fit"] = values["y"][center - fit_width : center + fit_width] - background + if len(values["y_fit"]) == 0: + values["x_fit"] = values["x"] + values["y_fit"] = values["y"] - background if len(flight_times) == 1: gmod = Model(gaussian) - parms = gmod.make_params(amp=max(values['y_fit']), cen=flight_times[0], wid=0.0000025) - parms['amp'].min = 0 - parms['amp'].max = 2 * max(values['y_fit']) - parms['wid'].min = 0.0000015 - parms['wid'].max = 0.0000195 - result = gmod.fit(values['y_fit'], x=values['x_fit'], amp=parms['amp'], cen=parms['cen'], wid=parms['wid']) - #result = gmod.fit(values['y_fit'], x=values['x_fit'], amp=max(values['y_fit']), + parms = gmod.make_params(amp=max(values["y_fit"]), cen=flight_times[0], wid=0.0000025) + parms["amp"].min = 0 + parms["amp"].max = 2 * max(values["y_fit"]) + parms["wid"].min = 0.0000015 + parms["wid"].max = 0.0000195 + result = gmod.fit( + values["y_fit"], + x=values["x_fit"], + amp=parms["amp"], + cen=parms["cen"], + wid=parms["wid"], + ) + # result = gmod.fit(values['y_fit'], x=values['x_fit'], amp=max(values['y_fit']), # cen=flight_times[0], wid=0.0000025) - fit_results = [(result.params['amp'].value, result.params['wid'].value, - result.params['cen'].value)] + fit_results = [ + ( + result.params["amp"].value, + result.params["wid"].value, + result.params["cen"].value, + ) + ] if len(flight_times) == 2: center1 = np.where(data[:, 0] > flight_times[0])[0][0] - max1 = max(data[center1-10:center1+10, 1]) + max1 = max(data[center1 - 10 : center1 + 10, 1]) center2 = np.where(data[:, 0] > flight_times[1])[0][0] - max2 = max(data[center2-10:center2+10, 1]) + max2 = max(data[center2 - 10 : center2 + 10, 1]) gmod = Model(double_gaussian) - result = gmod.fit(values['y'], x=values['x'], amp=max1, cen=flight_times[0], - wid=0.0000025, amp2=max2, cen2=flight_times[1], wid2=0.0000025) - fit_results = [(result.params['amp'].value, result.params['wid'].value, - result.params['cen'].value), - (result.params['amp'].value, result.params['wid'].value, - result.params['cen'].value)] + result = gmod.fit( + values["y"], + x=values["x"], + amp=max1, + cen=flight_times[0], + wid=0.0000025, + amp2=max2, + cen2=flight_times[1], + wid2=0.0000025, + ) + fit_results = [ + ( + result.params["amp"].value, + result.params["wid"].value, + result.params["cen"].value, + ), + ( + result.params["amp"].value, + result.params["wid"].value, + result.params["cen"].value, + ), + ] usefull = result.success - + if axis is not None: - axis.plot(values['x'], values['y'], 'k-') + axis.plot(values["x"], values["y"], "k-") if len(flight_times) == 1: - #ax.plot(X_values, result.init_fit+background, 'k--') - axis.plot(values['x'], gaussian(values['x'], result.params['amp'].value, - result.params['cen'].value, - result.params['wid'].value) + background, 'r-') + # ax.plot(X_values, result.init_fit+background, 'k--') + axis.plot( + values["x"], + gaussian( + values["x"], + result.params["amp"].value, + result.params["cen"].value, + result.params["wid"].value, + ) + + background, + "r-", + ) if len(flight_times) == 2: - #ax.plot(X_values, result.init_fit, 'k--') - axis.plot(values['x'], double_gaussian(values['x'], result.params['amp'].value, - result.params['cen'].value, - result.params['wid'].value, - result.params['amp2'].value, - result.params['cen2'].value, - result.params['wid2'].value)+background, 'r-') + # ax.plot(X_values, result.init_fit, 'k--') + axis.plot( + values["x"], + double_gaussian( + values["x"], + result.params["amp"].value, + result.params["cen"].value, + result.params["wid"].value, + result.params["amp2"].value, + result.params["cen2"].value, + result.params["wid2"].value, + ) + + background, + "r-", + ) try: - axis.axvline(values['x'][center-fit_width]) - axis.axvline(values['x'][center+fit_width]) + axis.axvline(values["x"][center - fit_width]) + axis.axvline(values["x"][center + fit_width]) except: pass - #axis.annotate(str(time), xy=(.05, .85), xycoords='axes fraction', fontsize=8) - axis.annotate("Usefull: " + str(usefull), xy=(.05, .7), - xycoords='axes fraction', fontsize=8) - #plt.show() + # axis.annotate(str(time), xy=(.05, .85), xycoords='axes fraction', fontsize=8) + axis.annotate( + "Usefull: " + str(usefull), + xy=(0.05, 0.7), + xycoords="axes fraction", + fontsize=8, + ) + # plt.show() return usefull, fit_results def get_data(spectrum_number, cursor): - """ Load data from spectrum number """ + """Load data from spectrum number""" try: - data = pickle.load(open(str(spectrum_number) + '.p', 'rb')) + data = pickle.load(open(str(spectrum_number) + ".p", "rb")) except (IOError, EOFError): - query = 'SELECT x*1000000, y FROM ' + XY_VALUES_TABLE - query += ' where measurement = ' + str(spectrum_number) + query = "SELECT x*1000000, y FROM " + XY_VALUES_TABLE + query += " where measurement = " + str(spectrum_number) cursor.execute(query) data = np.array(cursor.fetchall()) - pickle.dump(data, open(str(spectrum_number) + '.p', 'wb')) + pickle.dump(data, open(str(spectrum_number) + ".p", "wb")) try: - query = 'select time, unix_timestamp(time), ' + NORMALISATION_FIELD + ' from ' + query = "select time, unix_timestamp(time), " + NORMALISATION_FIELD + " from " query += MEASUREMENT_TABLE + ' where id = "' + str(spectrum_number) + '"' - except NameError: # No normalisation - query = 'select time, unix_timestamp(time), 1 from ' + MEASUREMENT_TABLE + except NameError: # No normalisation + query = "select time, unix_timestamp(time), 1 from " + MEASUREMENT_TABLE query += ' where id = "' + str(spectrum_number) + '"' cursor.execute(query) spectrum_info = cursor.fetchone() return data, spectrum_info + def find_dateplot_info(spectrum_info, cursor): - """ Find dateplot info for the spectrum """ - query = 'SELECT unix_timestamp(time), value FROM ' + DATEPLOT_TABLE - query += ' where type = ' + str(DATEPLOT_TYPE) + ' and time < "' + """Find dateplot info for the spectrum""" + query = "SELECT unix_timestamp(time), value FROM " + DATEPLOT_TABLE + query += " where type = " + str(DATEPLOT_TYPE) + ' and time < "' query += str(spectrum_info[0]) + '" order by time desc limit 1' cursor.execute(query) before_value = cursor.fetchone() time_before = spectrum_info[1] - before_value[0] assert time_before > 0 - before = {'value': before_value, 'time': time_before} + before = {"value": before_value, "time": time_before} - query = 'SELECT unix_timestamp(time), value FROM ' + DATEPLOT_TABLE - query += ' where type = ' + str(DATEPLOT_TYPE) + ' and time > "' + query = "SELECT unix_timestamp(time), value FROM " + DATEPLOT_TABLE + query += " where type = " + str(DATEPLOT_TYPE) + ' and time > "' query += str(spectrum_info[0]) + '" order by time limit 1' cursor.execute(query) after_value = cursor.fetchone() time_after = after_value[0] - spectrum_info[1] assert time_after > 0 - after = {'value': after_value, 'time': time_after} - calculated_temp = (before['value'][1] * before['time'] + - after['value'][1] * after['time']) / (after['time'] + before['time']) + after = {"value": after_value, "time": time_after} + calculated_temp = ( + before["value"][1] * before["time"] + after["value"][1] * after["time"] + ) / (after["time"] + before["time"]) return calculated_temp + def main(fit_info, spectrum_numbers, exclude_numbers): - """ Main function """ + """Main function""" try: - conn = pymysql.connect(host="servcinf-sql.fysik.dtu.dk", user="cinf_reader", - passwd="cinf_reader", db="cinfdata") + conn = pymysql.connect( + host="servcinf-sql.fysik.dtu.dk", + user="cinf_reader", + passwd="cinf_reader", + db="cinfdata", + ) cursor = conn.cursor() except pymysql.OperationalError: - conn = pymysql.connect(host="localhost", user="cinf_reader", passwd="cinf_reader", db="cinfdata", port=999) + conn = pymysql.connect( + host="localhost", + user="cinf_reader", + passwd="cinf_reader", + db="cinfdata", + port=999, + ) cursor = conn.cursor() - - cursor = conn.cursor() - + + cursor = conn.cursor() + dateplot_values = [] timestamps = [] for x_value in fit_info: - fit_info[x_value]['peak_area'] = {} - fit_info[x_value]['errors'] = {} - for name in fit_info[x_value]['names']: - fit_info[x_value]['peak_area'][name] = [] - fit_info[x_value]['errors'][name] = [] + fit_info[x_value]["peak_area"] = {} + fit_info[x_value]["errors"] = {} + for name in fit_info[x_value]["names"]: + fit_info[x_value]["peak_area"][name] = [] + fit_info[x_value]["errors"][name] = [] - pdf_file = PdfPages('multipage.pdf') + pdf_file = PdfPages("multipage.pdf") for spectrum_number in spectrum_numbers: - if spectrum_number in exclude_numbers: #NEW - print('SKIPPING', spectrum_number) + if spectrum_number in exclude_numbers: # NEW + print("SKIPPING", spectrum_number) continue print(spectrum_number) data, spectrum_info = get_data(spectrum_number, cursor) @@ -200,30 +266,41 @@ def main(fit_info, spectrum_numbers, exclude_numbers): j = j + 1 axis = pdffig.add_subplot(4, 4, j) if j == 1: - axis.text(0, 1.2, 'Spectrum id: ' + str(spectrum_number), - fontsize=12, transform=axis.transAxes) - axis.text(0, 1.1, 'Sweeps: {0:.2e}'.format(spectrum_info[2]), - fontsize=12, transform=axis.transAxes) - usefull, results = fit_peak(fit_info[x]['flighttime'], data, axis) - - for i in range(0, len(fit_info[x]['names'])): - name = fit_info[x]['names'][i] + axis.text( + 0, + 1.2, + "Spectrum id: " + str(spectrum_number), + fontsize=12, + transform=axis.transAxes, + ) + axis.text( + 0, + 1.1, + "Sweeps: {0:.2e}".format(spectrum_info[2]), + fontsize=12, + transform=axis.transAxes, + ) + usefull, results = fit_peak(fit_info[x]["flighttime"], data, axis) + + for i in range(0, len(fit_info[x]["names"])): + name = fit_info[x]["names"][i] try: - area = math.sqrt(math.pi)*results[i][0] * math.sqrt(results[i][1]) + area = math.sqrt(math.pi) * results[i][0] * math.sqrt(results[i][1]) except ValueError: area = 0 if usefull: print(name) - fit_info[x]['peak_area'][name].append(area * 2500 / spectrum_info[2]) - fit_info[x]['errors'][name].append(math.sqrt(area * 2500) / - spectrum_info[2]) + fit_info[x]["peak_area"][name].append(area * 2500 / spectrum_info[2]) + fit_info[x]["errors"][name].append( + math.sqrt(area * 2500) / spectrum_info[2] + ) else: - fit_info[x]['peak_area'][name].append(None) - fit_info[x]['errors'][name].append(None) + fit_info[x]["peak_area"][name].append(None) + fit_info[x]["errors"][name].append(None) print(usefull) timestamps.append(spectrum_info[1]) - plt.savefig(pdf_file, format='pdf') + plt.savefig(pdf_file, format="pdf") plt.close() pdf_file.close() @@ -233,135 +310,142 @@ def main(fit_info, spectrum_numbers, exclude_numbers): axis = fig.add_subplot(1, 1, 1) export_data = {} - export_data['timestamp'] = timestamps + export_data["timestamp"] = timestamps for x in fit_info: - for i in range(0, len(fit_info[x]['names'])): ## Itereate over number of peaks - name = fit_info[x]['names'][i] - export_data[name] = fit_info[x]['peak_area'][name] + for i in range(0, len(fit_info[x]["names"])): ## Itereate over number of peaks + name = fit_info[x]["names"][i] + export_data[name] = fit_info[x]["peak_area"][name] try: - axis.errorbar(timestamps, fit_info[x]['peak_area'][name], linestyle='-', - marker='o', label=fit_info[x]['names'][i], - yerr=fit_info[x]['errors'][name]) - except TypeError: # Cannot plot errorbars on plots with missing points - axis.plot(timestamps, fit_info[x]['peak_area'][name], - linestyle='-', marker='o', label=str(x)) - export_string = '' + axis.errorbar( + timestamps, + fit_info[x]["peak_area"][name], + linestyle="-", + marker="o", + label=fit_info[x]["names"][i], + yerr=fit_info[x]["errors"][name], + ) + except TypeError: # Cannot plot errorbars on plots with missing points + axis.plot( + timestamps, + fit_info[x]["peak_area"][name], + linestyle="-", + marker="o", + label=str(x), + ) + export_string = "" for name in export_data.keys(): - export_string += name + ' ' - export_string += '\n' + export_string += name + " " + export_string += "\n" for j in range(0, len(export_data[name])): for name in export_data.keys(): - export_string += str(export_data[name][j]) + ' ' - export_string += '\n' + export_string += str(export_data[name][j]) + " " + export_string += "\n" print(export_string) - export_file = open('export.txt', 'w') + export_file = open("export.txt", "w") export_file.write(export_string) export_file.close() axis2 = axis.twinx() - axis2.plot(timestamps, dateplot_values, 'k-', label='test') - axis.tick_params(direction='in', length=2, width=1, colors='k',labelsize=28, axis='both', pad=2) - axis2.tick_params(direction='in', length=2, width=1, colors='k',labelsize=28, axis='both', pad=2) - axis.set_ylabel('Integraged peak area', fontsize=28) - axis2.set_ylabel('Temperature', fontsize=25) - axis.set_xlabel('Time / s', fontsize=25) - axis.set_yscale('log') - - axis.legend(loc='upper left', fontsize=20) + axis2.plot(timestamps, dateplot_values, "k-", label="test") + axis.tick_params( + direction="in", length=2, width=1, colors="k", labelsize=28, axis="both", pad=2 + ) + axis2.tick_params( + direction="in", length=2, width=1, colors="k", labelsize=28, axis="both", pad=2 + ) + axis.set_ylabel("Integraged peak area", fontsize=28) + axis2.set_ylabel("Temperature", fontsize=25) + axis.set_xlabel("Time / s", fontsize=25) + axis.set_yscale("log") + + axis.legend(loc="upper left", fontsize=20) plt.show() - #print('----') - #print(dateplot_values) - #print('----') - #print(peak_areas) - #print('----') - + # print('----') + # print(dateplot_values) + # print('----') + # print(peak_areas) + # print('----') -if __name__ == '__main__': +if __name__ == "__main__": FIT_INFO = {} - FIT_INFO['M2'] = {} - FIT_INFO['M2']['flighttime'] = [3.8] - FIT_INFO['M2']['names'] = ['H2'] - - #FIT_INFO = {} - FIT_INFO['M4'] = {} - FIT_INFO['M4']['flighttime'] = [5.52] - FIT_INFO['M4']['names'] = ['He'] - - #FIT_INFO['M17']= {} - #FIT_INFO['M17']['flighttime'] = [11.81] - #FIT_INFO['M17']['names'] = ['NH3'] - - #FIT_INFO['M159'] = {} - #FIT_INFO['M159']['flighttime'] = [36.94] - #FIT_INFO['M159']['names'] = ['MAI'] - - #FIT_INFO['M127'] = {} - #FIT_INFO['M127']['flighttime'] = [32.95] - #FIT_INFO['M127']['names'] = ['I'] - - #FIT_INFO['M142'] = {} - #FIT_INFO['M142']['flighttime'] = [34.87] - #FIT_INFO['M142']['names'] = ['M142'] - - #FIT_INFO['M268'] = {} - #FIT_INFO['M268']['flighttime'] = [48.05] - #FIT_INFO['M268']['names'] = ['M268'] - - #FIT_INFO['M254'] = {} - #FIT_INFO['M254']['flighttime'] = [46.76] - #FIT_INFO['M254']['names'] = ['I2'] - - #FIT_INFO['M18'] = {} - #FIT_INFO['M18']['flighttime'] = [12.16] - #FIT_INFO['M18']['names'] = ['H18'] - - #FIT_INFO['M28'] = {} - #FIT_INFO['M28']['flighttime'] = [15.26] - #FIT_INFO['M28']['names'] = ['H28'] - - FIT_INFO['M34'] = {} - FIT_INFO['M34']['flighttime'] = [16.86] - FIT_INFO['M34']['names'] = ['H2S'] - - #FIT_INFO['M149'] = {} - #FIT_INFO['M149']['flighttime'] = [35.74] - #FIT_INFO['M149']['names'] = ['M149'] - - FIT_INFO['BiPhe'] = {} - FIT_INFO['BiPhe']['flighttime'] = [36.345] - FIT_INFO['BiPhe']['names'] = ['BiPhe'] - - #FIT_INFO['Background'] = {} - #FIT_INFO['Background']['flighttime'] = [36.6] - #FIT_INFO['Background']['names'] = ['Background'] - - - - FIT_INFO['DBT'] = {} - FIT_INFO['DBT']['flighttime'] = [39.75] - FIT_INFO['DBT']['names'] = ['DBT'] + FIT_INFO["M2"] = {} + FIT_INFO["M2"]["flighttime"] = [3.8] + FIT_INFO["M2"]["names"] = ["H2"] + + # FIT_INFO = {} + FIT_INFO["M4"] = {} + FIT_INFO["M4"]["flighttime"] = [5.52] + FIT_INFO["M4"]["names"] = ["He"] + + # FIT_INFO['M17']= {} + # FIT_INFO['M17']['flighttime'] = [11.81] + # FIT_INFO['M17']['names'] = ['NH3'] + + # FIT_INFO['M159'] = {} + # FIT_INFO['M159']['flighttime'] = [36.94] + # FIT_INFO['M159']['names'] = ['MAI'] + + # FIT_INFO['M127'] = {} + # FIT_INFO['M127']['flighttime'] = [32.95] + # FIT_INFO['M127']['names'] = ['I'] + + # FIT_INFO['M142'] = {} + # FIT_INFO['M142']['flighttime'] = [34.87] + # FIT_INFO['M142']['names'] = ['M142'] + + # FIT_INFO['M268'] = {} + # FIT_INFO['M268']['flighttime'] = [48.05] + # FIT_INFO['M268']['names'] = ['M268'] + + # FIT_INFO['M254'] = {} + # FIT_INFO['M254']['flighttime'] = [46.76] + # FIT_INFO['M254']['names'] = ['I2'] + + # FIT_INFO['M18'] = {} + # FIT_INFO['M18']['flighttime'] = [12.16] + # FIT_INFO['M18']['names'] = ['H18'] + + # FIT_INFO['M28'] = {} + # FIT_INFO['M28']['flighttime'] = [15.26] + # FIT_INFO['M28']['names'] = ['H28'] + + FIT_INFO["M34"] = {} + FIT_INFO["M34"]["flighttime"] = [16.86] + FIT_INFO["M34"]["names"] = ["H2S"] + + # FIT_INFO['M149'] = {} + # FIT_INFO['M149']['flighttime'] = [35.74] + # FIT_INFO['M149']['names'] = ['M149'] + + FIT_INFO["BiPhe"] = {} + FIT_INFO["BiPhe"]["flighttime"] = [36.345] + FIT_INFO["BiPhe"]["names"] = ["BiPhe"] + + # FIT_INFO['Background'] = {} + # FIT_INFO['Background']['flighttime'] = [36.6] + # FIT_INFO['Background']['names'] = ['Background'] + + FIT_INFO["DBT"] = {} + FIT_INFO["DBT"]["flighttime"] = [39.75] + FIT_INFO["DBT"]["names"] = ["DBT"] # - #FIT_INFO['M127'] = {} - #FIT_INFO['M127']['flighttime'] = [32.95, 32.98] - #FIT_INFO['M127']['names'] = ['I-low', 'I-high'] - - #FIT_INFO['M85'] = {} - #FIT_INFO['M85']['flighttime'] = [26.89, 26.91] - #FIT_INFO['M85']['names'] = ['M85-low', 'M85-high'] - - #FIT_INFO['M85-high'] = {} - #FIT_INFO['M85-high']['flighttime'] = [26.91] - #FIT_INFO['M85-high']['names'] = ['M85-high'] - - #FIT_INFO['M85-low'] = {} - #FIT_INFO['M85-low']['flighttime'] = [26.89] - #FIT_INFO['M85-low']['names'] = ['M85-low'] - - - + # FIT_INFO['M127'] = {} + # FIT_INFO['M127']['flighttime'] = [32.95, 32.98] + # FIT_INFO['M127']['names'] = ['I-low', 'I-high'] + # FIT_INFO['M85'] = {} + # FIT_INFO['M85']['flighttime'] = [26.89, 26.91] + # FIT_INFO['M85']['names'] = ['M85-low', 'M85-high'] + + # FIT_INFO['M85-high'] = {} + # FIT_INFO['M85-high']['flighttime'] = [26.91] + # FIT_INFO['M85-high']['names'] = ['M85-high'] + + # FIT_INFO['M85-low'] = {} + # FIT_INFO['M85-low']['flighttime'] = [26.89] + # FIT_INFO['M85-low']['names'] = ['M85-low'] """ FIT_INFO['21.97'] = {} @@ -372,15 +456,46 @@ def main(fit_info, spectrum_numbers, exclude_numbers): FIT_INFO['24.57']['flighttime'] = [24.57] FIT_INFO['24.57']['names'] = ['Oil II'] """ - #FIT_INFO['11.82'] = {} - #FIT_INFO['11.82']['flighttime'] = [11.81, 11.831] - #FIT_INFO['11.82']['names'] = ['11.82-low', '11.82-high'] - #Todo: Also include fit-information such as exact peak position - #SPECTRUM_NUMBERS = range(28263, 29762) + # FIT_INFO['11.82'] = {} + # FIT_INFO['11.82']['flighttime'] = [11.81, 11.831] + # FIT_INFO['11.82']['names'] = ['11.82-low', '11.82-high'] + # Todo: Also include fit-information such as exact peak position + # SPECTRUM_NUMBERS = range(28263, 29762) SPECTRUM_NUMBERS = range(28263, 28475) - #SPECTRUM_NUMBERS = range(9532, 10440) - EXCLUDE_NUMBERS = set([9454, 9458, 9464, 9465, 9478, 9487, 9505, 9905, 9940, 9955, 9971, 9991, 9994, 10007, 10078, 10106, 10139, 10142, 10188, 10203, 10213, 10216, 10324, 10442, 10444, 10463, 10470, 14118]) - # 10188 10212 - + # SPECTRUM_NUMBERS = range(9532, 10440) + EXCLUDE_NUMBERS = set( + [ + 9454, + 9458, + 9464, + 9465, + 9478, + 9487, + 9505, + 9905, + 9940, + 9955, + 9971, + 9991, + 9994, + 10007, + 10078, + 10106, + 10139, + 10142, + 10188, + 10203, + 10213, + 10216, + 10324, + 10442, + 10444, + 10463, + 10470, + 14118, + ] + ) + # 10188 10212 + main(FIT_INFO, SPECTRUM_NUMBERS, EXCLUDE_NUMBERS) diff --git a/PyExpLabSys/apps/transparency_measurement.py b/PyExpLabSys/apps/transparency_measurement.py index 2a47cb4e..d643fecb 100644 --- a/PyExpLabSys/apps/transparency_measurement.py +++ b/PyExpLabSys/apps/transparency_measurement.py @@ -25,15 +25,15 @@ resolution=(HEIGHT, WIDTH), # framerate=Fraction(1, 6), framerate=5, - sensor_mode=2 + sensor_mode=2, ) time.sleep(1) -camera.exposure_mode = 'off' +camera.exposure_mode = "off" camera.shutter_speed = 10000 camera.iso = 400 -camera.awb_mode = 'off' +camera.awb_mode = "off" camera.awb_gains = (1.0, 1.0) # First image @@ -49,14 +49,14 @@ output = np.empty((WIDTH, HEIGHT, 3), dtype=np.uint8) # camera.capture(output, 'rgb', use_video_port=True) for i in range(0, 5): - camera.capture(output, 'rgb', use_video_port=False) + camera.capture(output, "rgb", use_video_port=False) intensity = np.mean(output) / 256 intensities.append(intensity) print(intensities) mean_int = np.mean(intensities) rel_int = target / mean_int shutter = int(shutter * rel_int) - print('Rel Int: {:.3f}, Shutter: {}'.format(rel_int, shutter)) + print("Rel Int: {:.3f}, Shutter: {}".format(rel_int, shutter)) exit() @@ -64,33 +64,33 @@ t = time.time() output = np.empty((WIDTH, HEIGHT, 3), dtype=np.uint8) # camera.capture(output, 'rgb', use_video_port=True) -camera.capture(output, 'rgb', use_video_port=False) +camera.capture(output, "rgb", use_video_port=False) exposure_time = time.time() - t -print('Exposure time: {:.3f}'.format(exposure_time * 1000)) +print("Exposure time: {:.3f}".format(exposure_time * 1000)) data = Image.fromarray(output) -data.save('first.png') +data.save("first.png") first_intensity = np.mean(output) / 256 camera.shutter_speed = 10000 -print('Change sample') +print("Change sample") # time.sleep(2) # Second image output = np.empty((WIDTH, HEIGHT, 3), dtype=np.uint8) t = time.time() -# camera.capture(output, 'rgb', use_video_port=True) -camera.capture(output, 'rgb', use_video_port=False) +# camera.capture(output, 'rgb', use_video_port=True) +camera.capture(output, "rgb", use_video_port=False) exposure_time = time.time() - t -print('Exposure time: {:.3f}ms'.format(exposure_time * 1000)) +print("Exposure time: {:.3f}ms".format(exposure_time * 1000)) data = Image.fromarray(output) -data.save('second.png') +data.save("second.png") second_intensity = np.mean(output) / 256 -print('First: {}, Second: {}. Ratio: {}'.format( - first_intensity, - second_intensity, - first_intensity / second_intensity -)) +print( + "First: {}, Second: {}. Ratio: {}".format( + first_intensity, second_intensity, first_intensity / second_intensity + ) +) diff --git a/PyExpLabSys/apps/turbo_logger.py b/PyExpLabSys/apps/turbo_logger.py index 65edd170..b1cebd64 100644 --- a/PyExpLabSys/apps/turbo_logger.py +++ b/PyExpLabSys/apps/turbo_logger.py @@ -9,15 +9,19 @@ from PyExpLabSys.common.sockets import LiveSocket from PyExpLabSys.common.utilities import get_logger from PyExpLabSys.common.supported_versions import python2_and_3 -sys.path.append('/home/pi/PyExpLabSys/machines/' + sys.argv[1]) -import settings # pylint: disable=wrong-import-position, import-error + +sys.path.append("/home/pi/PyExpLabSys/machines/" + sys.argv[1]) +import settings # pylint: disable=wrong-import-position, import-error + python2_and_3(__file__) -LOGGER = get_logger('Turbo Pump', level='info', file_log=True, - file_name='turbo.log', terminal_log=False) +LOGGER = get_logger( + "Turbo Pump", level="info", file_log=True, file_name="turbo.log", terminal_log=False +) + def main(): - """ Main loop """ + """Main loop""" mainpump = tp.TurboDriver(adress=1, port=settings.port) mainpump.start() @@ -29,40 +33,48 @@ def main(): reader.daemon = True reader.start() - time.sleep(10) # Allow reader to make meaningfull moving avarage + time.sleep(10) # Allow reader to make meaningfull moving avarage - codenames = [settings.table_prefix + '_turbo_speed', - settings.table_prefix + '_turbo_power', - settings.table_prefix + '_turbo_current', - settings.table_prefix + '_turbo_temp_motor', - settings.table_prefix + '_turbo_temp_bottom', - settings.table_prefix + '_turbo_temp_bearings', - settings.table_prefix + '_turbo_temp_electronics'] + codenames = [ + settings.table_prefix + "_turbo_speed", + settings.table_prefix + "_turbo_power", + settings.table_prefix + "_turbo_current", + settings.table_prefix + "_turbo_temp_motor", + settings.table_prefix + "_turbo_temp_bottom", + settings.table_prefix + "_turbo_temp_bearings", + settings.table_prefix + "_turbo_temp_electronics", + ] loggers = {} - loggers[codenames[0]] = tp.TurboLogger(reader, 'rotation_speed', maximumtime=600) - loggers[codenames[1]] = tp.TurboLogger(reader, 'drive_power', maximumtime=600) - loggers[codenames[2]] = tp.TurboLogger(reader, 'drive_current', maximumtime=600) - loggers[codenames[3]] = tp.TurboLogger(reader, 'temp_motor', maximumtime=600) - loggers[codenames[4]] = tp.TurboLogger(reader, 'temp_bottom', maximumtime=600) - loggers[codenames[5]] = tp.TurboLogger(reader, 'temp_bearings', maximumtime=600) - loggers[codenames[6]] = tp.TurboLogger(reader, 'temp_electronics', maximumtime=600) + loggers[codenames[0]] = tp.TurboLogger(reader, "rotation_speed", maximumtime=600) + loggers[codenames[1]] = tp.TurboLogger(reader, "drive_power", maximumtime=600) + loggers[codenames[2]] = tp.TurboLogger(reader, "drive_current", maximumtime=600) + loggers[codenames[3]] = tp.TurboLogger(reader, "temp_motor", maximumtime=600) + loggers[codenames[4]] = tp.TurboLogger(reader, "temp_bottom", maximumtime=600) + loggers[codenames[5]] = tp.TurboLogger(reader, "temp_bearings", maximumtime=600) + loggers[codenames[6]] = tp.TurboLogger(reader, "temp_electronics", maximumtime=600) for name in codenames: loggers[name].daemon = True loggers[name].start() - livesocket = LiveSocket(settings.name + '-turboreader', codenames) + livesocket = LiveSocket(settings.name + "-turboreader", codenames) livesocket.start() - socket = DateDataPullSocket(settings.name + '-turboreader', codenames, - timeouts=[1.0] * len(codenames), port=9000) + socket = DateDataPullSocket( + settings.name + "-turboreader", + codenames, + timeouts=[1.0] * len(codenames), + port=9000, + ) socket.start() - db_logger = ContinuousDataSaver(continuous_data_table=settings.table, - username=settings.user, - password=settings.passwd, - measurement_codenames=codenames) + db_logger = ContinuousDataSaver( + continuous_data_table=settings.table, + username=settings.user, + password=settings.passwd, + measurement_codenames=codenames, + ) db_logger.start() time.sleep(5) @@ -70,13 +82,14 @@ def main(): time.sleep(0.1) for name in codenames: value = loggers[name].read_value() - socket.set_point_now(name, value) # Notice, this is the averaged value - livesocket.set_point_now(name, value) # Notice, this is the averaged value + socket.set_point_now(name, value) # Notice, this is the averaged value + livesocket.set_point_now(name, value) # Notice, this is the averaged value if loggers[name].trigged: db_logger.save_point_now(name, value) loggers[name].trigged = False -if __name__ == '__main__': + +if __name__ == "__main__": try: main() except Exception as e: diff --git a/PyExpLabSys/apps/wind_speed_logger.py b/PyExpLabSys/apps/wind_speed_logger.py index 13d9bdb1..3af8577f 100644 --- a/PyExpLabSys/apps/wind_speed_logger.py +++ b/PyExpLabSys/apps/wind_speed_logger.py @@ -25,18 +25,21 @@ from PyExpLabSys.common.supported_versions import python2_and_3 from ABE_ADCPi import ADCPi from ABE_helpers import ABEHelpers + python2_and_3(__file__) try: - sys.path.append('/home/pi/PyExpLabSys/machines/' + sys.argv[1]) + sys.path.append("/home/pi/PyExpLabSys/machines/" + sys.argv[1]) except IndexError: - print('You need to give the name of the raspberry pi as an argument') - print('This will ensure that the correct settings file will be used') + print("You need to give the name of the raspberry pi as an argument") + print("This will ensure that the correct settings file will be used") exit() -import credentials # pylint: disable=import-error, wrong-import-position -import settings # pylint: disable=import-error, wrong-import-position +import credentials # pylint: disable=import-error, wrong-import-position +import settings # pylint: disable=import-error, wrong-import-position + class WindReader(threading.Thread): - """ Read Cooling water pressure """ + """Read Cooling water pressure""" + def __init__(self, adc): threading.Thread.__init__(self) self.adc = adc @@ -44,11 +47,11 @@ def __init__(self, adc): self.windspeeds = {} for channel in settings.channels.keys(): self.windspeeds[channel] = -1 - self.windspeeds[channel+'_raw'] = -1 + self.windspeeds[channel + "_raw"] = -1 self.quit = False def value(self, channel): - """ Return the value of the reader """ + """Return the value of the reader""" value = self.windspeeds[channel] return value @@ -60,18 +63,20 @@ def run(self): for _ in range(0, self.average_length): voltage += self.adc.read_voltage(int(channel)) raw = voltage / self.average_length - self.windspeeds[channel + '_raw'] = raw + self.windspeeds[channel + "_raw"] = raw coeff_a = 0.591 coeff_b = 1.78 coeff_c = 2.25 try: - self.windspeeds[channel] = (-1 * (1/coeff_c) * - math.log(1- (raw - coeff_b) / coeff_a)) + self.windspeeds[channel] = ( + -1 * (1 / coeff_c) * math.log(1 - (raw - coeff_b) / coeff_a) + ) except ValueError: pass + def main(): - """ Main function """ + """Main function""" i2c_helper = ABEHelpers() bus = i2c_helper.get_smbus() adc_instance = ADCPi(bus, 0x68, 0x69, 18) @@ -82,28 +87,32 @@ def main(): loggers = {} for channel, codename in settings.channels.items(): - loggers[codename + '_raw'] = ValueLogger(windreader, comp_val=1.05, - channel=channel + '_raw', maximumtime=30) - loggers[codename + '_raw'].start() - loggers[codename] = ValueLogger(windreader, comp_val=1.005, - channel=channel, maximumtime=30) + loggers[codename + "_raw"] = ValueLogger( + windreader, comp_val=1.05, channel=channel + "_raw", maximumtime=30 + ) + loggers[codename + "_raw"].start() + loggers[codename] = ValueLogger( + windreader, comp_val=1.005, channel=channel, maximumtime=30 + ) loggers[codename].start() codenames = [] for name in settings.channels.values(): codenames.append(name) - codenames.append(name + '_raw') + codenames.append(name + "_raw") - socket = DateDataPullSocket('Fumehood Wind Speed', codenames, timeouts=2.0) + socket = DateDataPullSocket("Fumehood Wind Speed", codenames, timeouts=2.0) socket.start() - live_socket = LiveSocket('Fumehood Wind Speed', codenames) + live_socket = LiveSocket("Fumehood Wind Speed", codenames) live_socket.start() - db_logger = ContinuousDataSaver(continuous_data_table=settings.dateplot_table, - username=credentials.user, - password=credentials.passwd, - measurement_codenames=codenames) + db_logger = ContinuousDataSaver( + continuous_data_table=settings.dateplot_table, + username=credentials.user, + password=credentials.passwd, + measurement_codenames=codenames, + ) db_logger.start() time.sleep(10) @@ -115,9 +124,10 @@ def main(): socket.set_point_now(name, value) live_socket.set_point_now(name, value) if loggers[name].read_trigged(): - print(name + ': ' + str(value)) + print(name + ": " + str(value)) db_logger.save_point_now(name, value) loggers[name].clear_trigged() -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/PyExpLabSys/auxiliary/pid.py b/PyExpLabSys/auxiliary/pid.py index 6ac4af0e..4130ea26 100644 --- a/PyExpLabSys/auxiliary/pid.py +++ b/PyExpLabSys/auxiliary/pid.py @@ -1,15 +1,18 @@ """ PID calculator """ import logging from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class PID(object): - """ PID calculator """ + """PID calculator""" + def __init__(self, pid_p=0.15, pid_i=0.0025, pid_d=0, p_max=54, p_min=0): - LOGGER.debug('Starting PID') + LOGGER.debug("Starting PID") self.setpoint = -9999 self.pid_p = pid_p self.pid_i = pid_i @@ -20,29 +23,29 @@ def __init__(self, pid_p=0.15, pid_i=0.0025, pid_d=0, p_max=54, p_min=0): self.int_err = 0 def integration_contribution(self): - """ Return the contribution from the i-term """ + """Return the contribution from the i-term""" return self.pid_i * self.int_err def proportional_contribution(self): - """ Return the contribution from the p-term """ + """Return the contribution from the p-term""" return self.pid_p * self.error def integrated_error(self): - """ Return the currently integrated error """ + """Return the currently integrated error""" return self.int_err def reset_int_error(self): - """ Reset the integration error """ + """Reset the integration error""" self.int_err = 0 def update_setpoint(self, setpoint): - """ Update the setpoint """ - LOGGER.debug('Setting setpoint to: ' + str(setpoint)) + """Update the setpoint""" + LOGGER.debug("Setting setpoint to: " + str(setpoint)) self.setpoint = setpoint return setpoint def wanted_power(self, value): - """ Return the best estimate for wanted power """ + """Return the best estimate for wanted power""" self.error = self.setpoint - value power = self.pid_p * self.error power = power + self.pid_i * self.int_err diff --git a/PyExpLabSys/auxiliary/rtd_calculator.py b/PyExpLabSys/auxiliary/rtd_calculator.py index 66d018f7..b783558d 100644 --- a/PyExpLabSys/auxiliary/rtd_calculator.py +++ b/PyExpLabSys/auxiliary/rtd_calculator.py @@ -1,55 +1,60 @@ """ Calculates temperatures for an RTD """ from __future__ import print_function from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) class RtdCalculator(object): - """ Calculates temperatures for an RTD + """Calculates temperatures for an RTD The class calculates the RTD temperature at 0C and then solves this equation for T, with T0 being 0: R = R0(1 + A(T-T0) + B(T-T0)^2) => R = R0(1 + AT + BT**2) => R0BT**2 + R0AT + R0-R = 0 """ - def __init__(self, calib_temperature, calib_resistance, material='Pt'): - if material == 'Pt': + + def __init__(self, calib_temperature, calib_resistance, material="Pt"): + if material == "Pt": self.coeff_a = 3.9803e-3 self.coeff_b = -5.775e-7 - if material == 'Mo': + if material == "Mo": self.coeff_a = 4.579e-3 - self.coeff_b = 0 # Not correct - if material == 'W': + self.coeff_b = 0 # Not correct + if material == "W": self.coeff_a = 4.403e-3 - self.coeff_b = 0 # Not correct + self.coeff_b = 0 # Not correct self.calib_temperature = calib_temperature self.calib_resistance = calib_resistance - self.resistance_at_0 = calib_resistance / (1 + self.coeff_a * calib_temperature + - self.coeff_b * calib_temperature**2) - + self.resistance_at_0 = calib_resistance / ( + 1 + self.coeff_a * calib_temperature + self.coeff_b * calib_temperature**2 + ) def find_r(self, temperature): - """ Find the resistance for a given temperature """ - resistance = self.resistance_at_0 * (1 + self.coeff_a * temperature + - self.coeff_b * temperature**2) + """Find the resistance for a given temperature""" + resistance = self.resistance_at_0 * ( + 1 + self.coeff_a * temperature + self.coeff_b * temperature**2 + ) return resistance def find_temperature(self, resistance): - """ Find the current temperature for given resistance """ - A = self.coeff_a # pylint: disable=invalid-name - B = self.coeff_b # pylint: disable=invalid-name - R = resistance # pylint: disable=invalid-name - R0 = self.resistance_at_0 # pylint: disable=invalid-name + """Find the current temperature for given resistance""" + A = self.coeff_a # pylint: disable=invalid-name + B = self.coeff_b # pylint: disable=invalid-name + R = resistance # pylint: disable=invalid-name + R0 = self.resistance_at_0 # pylint: disable=invalid-name if B > 0: - temperature = ((-1 * R0 * A + ((R0 * A)**2 - 4 * R0 * B * (R0 - R))**(0.5)) / - (2 * R0 * B)) + temperature = (-1 * R0 * A + ((R0 * A) ** 2 - 4 * R0 * B * (R0 - R)) ** (0.5)) / ( + 2 * R0 * B + ) else: - temperature = (R/R0 - 1)/A + temperature = (R / R0 - 1) / A return temperature -if __name__ == '__main__': + +if __name__ == "__main__": RTD = RtdCalculator(150, 157) print(RTD.resistance_at_0) print(RTD.find_temperature(100)) diff --git a/PyExpLabSys/auxiliary/tc_calculator.py b/PyExpLabSys/auxiliary/tc_calculator.py index 778ada59..017d6027 100644 --- a/PyExpLabSys/auxiliary/tc_calculator.py +++ b/PyExpLabSys/auxiliary/tc_calculator.py @@ -1,18 +1,18 @@ -def TC_Calculator(V, No=1, tctype='K'): - if tctype == 'K': +def TC_Calculator(V, No=1, tctype="K"): + if tctype == "K": coef = [] - coef.append( 0.0) - coef.append( 0.0 * 1.0) - coef.append( 2.5928 * 10) - coef.append( -7.602961 * 10**-1) - coef.append( 4.637791 * 10**-2) - coef.append( -2.165394 * 10**-2) - coef.append( 6.048144 * 10**-5) - coef.append( -7.293422 * 10**-7) + coef.append(0.0) + coef.append(0.0 * 1.0) + coef.append(2.5928 * 10) + coef.append(-7.602961 * 10**-1) + coef.append(4.637791 * 10**-2) + coef.append(-2.165394 * 10**-2) + coef.append(6.048144 * 10**-5) + coef.append(-7.293422 * 10**-7) else: return None - V = V/No + V = V / No T = 0.0 for i, c in enumerate(coef): T += c * V**i diff --git a/PyExpLabSys/combos.py b/PyExpLabSys/combos.py index 5712268c..fca89042 100644 --- a/PyExpLabSys/combos.py +++ b/PyExpLabSys/combos.py @@ -16,9 +16,18 @@ class LiveContinuousLogger(object): """ - def __init__(self, name, codenames, continuous_data_table, username, password, - time_criteria=None, absolute_criteria=None, relative_criteria=None, - live_server_kwargs=None): + def __init__( + self, + name, + codenames, + continuous_data_table, + username, + password, + time_criteria=None, + absolute_criteria=None, + relative_criteria=None, + live_server_kwargs=None, + ): """Initialize local data Args: @@ -47,11 +56,11 @@ def __init__(self, name, codenames, continuous_data_table, username, password, # Initialize local variables self.name = name self.codenames = set(codenames) # It should be a set anyway - + # Update and check criteria: - self.time_criteria = self._init_criteria(time_criteria, 'time') - self.absolute_criteria = self._init_criteria(absolute_criteria, 'absolute') - self.relative_criteria = self._init_criteria(relative_criteria, 'relative') + self.time_criteria = self._init_criteria(time_criteria, "time") + self.absolute_criteria = self._init_criteria(absolute_criteria, "absolute") + self.relative_criteria = self._init_criteria(relative_criteria, "relative") # Init last times and values (non-existing key will trigger save) self.last_times = {} @@ -62,7 +71,9 @@ def __init__(self, name, codenames, continuous_data_table, username, password, live_server_kwargs = {} self.live_socket = LiveSocket(name, codenames, **live_server_kwargs) self.continuous_data_saver = ContinuousDataSaver( - continuous_data_table, username, password, + continuous_data_table, + username, + password, measurement_codenames=codenames, ) @@ -83,7 +94,7 @@ def _init_criteria(self, criteria, criteria_name): # If not a dictionary, assumed to be int or float if not isinstance(criteria, dict): if not criteria > 0: - error_message = 'The criterium for {} is expected to be positive. {} is not.' + error_message = "The criterium for {} is expected to be positive. {} is not." raise ValueError(error_message.format(criteria_name, criteria)) # Return dict with the passed in single value for all keys return {codename: criteria for codename in self.codenames} @@ -91,9 +102,10 @@ def _init_criteria(self, criteria, criteria_name): # Check that criteria contains exactly one entry for each codename. Note # self.codenames is a set if not set(criteria.keys()) == self.codenames: - error_message = \ - 'There is not a {} criteria in the criteria dict {} for every '\ - 'codename: {}'.format(criteria_name, criteria, self.codenames) + error_message = ( + "There is not a {} criteria in the criteria dict {} for every " + "codename: {}".format(criteria_name, criteria, self.codenames) + ) raise ValueError(error_message) return criteria @@ -101,7 +113,7 @@ def start(self): """Start the underlying :class:`.LiveSocket` and :class:`.ContinuousDataSaver`""" self.live_socket.start() self.continuous_data_saver.start() - + def stop(self): """Stop the underlying :class:`.LiveSocket` and :class:`.ContinuousDataSaver`""" self.live_socket.stop() @@ -205,7 +217,6 @@ def _test_logging_criteria(self, codename, point): return False - def _update_last_information(self, codename, point): """Update the information about last logged point""" current_time, current_value = point diff --git a/PyExpLabSys/common/analog_flow_control.py b/PyExpLabSys/common/analog_flow_control.py index 75ac81da..e1a5a4c8 100644 --- a/PyExpLabSys/common/analog_flow_control.py +++ b/PyExpLabSys/common/analog_flow_control.py @@ -5,15 +5,18 @@ from PyExpLabSys.common.sockets import DateDataPullSocket from PyExpLabSys.common.sockets import DataPushSocket from PyExpLabSys.common.sockets import LiveSocket + try: from ABE_ADCDACPi import ADCDACPi except ImportError: # Newer versions of ABElectronics Python code import from this location from ADCDACPi import ADCDACPi + class AnalogMFC(object): - """ Driver for controling an analog MFC (or PC) with - an AB Electronics ADCDAC """ + """Driver for controling an analog MFC (or PC) with + an AB Electronics ADCDAC""" + def __init__(self, channel, fullrange, voltagespan): self.channel = channel self.fullrange = fullrange @@ -22,25 +25,26 @@ def __init__(self, channel, fullrange, voltagespan): self.daq.set_adc_refvoltage(3.3) def read_flow(self): - """ Read the flow (or pressure) value """ + """Read the flow (or pressure) value""" value = 0 - for _ in range(0, 10): # Average to minimiza noise + for _ in range(0, 10): # Average to minimiza noise value += self.daq.read_adc_voltage(1, 1) value = value / 10 - #print('Value: ' + str(value)) + # print('Value: ' + str(value)) flow = value * self.fullrange / self.voltagespan return flow def set_flow(self, flow): - """ Set the wanted flow (or pressure) """ - voltage = flow * self.voltagespan / self.fullrange - print('Voltage: ' + str(voltage)) + """Set the wanted flow (or pressure)""" + voltage = flow * self.voltagespan / self.fullrange + print("Voltage: " + str(voltage)) self.daq.set_dac_voltage(1, voltage) return voltage class FlowControl(threading.Thread): - """ Keep updated values of the current flow or pressure """ + """Keep updated values of the current flow or pressure""" + def __init__(self, mfcs, name): threading.Thread.__init__(self) self.daemon = True @@ -51,19 +55,20 @@ def __init__(self, mfcs, name): for device in devices: self.values[device] = None - self.pullsocket = DateDataPullSocket(name + '_analog_control', devices, - timeouts=[3.0] * len(devices)) + self.pullsocket = DateDataPullSocket( + name + "_analog_control", devices, timeouts=[3.0] * len(devices) + ) self.pullsocket.start() - self.pushsocket = DataPushSocket(name + '_analog_pc_control', action='enqueue') + self.pushsocket = DataPushSocket(name + "_analog_pc_control", action="enqueue") self.pushsocket.start() - self.livesocket = LiveSocket(name + '_analog_mfc_control', devices) + self.livesocket = LiveSocket(name + "_analog_mfc_control", devices) self.livesocket.start() self.running = True def value(self, device): - """ Return the current value of a device """ + """Return the current value of a device""" return self.values[device] def run(self): @@ -71,7 +76,7 @@ def run(self): time.sleep(0.1) qsize = self.pushsocket.queue.qsize() while qsize > 0: - print('queue-size: ' + str(qsize)) + print("queue-size: " + str(qsize)) element = self.pushsocket.queue.get() mfc = list(element.keys())[0] self.mfcs[mfc].set_flow(element[mfc]) diff --git a/PyExpLabSys/common/chiller_reader.py b/PyExpLabSys/common/chiller_reader.py index 95e04800..4745301c 100644 --- a/PyExpLabSys/common/chiller_reader.py +++ b/PyExpLabSys/common/chiller_reader.py @@ -4,49 +4,52 @@ import time import PyExpLabSys.drivers.polyscience_4100 as polyscience_4100 from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class ChillerReader(threading.Thread): - """ Reader class that will monitor a polyscience chiller """ + """Reader class that will monitor a polyscience chiller""" + def __init__(self, serial_port): threading.Thread.__init__(self) self.chiller = polyscience_4100.Polyscience4100(serial_port) self.status = {} - self.status['temp'] = -9999 - self.status['flow'] = -9999 - self.status['temp_amb'] = -9999 - self.status['pressure'] = -9999 - self.status['setpoint'] = -9999 - self.status['running'] = 'Off' + self.status["temp"] = -9999 + self.status["flow"] = -9999 + self.status["temp_amb"] = -9999 + self.status["pressure"] = -9999 + self.status["setpoint"] = -9999 + self.status["running"] = "Off" self.ttl = 100 self.quit = False self.daemon = True def value(self, channel): - """ Return the value of the reader """ + """Return the value of the reader""" self.ttl = self.ttl - 1 if self.ttl < 0: self.quit = True if channel == 0: - return_val = self.status['temp'] + return_val = self.status["temp"] if channel == 1: - return_val = self.status['flow'] + return_val = self.status["flow"] if channel == 2: - return_val = self.status['temp_amb'] + return_val = self.status["temp_amb"] if channel == 3: - return_val = self.status['pressure'] + return_val = self.status["pressure"] if channel == 4: - return_val = self.status['setpoint'] + return_val = self.status["setpoint"] return return_val def run(self): while not self.quit: print(self.ttl) - self.status['temp'] = self.chiller.read_temperature() - self.status['flow'] = self.chiller.read_flow_rate() - self.status['temp_amb'] = self.chiller.read_ambient_temperature() - self.status['pressure'] = self.chiller.read_pressure() - self.status['setpoint'] = self.chiller.read_setpoint() - self.status['running'] = self.chiller.read_status() + self.status["temp"] = self.chiller.read_temperature() + self.status["flow"] = self.chiller.read_flow_rate() + self.status["temp_amb"] = self.chiller.read_ambient_temperature() + self.status["pressure"] = self.chiller.read_pressure() + self.status["setpoint"] = self.chiller.read_setpoint() + self.status["running"] = self.chiller.read_status() self.ttl = 100 time.sleep(1) diff --git a/PyExpLabSys/common/database_saver.py b/PyExpLabSys/common/database_saver.py index 5e17ed18..215aee92 100644 --- a/PyExpLabSys/common/database_saver.py +++ b/PyExpLabSys/common/database_saver.py @@ -14,19 +14,21 @@ import MySQLdb except ImportError: import pymysql as MySQLdb + MySQLdb.install_as_MySQLdb() from ..settings import Settings + SETTINGS = Settings() # Used for check of valid, un-escaped column names, to prevent injection -COLUMN_NAME = re.compile(r'^[0-9a-zA-Z$_]*$') +COLUMN_NAME = re.compile(r"^[0-9a-zA-Z$_]*$") # namedtuple used for custom column formatting, see MeasurementSaver.__init__ -CustomColumn = namedtuple('CustomColumn', ['value', 'format_string']) +CustomColumn = namedtuple("CustomColumn", ["value", "format_string"]) # Loging object for the DataSetSaver (DSS) shortened, because it will be # written a lot -DSS_LOG = logging.getLogger(__name__ + '.MeasurementSaver') +DSS_LOG = logging.getLogger(__name__ + ".MeasurementSaver") DSS_LOG.addHandler(logging.NullHandler()) @@ -47,8 +49,14 @@ class DataSetSaver(object): """ - def __init__(self, measurements_table, xy_values_table, username, password, - measurement_specs=None): + def __init__( + self, + measurements_table, + xy_values_table, + username, + password, + measurement_specs=None, + ): """Initialize local parameters Args: @@ -94,9 +102,12 @@ def __init__(self, measurements_table, xy_values_table, username, password, """ DSS_LOG.info( - '__init__ with measurement_table=%s, xy_values_table=%s, ' - 'username=%s, password=*****, measurement_specs: %s', - measurements_table, xy_values_table, username, measurement_specs, + "__init__ with measurement_table=%s, xy_values_table=%s, " + "username=%s, password=*****, measurement_specs: %s", + measurements_table, + xy_values_table, + username, + measurement_specs, ) # Initialize instance variables @@ -105,13 +116,13 @@ def __init__(self, measurements_table, xy_values_table, username, password, self.sql_saver = SqlSaver(username, password) # Initialize queries - query = 'INSERT INTO {} ({{}}) values ({{}})' + query = "INSERT INTO {} ({{}}) values ({{}})" self.insert_measurement_query = query.format(measurements_table) - query = 'INSERT INTO {} (measurement, x, y) values (%s, %s, %s)' + query = "INSERT INTO {} (measurement, x, y) values (%s, %s, %s)" self.insert_point_query = query.format(xy_values_table) - query = 'INSERT INTO {} (measurement, x, y) values {{}}' + query = "INSERT INTO {} (measurement, x, y) values {{}}" self.insert_batch_query = query.format(xy_values_table) - query = 'SELECT DISTINCT {{}} from {}' + query = "SELECT DISTINCT {{}} from {}" self.select_distict_query = query.format(measurements_table) # Init local database connection @@ -119,7 +130,7 @@ def __init__(self, measurements_table, xy_values_table, username, password, host=socket.gethostbyname(SETTINGS.sql_server_host), user=username, passwd=password, - db=SETTINGS.sql_database + db=SETTINGS.sql_database, ) self.cursor = self.connection.cursor() @@ -141,15 +152,14 @@ def add_measurement(self, codename, metadata): metadata (dict): The dictionary that holds the information for the measurements table. See :meth:`__init__` for details. """ - DSS_LOG.info('Add measurement codenamed: \'%s\' with metadata: %s', - codename, metadata) + DSS_LOG.info("Add measurement codenamed: '%s' with metadata: %s", codename, metadata) # Collect column names, values and format strings, a format string is a SQL # value placeholder including processing like e.g: %s or FROM_UNIXTIME(%s) column_names, values, value_format_strings = [], [], [] for column_name, value in metadata.items(): if not COLUMN_NAME.match(column_name): - msg = 'Invalid column name: \'{}\'. Only column names using a-z, ' - msg += 'A-Z, 0-9 and \'_\' and \'$\' are allowed' + msg = "Invalid column name: '{}'. Only column names using a-z, " + msg += "A-Z, 0-9 and '_' and '$' are allowed" raise ValueError(msg.format(column_name)) if isinstance(value, CustomColumn): @@ -158,23 +168,22 @@ def add_measurement(self, codename, metadata): real_value, value_format_string = value else: # Else, that value is just a value with default place holder - real_value, value_format_string = value, '%s' + real_value, value_format_string = value, "%s" - column_names.append('`' + column_name + '`') + column_names.append("`" + column_name + "`") values.append(real_value) value_format_strings.append(value_format_string) # Form the column string e.g: 'name, time, type' - column_string = ', '.join(column_names) + column_string = ", ".join(column_names) # Form the value marker string e.g: '%s, FROM_UNIXTIME(%s), %s' - value_marker_string = ', '.join(value_format_strings) - query = self.insert_measurement_query.format( - column_string, value_marker_string) + value_marker_string = ", ".join(value_format_strings) + query = self.insert_measurement_query.format(column_string, value_marker_string) # Make the insert and save the measurement_table id for use in saving data self.cursor.execute(query, values) self.measurement_ids[codename] = self.cursor.lastrowid - DSS_LOG.debug('Measurement codenamed: \'%s\' added', codename) + DSS_LOG.debug("Measurement codenamed: '%s' added", codename) def save_point(self, codename, point): """Save a point for a specific codename @@ -183,11 +192,11 @@ def save_point(self, codename, point): codename (str): The codename for the measurement to add the point to point (sequence): A sequence of x, y """ - DSS_LOG.debug('For codename \'%s\' save point: %s', codename, point) + DSS_LOG.debug("For codename '%s' save point: %s", codename, point) try: query_args = [self.measurement_ids[codename]] except KeyError: - message = 'No entry in measurements_ids for codename: \'{}\'' + message = "No entry in measurements_ids for codename: '{}'" raise ValueError(message.format(codename)) # The query expects 3 values; measurement_id, x, y @@ -211,17 +220,21 @@ def save_points_batch(self, codename, x_values, y_values, batchsize=1000): safe and that if it is changed by the user, expect problems if exceeding the lower 10000ths. """ - DSS_LOG.debug('For codename \'%s\' save %s points in batches of %s', - codename, len(x_values), batchsize) + DSS_LOG.debug( + "For codename '%s' save %s points in batches of %s", + codename, + len(x_values), + batchsize, + ) # Check lengths and get measurement_id if len(x_values) != len(y_values): - msg = 'Number of x and y values must be the same. Values are {} and {}' + msg = "Number of x and y values must be the same. Values are {} and {}" raise ValueError(msg.format(len(x_values), len(y_values))) try: measurement_id = self.measurement_ids[codename] except KeyError: - message = 'No entry in measurements_ids for codename: \'{}\'' + message = "No entry in measurements_ids for codename: '{}'" raise ValueError(message.format(codename)) # Gather values in batches of batchsize, start enumerate from 1, to make @@ -235,7 +248,7 @@ def save_points_batch(self, codename, x_values, y_values, batchsize=1000): # Save a batch (> should not be necessary) if number_of_values >= batchsize: - value_marker_string = ', '.join(['(%s, %s, %s)'] * number_of_values) + value_marker_string = ", ".join(["(%s, %s, %s)"] * number_of_values) query = self.insert_batch_query.format(value_marker_string) self.sql_saver.enqueue_query(query, values) values = [] @@ -243,7 +256,7 @@ def save_points_batch(self, codename, x_values, y_values, batchsize=1000): # Save the remaining number of points (smaller than the batchsize) if number_of_values > 0: - value_marker_string = ', '.join(['(%s, %s, %s)'] * number_of_values) + value_marker_string = ", ".join(["(%s, %s, %s)"] * number_of_values) query = self.insert_batch_query.format(value_marker_string) self.sql_saver.enqueue_query(query, values) @@ -274,11 +287,11 @@ def stop(self): And shut down the underlying :class:`.SqlSaver` instance nicely. """ - DSS_LOG.info('stop called') + DSS_LOG.info("stop called") self.cursor.close() self.connection.close() self.sql_saver.stop() - DSS_LOG.debug('stopped') + DSS_LOG.debug("stopped") def wait_for_queue_to_empty(self): """Wait for the query queue in the SqlSaver to empty @@ -289,7 +302,7 @@ def wait_for_queue_to_empty(self): self.sql_saver.wait_for_queue_to_empty() -CDS_LOG = logging.getLogger(__name__ + '.ContinuousDataSaver') +CDS_LOG = logging.getLogger(__name__ + ".ContinuousDataSaver") CDS_LOG.addHandler(logging.NullHandler()) @@ -320,8 +333,11 @@ def __init__(self, continuous_data_table, username, password, measurement_codena """ CDS_LOG.info( - '__init__ with continuous_data_table=%s, username=%s, password=*****, ' - 'measurement_codenames=%s', continuous_data_table, username, measurement_codenames, + "__init__ with continuous_data_table=%s, username=%s, password=*****, " + "measurement_codenames=%s", + continuous_data_table, + username, + measurement_codenames, ) # Initialize instance variables @@ -335,7 +351,7 @@ def __init__(self, continuous_data_table, username, password, measurement_codena host=socket.gethostbyname(SETTINGS.sql_server_host), user=username, passwd=password, - db=SETTINGS.sql_database + db=SETTINGS.sql_database, ) self.cursor = self.connection.cursor() @@ -355,13 +371,15 @@ def add_continuous_measurement(self, codename): for contionuous measurements NOT codenames that can be userdefined """ - CDS_LOG.info('Add measurements for codename \'%s\'', codename) - query = 'SELECT id FROM dateplots_descriptions WHERE codename=\'{}\'' + CDS_LOG.info("Add measurements for codename '%s'", codename) + query = "SELECT id FROM dateplots_descriptions WHERE codename='{}'" self.cursor.execute(query.format(codename)) results = self.cursor.fetchall() if len(results) != 1: - message = 'Measurement code name \'{}\' does not have exactly one entry in '\ - 'dateplots_descriptions'.format(codename) + message = ( + "Measurement code name '{}' does not have exactly one entry in " + "dateplots_descriptions".format(codename) + ) CDS_LOG.critical(message) raise ValueError(message) self.codename_translation[codename] = results[0][0] @@ -377,8 +395,9 @@ def save_point_now(self, codename, value): float: The Unixtime used """ unixtime = time.time() - CDS_LOG.debug('Adding timestamp %s to value %s for codename %s', unixtime, value, - codename) + CDS_LOG.debug( + "Adding timestamp %s to value %s for codename %s", unixtime, value, codename + ) self.save_point(codename, (unixtime, value)) return unixtime @@ -392,19 +411,19 @@ def save_point(self, codename, point): try: unixtime, value = point except ValueError: - message = '\'point\' must be a iterable with 2 values, got {}'.format(point) + message = "'point' must be a iterable with 2 values, got {}".format(point) raise ValueError(message) # Save the point - CDS_LOG.debug('Save point (%s, %s) for codename: %s', unixtime, value, codename) + CDS_LOG.debug("Save point (%s, %s) for codename: %s", unixtime, value, codename) measurement_number = self.codename_translation[codename] - query = 'INSERT INTO {} (type, time, value) VALUES (%s, FROM_UNIXTIME(%s), %s);' + query = "INSERT INTO {} (type, time, value) VALUES (%s, FROM_UNIXTIME(%s), %s);" query = query.format(self.continuous_data_table) self.sql_saver.enqueue_query(query, (measurement_number, unixtime, value)) def start(self): """Starts the underlying :class:`.SqlSaver`""" - CDS_LOG.info('start called') + CDS_LOG.info("start called") self.sql_saver.start() def stop(self): @@ -413,12 +432,12 @@ def stop(self): And shut down the underlying :class:`.SqlSaver` instance nicely. """ - CDS_LOG.info('stop called') + CDS_LOG.info("stop called") self.sql_saver.stop() - CDS_LOG.debug('stop finished') + CDS_LOG.debug("stop finished") -SQL_SAVER_LOG = logging.getLogger(__name__ + '.SqlSaver') +SQL_SAVER_LOG = logging.getLogger(__name__ + ".SqlSaver") SQL_SAVER_LOG.addHandler(logging.NullHandler()) @@ -450,10 +469,11 @@ def __init__(self, username, password, queue=None): :py:class:`Queue.Queue` object will be used. """ - SQL_SAVER_LOG.info('Init with username: %s, password: ***** and queue: %s', - username, queue) + SQL_SAVER_LOG.info( + "Init with username: %s, password: ***** and queue: %s", username, queue + ) super(SqlSaver, self).__init__() - #threading.Thread.__init__(self) + # threading.Thread.__init__(self) self.daemon = True # Initialize internal variables @@ -470,27 +490,30 @@ def __init__(self, username, password, queue=None): self.queue = queue # Initialize database connection - SQL_SAVER_LOG.debug('Open connection to MySQL database') + SQL_SAVER_LOG.debug("Open connection to MySQL database") self.connection = MySQLdb.connect( host=socket.gethostbyname(SETTINGS.sql_server_host), user=username, passwd=password, - db=SETTINGS.sql_database + db=SETTINGS.sql_database, ) self.cursor = self.connection.cursor() - SQL_SAVER_LOG.debug('Connection opened, init done') + SQL_SAVER_LOG.debug("Connection opened, init done") def stop(self): """Add stop word to queue to exit the loop when the queue is empty""" - SQL_SAVER_LOG.info('stop called. Wait for %s elements remaining in the queue to ' - 'be sent to the database', self.queue.qsize()) - self.queue.put(('STOP', None)) + SQL_SAVER_LOG.info( + "stop called. Wait for %s elements remaining in the queue to " + "be sent to the database", + self.queue.qsize(), + ) + self.queue.put(("STOP", None)) self._stop_called = True # Make sure to wait untill it is closed down to return, otherwise we are going to # tear down the environment around it while self.is_alive(): time.sleep(10**-5) - SQL_SAVER_LOG.debug('stopped') + SQL_SAVER_LOG.debug("stopped") def enqueue_query(self, query, query_args=None): """Enqueue a qeury and arguments @@ -501,13 +524,14 @@ def enqueue_query(self, query, query_args=None): to be formatted into the query. ``query`` and ``query_args`` in combination are the arguments to cursor.execute. """ - SQL_SAVER_LOG.debug('Enqueue query\n\'%.70s...\'\nwith args: %.60s...', query, - query_args) + SQL_SAVER_LOG.debug( + "Enqueue query\n'%.70s...'\nwith args: %.60s...", query, query_args + ) self.queue.put((query, query_args)) def run(self): """Execute SQL inserts from the queue until stopped""" - SQL_SAVER_LOG.info('run started') + SQL_SAVER_LOG.info("run started") while True: start = time.time() query, args = self.queue.get() @@ -516,11 +540,11 @@ def run(self): # if not the user os waiting without information and may think that the # process hangs if self._stop_called: - SQL_SAVER_LOG.info('Dequeued element, %s remaining', self.queue.qsize()) + SQL_SAVER_LOG.info("Dequeued element, %s remaining", self.queue.qsize()) else: - SQL_SAVER_LOG.debug('Dequeued element, %s remaining', self.queue.qsize()) + SQL_SAVER_LOG.debug("Dequeued element, %s remaining", self.queue.qsize()) - if query == 'STOP': # Magic key-word to stop Sql Saver + if query == "STOP": # Magic key-word to stop Sql Saver break success = False @@ -528,13 +552,14 @@ def run(self): try: self.cursor.execute(query, args=args) success = True - SQL_SAVER_LOG.debug('Executed query\n\'%.70s\'\nwith args: %.60s', query, - args) + SQL_SAVER_LOG.debug( + "Executed query\n'%.70s'\nwith args: %.60s", query, args + ) # Naming exceptions is a bit tricky here, since we import different # sql-libraries at runtime, not all exceptions are available and we # end up with a NameError instead. Catching all should be ok here. - except Exception as e: # Failed to perfom commit - msg = 'Executing a query raised an error: {}'.format(e) + except Exception as e: # Failed to perfom commit + msg = "Executing a query raised an error: {}".format(e) SQL_SAVER_LOG.error(msg) time.sleep(5) try: @@ -542,10 +567,10 @@ def run(self): host=socket.gethostbyname(SETTINGS.sql_server_host), user=self.username, passwd=self.password, - db=SETTINGS.sql_database + db=SETTINGS.sql_database, ) self.cursor = self.connection.cursor() - except Exception: # Failed to re-connect + except Exception: # Failed to re-connect pass self.connection.commit() @@ -553,7 +578,7 @@ def run(self): self.commit_time = time.time() - start self.connection.close() - SQL_SAVER_LOG.debug('run stopped') + SQL_SAVER_LOG.debug("run stopped") def wait_for_queue_to_empty(self): """Wait for the queue to empty @@ -568,43 +593,50 @@ def wait_for_queue_to_empty(self): def run_module(): """Run the module to perform elementary functional test""" import numpy - print('Test DataSetSaver.\nInit and start.') - data_set_saver = DataSetSaver('measurements_dummy', 'xy_values_dummy', 'dummy', 'dummy') + + print("Test DataSetSaver.\nInit and start.") + data_set_saver = DataSetSaver("measurements_dummy", "xy_values_dummy", "dummy", "dummy") data_set_saver.start() - print('Make 2 data sets. Save data as mass spectra') - metadata = {'type': 4, 'comment': 'Test sine1', 'sem_voltage': 47, 'mass_label': 'Sine', - 'preamp_range': -9} - data_set_saver.add_measurement('sine1', metadata) - metadata['comment'] = 'Test sine2' - data_set_saver.add_measurement('sine2', metadata) + print("Make 2 data sets. Save data as mass spectra") + metadata = { + "type": 4, + "comment": "Test sine1", + "sem_voltage": 47, + "mass_label": "Sine", + "preamp_range": -9, + } + data_set_saver.add_measurement("sine1", metadata) + metadata["comment"] = "Test sine2" + data_set_saver.add_measurement("sine2", metadata) # Make measurement x = numpy.arange(0, 3, 0.03) y = numpy.sin(x) # Save all at once - data_set_saver.save_points_batch('sine1', x, y) + data_set_saver.save_points_batch("sine1", x, y) print('Saved "sine1" as a batch') # Save point by point for xpoint, ypoint in zip(x, y): - data_set_saver.save_point('sine2', (xpoint, ypoint + 0.3)) + data_set_saver.save_point("sine2", (xpoint, ypoint + 0.3)) print('Saved "sine2" as single points') data_set_saver.stop() - print('Stop DataSetSaver\n') + print("Stop DataSetSaver\n") - print('Test ContinuousDataSaver.\nInit and start') - continuous_data_saver = ContinuousDataSaver('dateplots_dummy', 'dummy', 'dummy') + print("Test ContinuousDataSaver.\nInit and start") + continuous_data_saver = ContinuousDataSaver("dateplots_dummy", "dummy", "dummy") continuous_data_saver.start() print('Use dateplots "sine1" and "sine2"') - continuous_data_saver.add_continuous_measurement('dummy_sine_one') - continuous_data_saver.add_continuous_measurement('dummy_sine_two') - print('Save 10 points for each, 0.1 s apart (will take 1s)') + continuous_data_saver.add_continuous_measurement("dummy_sine_one") + continuous_data_saver.add_continuous_measurement("dummy_sine_two") + print("Save 10 points for each, 0.1 s apart (will take 1s)") for _ in range(10): - continuous_data_saver.save_point_now('dummy_sine_one', numpy.sin(time.time())) - continuous_data_saver.save_point_now('dummy_sine_two', - numpy.sin(time.time() + numpy.pi)) + continuous_data_saver.save_point_now("dummy_sine_one", numpy.sin(time.time())) + continuous_data_saver.save_point_now( + "dummy_sine_two", numpy.sin(time.time() + numpy.pi) + ) time.sleep(0.1) continuous_data_saver.stop() - print('Stop ContinuousDataSaver') + print("Stop ContinuousDataSaver") -if __name__ == '__main__': +if __name__ == "__main__": run_module() diff --git a/PyExpLabSys/common/decorators.py b/PyExpLabSys/common/decorators.py index 0352db38..aad108cd 100644 --- a/PyExpLabSys/common/decorators.py +++ b/PyExpLabSys/common/decorators.py @@ -11,8 +11,10 @@ def execute_on_exception(name_of_shutdown_method): object as the decorated method) to call if the decorated methods raises an exception """ + def decorator(method): """The decorator for the method""" + @functools.wraps(method) def new_method(*args, **kwargs): """Decorated method""" @@ -27,5 +29,7 @@ def new_method(*args, **kwargs): # Re-raise for good measure raise return out + return new_method + return decorator diff --git a/PyExpLabSys/common/flow_control_bronkhorst.py b/PyExpLabSys/common/flow_control_bronkhorst.py index f044eb77..a9baaca3 100644 --- a/PyExpLabSys/common/flow_control_bronkhorst.py +++ b/PyExpLabSys/common/flow_control_bronkhorst.py @@ -7,39 +7,42 @@ from PyExpLabSys.common.sockets import DataPushSocket from PyExpLabSys.common.sockets import LiveSocket from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class FlowControl(threading.Thread): - """ Keep updated values of the current flow """ + """Keep updated values of the current flow""" + def __init__(self, ranges, devices, socket_name): threading.Thread.__init__(self) self.devices = devices name = {} mfcs = {} - print('!') + print("!") for i in range(0, 8): - print('----------------') - print('Cheking port number: {}'.format(i)) + print("----------------") + print("Cheking port number: {}".format(i)) error = 0 - name[i] = '' - while (error < 3) and (name[i] == ''): + name[i] = "" + while (error < 3) and (name[i] == ""): # Pro forma-range will be update in a few lines ioerror = 0 while ioerror < 10: time.sleep(0.5) print(ioerror) try: - bronk = bronkhorst.Bronkhorst('/dev/ttyUSB' + str(i), 1) - print('MFC Found') + bronk = bronkhorst.Bronkhorst("/dev/ttyUSB" + str(i), 1) + print("MFC Found") break except: # pylint: disable=bare-except ioerror = ioerror + 1 if ioerror == 10: - print('No MFC found on this port') + print("No MFC found on this port") break - print('Error count before identification: {}'.format(ioerror)) + print("Error count before identification: {}".format(ioerror)) name[i] = bronk.read_serial() - print('MFC Name: {}'.format(name[i])) + print("MFC Name: {}".format(name[i])) name[i] = name[i].strip() error = error + 1 if name[i] in devices: @@ -47,28 +50,28 @@ def __init__(self, ranges, devices, socket_name): if ioerror < 10: print(ioerror) try: - mfcs[name[i]] = bronkhorst.Bronkhorst('/dev/ttyUSB' + str(i), - ranges[name[i]]) - mfcs[name[i]].set_control_mode() #Accept setpoint from rs232 + mfcs[name[i]] = bronkhorst.Bronkhorst( + "/dev/ttyUSB" + str(i), ranges[name[i]] + ) + mfcs[name[i]].set_control_mode() # Accept setpoint from rs232 except IOError: ioerror = ioerror + 1 if ioerror == 10: - print('Found MFC but could not set range') + print("Found MFC but could not set range") self.mfcs = mfcs - self.pullsocket = DateDataPullSocket(socket_name, devices, - timeouts=3.0, port=9000) + self.pullsocket = DateDataPullSocket(socket_name, devices, timeouts=3.0, port=9000) self.pullsocket.start() - self.pushsocket = DataPushSocket(socket_name, action='enqueue') + self.pushsocket = DataPushSocket(socket_name, action="enqueue") self.pushsocket.start() self.livesocket = LiveSocket(socket_name, devices) self.livesocket.start() self.running = True - self.reactor_pressure = float('NaN') + self.reactor_pressure = float("NaN") def value(self): - """ Helper function for the reactor logger functionality """ + """Helper function for the reactor logger functionality""" return self.reactor_pressure def run(self): @@ -86,6 +89,6 @@ def run(self): flow = self.mfcs[mfc].read_flow() self.pullsocket.set_point_now(mfc, flow) self.livesocket.set_point_now(mfc, flow) - if mfc == self.devices[0]: # First device is considered pressure controller + if mfc == self.devices[0]: # First device is considered pressure controller print("Pressure: " + str(flow)) self.reactor_pressure = flow diff --git a/PyExpLabSys/common/functions.py b/PyExpLabSys/common/functions.py index 1a11d614..059d0ac1 100644 --- a/PyExpLabSys/common/functions.py +++ b/PyExpLabSys/common/functions.py @@ -3,10 +3,10 @@ import time import datetime -def loop_class_on_exceptions(class_to_loop, - tuple_of_exceptions_to_ignore=None, - wait_between_loops=600, - **kwargs): + +def loop_class_on_exceptions( + class_to_loop, tuple_of_exceptions_to_ignore=None, wait_between_loops=600, **kwargs +): """Reinitialize and run a class on certain errors by wrapping in a try-except clause Args: @@ -21,7 +21,7 @@ class and calling its "run" method Usage: from PyExpLabSys.common.functions import loop_class_on_exceptions - + class MyClass: def __init__(self, my_message='Default'): self.msg = my_message @@ -42,31 +42,31 @@ def stop(self): wait_between_loops=5, my_message='Hello world!', ) - + """ # Check arguments - msg = '' + msg = "" if not isinstance(class_to_loop, type): msg = ( - 'First argument must be the instance of the class you want looped on', - ' errors. This class must also have a "run" method.' - ) + "First argument must be the instance of the class you want looped on", + ' errors. This class must also have a "run" method.', + ) if not tuple_of_exceptions_to_ignore is None: if not isinstance(tuple_of_exceptions_to_ignore, tuple): msg = ( - 'Second argument must be ´None´ or a tuple of exceptions, which should', - ' trigger a restart of the class.' - ) + "Second argument must be ´None´ or a tuple of exceptions, which should", + " trigger a restart of the class.", + ) else: if KeyboardInterrupt in tuple_of_exceptions_to_ignore: - msg = 'KeyboardInterrupt is reserved for breaking out of the outer loop!' + msg = "KeyboardInterrupt is reserved for breaking out of the outer loop!" if msg: raise TypeError(msg) # Start main loop if tuple_of_exceptions_to_ignore is None: - print('Starting main class without cathing any errors (no looping)') + print("Starting main class without cathing any errors (no looping)") main_class = class_to_loop(**kwargs) main_class.run() SystemExit() @@ -74,32 +74,31 @@ def stop(self): while True: try: now = datetime.datetime.now() - now = now.strftime('%Y-%m-%D %H:%M:%S') + now = now.strftime("%Y-%m-%D %H:%M:%S") print( - '{}:\n'.format(now), - 'Starting main class while catching following errors: ', - '{}.\n'.format(tuple_of_exceptions_to_ignore), - 'Use KeyboardInterrupt to break out of this loop.\n', - '_'*10 - ) + "{}:\n".format(now), + "Starting main class while catching following errors: ", + "{}.\n".format(tuple_of_exceptions_to_ignore), + "Use KeyboardInterrupt to break out of this loop.\n", + "_" * 10, + ) main_class = class_to_loop(**kwargs) main_class.run() except KeyboardInterrupt: - print('Stopping script...') + print("Stopping script...") main_class.stop() break except tuple_of_exceptions_to_ignore as error: now = datetime.datetime.now() - now = now.strftime('%Y-%m-%D %H:%M:%S') - print('\n{}\nCaught error: {}'.format(now, error)) - print('Stopping script. Restarting in {} s:'.format(wait_between_loops)) + now = now.strftime("%Y-%m-%D %H:%M:%S") + print("\n{}\nCaught error: {}".format(now, error)) + print("Stopping script. Restarting in {} s:".format(wait_between_loops)) main_class.stop() t0 = time.time() t = t0 while t - t0 < wait_between_loops: time.sleep(1) t = time.time() - print('{:>5.1f} s '.format(wait_between_loops - (t-t0)), end='\r') - print('_'*10) + print("{:>5.1f} s ".format(wait_between_loops - (t - t0)), end="\r") + print("_" * 10) continue - diff --git a/PyExpLabSys/common/loggers.py b/PyExpLabSys/common/loggers.py index 933a16f7..98de86a0 100644 --- a/PyExpLabSys/common/loggers.py +++ b/PyExpLabSys/common/loggers.py @@ -12,13 +12,16 @@ import threading import time import logging + try: import MySQLdb - SQL = 'mysqldb' + + SQL = "mysqldb" ODBC_PROGRAMMING_ERROR = Exception except ImportError: import pyodbc - SQL = 'pyodbc' + + SQL = "pyodbc" ODBC_PROGRAMMING_ERROR = pyodbc.ProgrammingError @@ -29,8 +32,11 @@ class NoneResponse: """NoneResponse""" + def __init__(self): - LOGGER.debug('NoneResponse class instantiated') + LOGGER.debug("NoneResponse class instantiated") + + #: Module variable used to indicate a none response, currently is an instance #: if NoneResponse NONE_RESPONSE = NoneResponse() @@ -38,28 +44,28 @@ def __init__(self): class InterruptableThread(threading.Thread): """Class to run a MySQL query with a time out""" + def __init__(self, cursor, query): - LOGGER.debug('InterruptableThread.__init__ start') + LOGGER.debug("InterruptableThread.__init__ start") threading.Thread.__init__(self) self.cursor = cursor self.query = query self.result = NONE_RESPONSE self.daemon = True - LOGGER.debug('InterruptableThread.__init__ end') + LOGGER.debug("InterruptableThread.__init__ end") def run(self): """Start the thread""" - LOGGER.debug('InterruptableThread.run start') + LOGGER.debug("InterruptableThread.run start") self.cursor.execute(self.query) - if SQL == 'mysqldb': + if SQL == "mysqldb": self.result = self.cursor.fetchall() else: try: self.result = self.cursor.fetchall() except ODBC_PROGRAMMING_ERROR: self.result = None - LOGGER.debug('InterruptableThread.run end. Executed query: {}' - ''.format(self.query)) + LOGGER.debug("InterruptableThread.run end. Executed query: {}" "".format(self.query)) def timeout_query(cursor, query, timeout_duration=3): @@ -74,21 +80,24 @@ def timeout_query(cursor, query, timeout_duration=3): (tuple or :data:`NONE_RESPONSE`): A tuple of results from the query or :py:data:`NONE_RESPONSE` if the query timed out """ - LOGGER.debug('timeout_query start') + LOGGER.debug("timeout_query start") # Spawn a thread for the query query_thread = InterruptableThread(cursor, query) # Start and join query_thread.start() query_thread.join(timeout_duration) - LOGGER.debug('timeout_query end') + LOGGER.debug("timeout_query end") return query_thread.result class StartupException(Exception): """Exception raised when the continous logger fails to start up""" + def __init__(self, *args, **kwargs): - LOGGER.debug('StartupException instantiated with args, kwargs: {}, {}' - ''.format(str(args), str(kwargs))) + LOGGER.debug( + "StartupException instantiated with args, kwargs: {}, {}" + "".format(str(args), str(kwargs)) + ) super(StartupException, self).__init__(*args, **kwargs) @@ -103,11 +112,19 @@ class ContinuousLogger(threading.Thread): :var database: Database name, value is ``cinfdata``. """ - host = 'servcinf-sql.fysik.dtu.dk' - database = 'cinfdata' - - def __init__(self, table, username, password, measurement_codenames, - dequeue_timeout=1, reconnect_waittime=60, dsn=None): + host = "servcinf-sql.fysik.dtu.dk" + database = "cinfdata" + + def __init__( + self, + table, + username, + password, + measurement_codenames, + dequeue_timeout=1, + reconnect_waittime=60, + dsn=None, + ): """Initialize the continous logger Args: @@ -130,27 +147,32 @@ def __init__(self, table, username, password, measurement_codenames, connection or translate the code names """ - deprecation_warning = 'DEPRECATION WARNING: The '\ - 'PyExpLabSys.common.loggers.ContinuousLogger class is deprecated. Please '\ - 'instead use the PyExpLabSys.common.database_saver.ContinuousDataSaver class '\ - 'instead.' + deprecation_warning = ( + "DEPRECATION WARNING: The " + "PyExpLabSys.common.loggers.ContinuousLogger class is deprecated. Please " + "instead use the PyExpLabSys.common.database_saver.ContinuousDataSaver class " + "instead." + ) print(deprecation_warning) - LOGGER.info('CL: __init__ called') + LOGGER.info("CL: __init__ called") # Initialize thread super(ContinuousLogger, self).__init__() self.daemon = True self._stop = False - LOGGER.debug('CL: thread initialized') + LOGGER.debug("CL: thread initialized") # Initialize local variables - self.mysql = {'table': table, 'username': username, - 'password': password, - 'dsn': dsn} + self.mysql = { + "table": table, + "username": username, + "password": password, + "dsn": dsn, + } self._dequeue_timeout = dequeue_timeout self._reconnect_waittime = reconnect_waittime self._cursor = None self._connection = None self.data_queue = queue.Queue() - LOGGER.debug('CL: instance attributes initialized') + LOGGER.debug("CL: instance attributes initialized") # Dict used to translate code_names to measurement numbers self._codename_translation = {} # Init database connection, allow for the system not fully up @@ -158,89 +180,96 @@ def __init__(self, table, username, password, measurement_codenames, db_connection_starttime = time.time() while not database_up: try: - LOGGER.debug('CL: Try to open database connection') + LOGGER.debug("CL: Try to open database connection") self._init_connection() database_up = True except StartupException: if time.time() - db_connection_starttime > 300: raise - LOGGER.debug( - 'CL: Database connection not formed, retry in 20 sec') + LOGGER.debug("CL: Database connection not formed, retry in 20 sec") time.sleep(20) # Get measurement numbers from codenames self._init_measurement_numbers(measurement_codenames) - LOGGER.info('CL: __init__ done') + LOGGER.info("CL: __init__ done") def _init_connection(self): """Initialize the database connection.""" try: - if SQL == 'mysqldb': + if SQL == "mysqldb": self._connection = MySQLdb.connect( - host=self.host, user=self.mysql['username'], - passwd=self.mysql['password'], db=self.database + host=self.host, + user=self.mysql["username"], + passwd=self.mysql["password"], + db=self.database, ) else: - connect_string = 'DSN={}'.format(self.mysql['dsn']) + connect_string = "DSN={}".format(self.mysql["dsn"]) self._connection = pyodbc.connect(connect_string) self._cursor = self._connection.cursor() - LOGGER.info('CL: Database connection initialized') + LOGGER.info("CL: Database connection initialized") except MySQLdb.OperationalError: - message = 'Could not connect to database' - LOGGER.warning('CL: ' + message) + message = "Could not connect to database" + LOGGER.warning("CL: " + message) raise StartupException(message) def _init_measurement_numbers(self, measurement_codenames): """Get the measurement numbers that corresponds to the measurement codenames """ - LOGGER.debug('CL: init measurements numbers') + LOGGER.debug("CL: init measurements numbers") for codename in measurement_codenames: - query = 'SELECT id FROM dateplots_descriptions '\ - 'WHERE codename=\'{}\''.format(codename) - LOGGER.debug('CL: Query: ' + query) + query = "SELECT id FROM dateplots_descriptions " "WHERE codename='{}'".format( + codename + ) + LOGGER.debug("CL: Query: " + query) self._cursor.execute(query) results = self._cursor.fetchall() - LOGGER.debug('CL: query for {} returned {}' - ''.format(codename, str(results))) + LOGGER.debug("CL: query for {} returned {}" "".format(codename, str(results))) if len(results) != 1: - message = 'Measurement code name \'{}\' does not have exactly'\ - ' one entry in dateplots_descriptions'.format(codename) - LOGGER.critical('CL: ' + message) + message = ( + "Measurement code name '{}' does not have exactly" + " one entry in dateplots_descriptions".format(codename) + ) + LOGGER.critical("CL: " + message) raise StartupException(message) self._codename_translation[codename] = results[0][0] - LOGGER.info('Codenames translated to measurement numbers: {}' - ''.format(str(self._codename_translation))) + LOGGER.info( + "Codenames translated to measurement numbers: {}" + "".format(str(self._codename_translation)) + ) def stop(self): """Stop the thread""" - LOGGER.info('CL: Set stop. Wait before returning') + LOGGER.info("CL: Set stop. Wait before returning") self._stop = True time.sleep(max(1, 1.2 * self._dequeue_timeout)) - LOGGER.debug('CL: Stop finished') + LOGGER.debug("CL: Stop finished") def run(self): """Start the thread. Must be run before points are added.""" while not self._stop: try: - point = self.data_queue.get(block=True, - timeout=self._dequeue_timeout) + point = self.data_queue.get(block=True, timeout=self._dequeue_timeout) result = self._send_point(point) LOGGER.info('CL: Point "{}" dequeued and sent'.format(point)) if result is False: self.data_queue.put(point) - LOGGER.debug('CL: Point could not be sent. Re-queued') + LOGGER.debug("CL: Point could not be sent. Re-queued") self._reinit_connection() except queue.Empty: pass # When we stop the logger self._connection.close() - LOGGER.info('Database connection closed. Remaining in queue: {}' - .format(self.data_queue.qsize())) + LOGGER.info( + "Database connection closed. Remaining in queue: {}".format( + self.data_queue.qsize() + ) + ) def _send_point(self, point): """Send all points in the queue to the data base""" result = timeout_query(self._cursor, point) - LOGGER.debug('CL: timeout_query called from send_point') + LOGGER.debug("CL: timeout_query called from send_point") # If the query was un successfully executed, put the points back in # the queue and raise and set succes to False success = False if (result is NONE_RESPONSE) else True @@ -251,13 +280,13 @@ def _reinit_connection(self): database_up = False while not database_up: try: - LOGGER.debug('CL: Try to re-open database connection') + LOGGER.debug("CL: Try to re-open database connection") self._init_connection() database_up = True except StartupException: pass time.sleep(self._reconnect_waittime) - LOGGER.debug('CL: Database connection re-opened') + LOGGER.debug("CL: Database connection re-opened") def enqueue_point_now(self, codename, value): """Add a point to the queue and use the current time as the time @@ -271,7 +300,7 @@ def enqueue_point_now(self, codename, value): float: The Unixtime used """ unixtime = time.time() - LOGGER.debug('CL: Adding timestamp {} to point'.format(unixtime)) + LOGGER.debug("CL: Adding timestamp {} to point".format(unixtime)) self.enqueue_point(codename, (unixtime, value)) return unixtime @@ -287,13 +316,13 @@ def enqueue_point(self, codename, point): try: unixtime, value = point except ValueError: - message = '\'point\' must be a iterable with 2 values' + message = "'point' must be a iterable with 2 values" raise ValueError(message) meas_number = self._codename_translation[codename] - query = ('INSERT INTO {} (type, time, value) VALUES ' - '({}, FROM_UNIXTIME({}), {});') - query = query.format(self.mysql['table'], meas_number, unixtime, value) + query = "INSERT INTO {} (type, time, value) VALUES " "({}, FROM_UNIXTIME({}), {});" + query = query.format(self.mysql["table"], meas_number, unixtime, value) self.data_queue.put(query) - LOGGER.info('CL: Point ({}, {}, {}) added to queue. Queue size: {}' - ''.format(codename, unixtime, value, - self.data_queue.qsize())) + LOGGER.info( + "CL: Point ({}, {}, {}) added to queue. Queue size: {}" + "".format(codename, unixtime, value, self.data_queue.qsize()) + ) diff --git a/PyExpLabSys/common/massspec/channel.py b/PyExpLabSys/common/massspec/channel.py index 9f8f3de4..20fd824b 100644 --- a/PyExpLabSys/common/massspec/channel.py +++ b/PyExpLabSys/common/massspec/channel.py @@ -6,14 +6,30 @@ class MSChannel(object): - """ A mass spec channel """ - - required_fields = ['mass'] - optional_fields = ['channel_range', 'time', 'delay', 'label', 'color', - 'active', 'auto_label'] - - def __init__(self, mass, channel_range='1E-7', time=100, delay=100, - label='', color='#000000', active=True, auto_label=True): + """A mass spec channel""" + + required_fields = ["mass"] + optional_fields = [ + "channel_range", + "time", + "delay", + "label", + "color", + "active", + "auto_label", + ] + + def __init__( + self, + mass, + channel_range="1E-7", + time=100, + delay=100, + label="", + color="#000000", + active=True, + auto_label=True, + ): """Initialize the channel Arguments: @@ -30,23 +46,31 @@ def __init__(self, mass, channel_range='1E-7', time=100, delay=100, active whether the channel is active (bool) auto_label whether to update the label automatically (bool) """ - self._range_translation = {'1E-5': 'L', '1E-7': 'M', '1E-9': 'H', - '1E-11': 'VH'} + self._range_translation = {"1E-5": "L", "1E-7": "M", "1E-9": "H", "1E-11": "VH"} # Internally store the channel properties in a dict - self._channel_desc = \ - {'mass': mass, 'channel_range': channel_range, 'time': time, - 'delay': delay, 'label': label, 'color': color, 'active': active, - 'auto_label': auto_label} + self._channel_desc = { + "mass": mass, + "channel_range": channel_range, + "time": time, + "delay": delay, + "label": label, + "color": color, + "active": active, + "auto_label": auto_label, + } self._update_label() def __str__(self): """Return a str representation""" - active_str = 'Active' if self.active else 'In-active' - out = '{active_str} channel for mass {mass}, range: {channel_range}, '\ - 'time: {time} and delay: {delay}\n'\ - '+label: {label}\n'\ - '+color: {color}, active: {active}, auto_label: {auto_label}'\ - .format(active_str=active_str, **self._channel_desc) + active_str = "Active" if self.active else "In-active" + out = ( + "{active_str} channel for mass {mass}, range: {channel_range}, " + "time: {time} and delay: {delay}\n" + "+label: {label}\n" + "+color: {color}, active: {active}, auto_label: {auto_label}".format( + active_str=active_str, **self._channel_desc + ) + ) return out @property @@ -62,104 +86,107 @@ def from_dict(cls, channel_dict): """ for key in cls.required_fields: if not key in channel_dict: - message = 'The key \'{}\' is missing'.format(key) + message = "The key '{}' is missing".format(key) raise ValueError(message) for key in channel_dict: if not key in cls.required_fields + cls.optional_fields: - message = 'The key \'{}\' is not allowed'.format(key) + message = "The key '{}' is not allowed".format(key) raise ValueError(message) - mass = channel_dict.pop('mass') + mass = channel_dict.pop("mass") return cls(mass, **channel_dict) @property def mass(self): """The mass property""" - return self._channel_desc['mass'] + return self._channel_desc["mass"] @mass.setter def mass(self, mass): # pylint: disable=C0111 - self._channel_desc['mass'] = mass + self._channel_desc["mass"] = mass self._update_label() @property def channel_range(self): """The channel_range property""" - return self._channel_desc['channel_range'] + return self._channel_desc["channel_range"] @channel_range.setter def channel_range(self, channel_range): # pylint: disable=C0111 if channel_range in self._range_translation: - self._channel_desc['channel_range'] = channel_range + self._channel_desc["channel_range"] = channel_range self._update_label() else: - message = '\'{}\' is not a valid value for channel_range, see '\ - 'docstring for __init__'.format(channel_range) + message = ( + "'{}' is not a valid value for channel_range, see " + "docstring for __init__".format(channel_range) + ) raise ValueError(message) @property def time(self): """The time property""" - return self._channel_desc['time'] + return self._channel_desc["time"] @time.setter def time(self, time): # pylint: disable=C0111 - self._channel_desc['time'] = time + self._channel_desc["time"] = time @property def delay(self): """The delay property""" - return self._channel_desc['delay'] + return self._channel_desc["delay"] @delay.setter def delay(self, delay): # pylint: disable=C0111 - self._channel_desc['delay'] = delay + self._channel_desc["delay"] = delay @property def label(self): """The label property""" - return self._channel_desc['label'] + return self._channel_desc["label"] @label.setter def label(self, label): # pylint: disable=C0111 if self.auto_label: - message = 'Cannot set label when auto_label is True' + message = "Cannot set label when auto_label is True" raise AttributeError(message) else: - self._channel_desc['label'] = label + self._channel_desc["label"] = label @property def color(self): """The color property""" - return self._channel_desc['color'] + return self._channel_desc["color"] @color.setter def color(self, color): # pylint: disable=C0111 - self._channel_desc['color'] = color + self._channel_desc["color"] = color @property def active(self): """The active property""" - return self._channel_desc['active'] + return self._channel_desc["active"] @active.setter def active(self, active): # pylint: disable=C0111 - self._channel_desc['active'] = active + self._channel_desc["active"] = active @property def auto_label(self): """The auto_label property""" - return self._channel_desc['auto_label'] + return self._channel_desc["auto_label"] @auto_label.setter def auto_label(self, auto_label): # pylint: disable=C0111 - self._channel_desc['auto_label'] = auto_label + self._channel_desc["auto_label"] = auto_label def _update_label(self): """Update the label if auto_label is active""" if self.auto_label: mass = self.mass - if abs(int(mass) - mass) < 1E-3: + if abs(int(mass) - mass) < 1e-3: mass = int(mass) - self._channel_desc['label'] = 'M{}{}'.\ - format(mass, self._range_translation[self.channel_range]) + self._channel_desc["label"] = "M{}{}".format( + mass, self._range_translation[self.channel_range] + ) return self.label diff --git a/PyExpLabSys/common/massspec/qt.py b/PyExpLabSys/common/massspec/qt.py index dda8f2e6..a69f908b 100644 --- a/PyExpLabSys/common/massspec/qt.py +++ b/PyExpLabSys/common/massspec/qt.py @@ -5,8 +5,9 @@ from PyQt4 import QtGui, QtCore from channel import Channel + class QtMSChannel(object): - """ Mass spectrometer channel including separate widgets to change the + """Mass spectrometer channel including separate widgets to change the values. These widgets are meant to be included separately in a grid. The widget defined are: @@ -20,12 +21,12 @@ class QtMSChannel(object): """ def __init__(self, parent, channel): - """ Initialize the QtMSChannel """ + """Initialize the QtMSChannel""" # Instantiate ??? self._channel = channel # Initialize widgets - self._gui = {'active': QtGui.QCheckBox('Active', parent)} + self._gui = {"active": QtGui.QCheckBox("Active", parent)} def gui(self, name): - """ Return the GUI component with the given name """ + """Return the GUI component with the given name""" return self._gui[name] diff --git a/PyExpLabSys/common/microreactor_temperature_control.py b/PyExpLabSys/common/microreactor_temperature_control.py index e941a87b..abb8759a 100644 --- a/PyExpLabSys/common/microreactor_temperature_control.py +++ b/PyExpLabSys/common/microreactor_temperature_control.py @@ -4,13 +4,16 @@ import logging import curses from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class CursesTui(threading.Thread): - """ Text user interface for heater heating control """ + """Text user interface for heater heating control""" + def __init__(self, heating_class): threading.Thread.__init__(self) self.start_time = time.time() @@ -25,15 +28,15 @@ def __init__(self, heating_class): def run(self): while not self.quit: - self.screen.addstr(3, 2, 'Running') - val = self.heater.power_calculator.values['setpoint'] + self.screen.addstr(3, 2, "Running") + val = self.heater.power_calculator.values["setpoint"] self.screen.addstr(9, 40, "Setpoint: {0:.2f}C ".format(val)) - val = self.heater.power_calculator.values['temperature'] + val = self.heater.power_calculator.values["temperature"] try: self.screen.addstr(9, 2, "Temeperature: {0:.4f}C ".format(val)) except (ValueError, TypeError): self.screen.addstr(9, 2, "Temeperature: - ") - val = self.heater.values['wanted_voltage'] + val = self.heater.values["wanted_voltage"] self.screen.addstr(10, 2, "Wanted Voltage: {0:.2f} ".format(val)) val = self.heater.power_calculator.pid.setpoint self.screen.addstr(11, 2, "PID-setpint: {0:.2f}C ".format(val)) @@ -42,45 +45,49 @@ def run(self): val = time.time() - self.start_time self.screen.addstr(15, 2, "Runetime: {0:.0f}s".format(val)) - val = self.heater.values['actual_voltage_1'] + val = self.heater.values["actual_voltage_1"] self.screen.addstr(11, 40, "Actual Voltage 1: {0:.2f}V ".format(val)) - val = self.heater.values['actual_voltage_2'] + val = self.heater.values["actual_voltage_2"] self.screen.addstr(12, 40, "Actual Voltage 2: {0:.2f}V ".format(val)) - val = self.heater.values['actual_current_1'] * 1000 + val = self.heater.values["actual_current_1"] * 1000 self.screen.addstr(13, 40, "Actual Current 1: {0:.0f}mA ".format(val)) - val = self.heater.values['actual_current_2'] * 1000 + val = self.heater.values["actual_current_2"] * 1000 self.screen.addstr(14, 40, "Actual Current 2: {0:.0f}mA ".format(val)) - power1 = (self.heater.values['actual_voltage_1'] * - self.heater.values['actual_current_1']) + power1 = ( + self.heater.values["actual_voltage_1"] * self.heater.values["actual_current_1"] + ) self.screen.addstr(15, 40, "Power, heater 1: {0:.3f}W ".format(power1)) - power2 = (self.heater.values['actual_voltage_2'] * - self.heater.values['actual_current_2']) - + power2 = ( + self.heater.values["actual_voltage_2"] * self.heater.values["actual_current_2"] + ) + self.screen.addstr(16, 40, "Power, heater 2: {0:.3f}W ".format(power2)) - self.screen.addstr(17, 40, "Total Power1: {0:.3f}W ".format( - power1 + power2)) + self.screen.addstr( + 17, 40, "Total Power1: {0:.3f}W ".format(power1 + power2) + ) self.screen.addstr(19, 2, "Keys: (i)ncrement, (d)ecrement and (q)uit") - key_val = self.screen.getch() - if key_val == ord('q'): + if key_val == ord("q"): self.heater.quit = True self.quit = True - if key_val == ord('i'): + if key_val == ord("i"): self.heater.power_calculator.update_setpoint( - self.heater.power_calculator.values['setpoint'] + 1) - if key_val == ord('d'): + self.heater.power_calculator.values["setpoint"] + 1 + ) + if key_val == ord("d"): self.heater.power_calculator.update_setpoint( - self.heater.power_calculator.values['setpoint'] - 1) + self.heater.power_calculator.values["setpoint"] - 1 + ) self.screen.refresh() time.sleep(0.2) self.stop() - LOGGER.info('TUI ended') + LOGGER.info("TUI ended") def stop(self): - """ Clean up console """ + """Clean up console""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -89,61 +96,62 @@ def stop(self): class HeaterClass(threading.Thread): - """ Do the actual heating """ + """Do the actual heating""" + def __init__(self, power_calculator, pullsocket, power_supply): threading.Thread.__init__(self) self.power_calculator = power_calculator self.pullsocket = pullsocket self.power_supply = power_supply self.values = {} - self.values['wanted_voltage'] = 0 - self.values['actual_voltage_1'] = 0 - self.values['actual_voltage_2'] = 0 - self.values['actual_current_1'] = 0 - self.values['actual_current_2'] = 0 + self.values["wanted_voltage"] = 0 + self.values["actual_voltage_1"] = 0 + self.values["actual_voltage_2"] = 0 + self.values["actual_current_1"] = 0 + self.values["actual_current_2"] = 0 self.quit = False def run(self): while not self.quit: - self.values['wanted_voltage'] = self.power_calculator.read_power() - self.pullsocket.set_point_now('wanted_voltage', self.values['wanted_voltage']) - self.power_supply[1].set_voltage(self.values['wanted_voltage']) + self.values["wanted_voltage"] = self.power_calculator.read_power() + self.pullsocket.set_point_now("wanted_voltage", self.values["wanted_voltage"]) + self.power_supply[1].set_voltage(self.values["wanted_voltage"]) time.sleep(0.1) - self.power_supply[2].set_voltage(self.values['wanted_voltage'] * 0.5) + self.power_supply[2].set_voltage(self.values["wanted_voltage"] * 0.5) ps_value = -11 while ps_value < -10: ps_value = self.power_supply[1].read_actual_voltage() - LOGGER.info('Voltage 1: ' + str(ps_value)) - self.values['actual_voltage_1'] = ps_value - self.pullsocket.set_point_now('actual_voltage_1', ps_value) + LOGGER.info("Voltage 1: " + str(ps_value)) + self.values["actual_voltage_1"] = ps_value + self.pullsocket.set_point_now("actual_voltage_1", ps_value) time.sleep(0.5) ps_value = -11 while ps_value < -10: ps_value = self.power_supply[1].read_actual_current() - self.values['actual_current_1'] = ps_value - self.pullsocket.set_point_now('actual_current_1', ps_value) + self.values["actual_current_1"] = ps_value + self.pullsocket.set_point_now("actual_current_1", ps_value) time.sleep(0.5) ps_value = -11 while ps_value < -10: ps_value = self.power_supply[2].read_actual_voltage() LOGGER.info(ps_value) - self.values['actual_voltage_2'] = ps_value - self.pullsocket.set_point_now('actual_voltage_2', ps_value) + self.values["actual_voltage_2"] = ps_value + self.pullsocket.set_point_now("actual_voltage_2", ps_value) time.sleep(0.5) ps_value = -11 while ps_value < -10: ps_value = self.power_supply[2].read_actual_current() - self.values['actual_current_2'] = ps_value - self.pullsocket.set_point_now('actual_current_2', ps_value) + self.values["actual_current_2"] = ps_value + self.pullsocket.set_point_now("actual_current_2", ps_value) time.sleep(0.5) for i in range(1, 3): self.power_supply[i].set_voltage(0) - LOGGER.info('%s set voltage', i) + LOGGER.info("%s set voltage", i) self.power_supply[i].output_status(False) - LOGGER.info('%s output status', i) + LOGGER.info("%s output status", i) self.stop() def stop(self): - """ Clean up """ + """Clean up""" time.sleep(0.5) diff --git a/PyExpLabSys/common/plotters.py b/PyExpLabSys/common/plotters.py index 7a6a2f79..f0e15af9 100644 --- a/PyExpLabSys/common/plotters.py +++ b/PyExpLabSys/common/plotters.py @@ -12,9 +12,17 @@ class DataPlotter(object): """This class provides a data plotter for continuous data""" - def __init__(self, left_plotlist, right_plotlist=None, left_log=False, - right_log=False, auto_update=True, backend='qwt', parent=None, - **kwargs): + def __init__( + self, + left_plotlist, + right_plotlist=None, + left_log=False, + right_log=False, + auto_update=True, + backend="qwt", + parent=None, + **kwargs, + ): """Initialize the plotting backend, data and local setting :param left_plotlist: Codenames for the plots that should go on the @@ -86,17 +94,18 @@ def __init__(self, left_plotlist, right_plotlist=None, left_log=False, # Check legend message = message or self._init_check_legends_plots(all_plots, kwargs) # Backend - if backend not in ['qwt']: - message = 'Backend must be \'qwt\'' + if backend not in ["qwt"]: + message = "Backend must be 'qwt'" if message is not None: raise ValueError(message) # Initiate the backend - if backend == 'qwt': + if backend == "qwt": from PyExpLabSys.common.plotters_backend_qwt import QwtPlot - self._plot = QwtPlot(parent, left_plotlist, right_plotlist, - left_log, right_log, - **kwargs) + + self._plot = QwtPlot( + parent, left_plotlist, right_plotlist, left_log, right_log, **kwargs + ) # Initiate the data self._data = {} @@ -110,19 +119,24 @@ def _init_check_left(left_plotlist, kwargs): """Check input related to the left curves""" message = None if not len(left_plotlist) > 0: - message = 'At least one item in left_plotlist is required' + message = "At least one item in left_plotlist is required" # Check number of left labels and colors - for kwarg in ['left_labels', 'left_colors']: - if kwargs.get(kwarg) is not None and\ - len(left_plotlist) != len(kwargs.get(kwarg)): - message = 'There must be as many items in \'{}\' as there '\ - 'are left plots'.format(kwarg) + for kwarg in ["left_labels", "left_colors"]: + if kwargs.get(kwarg) is not None and len(left_plotlist) != len(kwargs.get(kwarg)): + message = ( + "There must be as many items in '{}' as there " + "are left plots".format(kwarg) + ) # Check left thickness if it is a list - if kwargs.get('left_thickness') is not None and\ - isinstance(kwargs['left_thickness'], collections.Iterable) and\ - len(left_plotlist) != len(kwargs['left_thickness']): - message = '\'left_thickness\' must either be an int or a iterable'\ - ' with as many ints as there are left plots' + if ( + kwargs.get("left_thickness") is not None + and isinstance(kwargs["left_thickness"], collections.Iterable) + and len(left_plotlist) != len(kwargs["left_thickness"]) + ): + message = ( + "'left_thickness' must either be an int or a iterable" + " with as many ints as there are left plots" + ) return message @staticmethod @@ -130,30 +144,40 @@ def _init_check_right(right_plotlist, kwargs): """Check input related to the right curves""" message = None if right_plotlist is not None: - for kwarg in ['right_labels', 'right_colors']: - if kwargs.get(kwarg) is not None and\ - len(right_plotlist) != len(kwargs.get(kwarg)): - message = 'There must be as many items in \'{}\' as '\ - 'there are right plots'.format(kwarg) - if kwargs.get('right_thickness') is not None and\ - isinstance(kwargs['right_thickness'], collections.Iterable)\ - and len(right_plotlist) != len(kwargs['right_thickness']): - message = '\'right_thickness\' must either be an int or a'\ - ' iterable with as many ints as there are left plots' + for kwarg in ["right_labels", "right_colors"]: + if kwargs.get(kwarg) is not None and len(right_plotlist) != len( + kwargs.get(kwarg) + ): + message = ( + "There must be as many items in '{}' as " + "there are right plots".format(kwarg) + ) + if ( + kwargs.get("right_thickness") is not None + and isinstance(kwargs["right_thickness"], collections.Iterable) + and len(right_plotlist) != len(kwargs["right_thickness"]) + ): + message = ( + "'right_thickness' must either be an int or a" + " iterable with as many ints as there are left plots" + ) return message @staticmethod def _init_check_legends_plots(all_plots, kwargs): """Check legend name and for duplicate plot names""" message = None - if kwargs.get('legend') is not None and not kwargs['legend'] in\ - ['left', 'right', 'bottom', 'top']: - message = 'legend must be one of: \'left\', \'right\', '\ - '\'bottom\', \'top\'' + if kwargs.get("legend") is not None and not kwargs["legend"] in [ + "left", + "right", + "bottom", + "top", + ]: + message = "legend must be one of: 'left', 'right', " "'bottom', 'top'" # Check for duplicate plot names for plot in all_plots: if all_plots.count(plot) > 1: - message = 'Duplicate codename {} not allowed'.format(plot) + message = "Duplicate codename {} not allowed".format(plot) return message def add_point(self, plot, point, update=None): @@ -193,9 +217,18 @@ def plot(self): class ContinuousPlotter(object): """This class provides a data plotter for continuous data""" - def __init__(self, left_plotlist, right_plotlist=None, left_log=False, - right_log=False, timespan=600, preload=60, auto_update=True, - backend='none', **kwargs): + def __init__( + self, + left_plotlist, + right_plotlist=None, + left_log=False, + right_log=False, + timespan=600, + preload=60, + auto_update=True, + backend="none", + **kwargs, + ): """Initialize the plotting backend, data and local setting :param left_plotlist: Codenames for the plots that should go on the @@ -232,23 +265,26 @@ def __init__(self, left_plotlist, right_plotlist=None, left_log=False, # Input checks message = None if not len(left_plotlist) > 0: - message = 'At least one item in left_plotlist is required' - if kwargs.get('left_labels') is not None and\ - len(left_plotlist) != len(kwargs.get('left_labels')): - message = 'There must be as many left labels as there are plots' - if right_plotlist is not None and\ - kwargs.get('right_labels') is not None and\ - len(right_plotlist) != len(kwargs.get('right_labels')): - message = 'There must be as many right labels as there are plots' + message = "At least one item in left_plotlist is required" + if kwargs.get("left_labels") is not None and len(left_plotlist) != len( + kwargs.get("left_labels") + ): + message = "There must be as many left labels as there are plots" + if ( + right_plotlist is not None + and kwargs.get("right_labels") is not None + and len(right_plotlist) != len(kwargs.get("right_labels")) + ): + message = "There must be as many right labels as there are plots" if timespan < 1: - message = 'timespan must be positive' + message = "timespan must be positive" if preload < 0: - message = 'preload must be positive or 0' - if backend not in ['none']: - message = 'Backend must be \'none\'' + message = "preload must be positive or 0" + if backend not in ["none"]: + message = "Backend must be 'none'" for plot in all_plots: if all_plots.count(plot) > 1: - message = 'Duplicate codename {} not allowed'.format(plot) + message = "Duplicate codename {} not allowed".format(plot) if message is not None: raise ValueError(message) @@ -269,7 +305,7 @@ def __init__(self, left_plotlist, right_plotlist=None, left_log=False, self.start = time.time() self.end = self.start + timespan - message = 'No plotter for continuous data implemented' + message = "No plotter for continuous data implemented" raise NotImplementedError(message) def add_point_now(self, plot, value, update=None): diff --git a/PyExpLabSys/common/plotters_backend_qwt.py b/PyExpLabSys/common/plotters_backend_qwt.py index 32ad964d..1b41ba2e 100644 --- a/PyExpLabSys/common/plotters_backend_qwt.py +++ b/PyExpLabSys/common/plotters_backend_qwt.py @@ -7,25 +7,31 @@ import os import numpy as np + try: from PyQt4 import Qt, QtGui, QtCore import PyQt4.Qwt5 as Qwt except ImportError: # If we are building docs for read the docs, make fake version else re-raise import sys - if os.environ.get('READTHEDOCS', None) == 'True' or 'sphinx' in sys.modules: + + if os.environ.get("READTHEDOCS", None) == "True" or "sphinx" in sys.modules: + class Qwt: QwtPlot = list + else: raise + class Colors: """Class that gives plot colors""" + def __init__(self): self.current = -1 self.predefined = [] self.colors = QtGui.QColor.colorNames() - for name in ['blue', 'red', 'black', 'green', 'magenta']: + for name in ["blue", "red", "black", "green", "magenta"]: index = self.colors.indexOf(QtCore.QString(name)) self.predefined.append(self.colors[index]) self.colors.removeAt(index) @@ -43,9 +49,16 @@ def get_color(self): class QwtPlot(Qwt.QwtPlot): """Class that represents a Qwt plot""" - def __init__(self, parent, left_plotlist, right_plotlist=None, - left_log=False, right_log=False, - **kwargs): + + def __init__( + self, + parent, + left_plotlist, + right_plotlist=None, + left_log=False, + right_log=False, + **kwargs, + ): """Initialize the plot and local setting :param parent: The parent GUI object, then that should be supplied here @@ -133,19 +146,24 @@ def _init_check_left(left_plotlist, kwargs): """Check input related to the left curves""" message = None if not len(left_plotlist) > 0: - message = 'At least one item in left_plotlist is required' + message = "At least one item in left_plotlist is required" # Check number of left labels and colors - for kwarg in ['left_labels', 'left_colors']: - if kwargs.get(kwarg) is not None and\ - len(left_plotlist) != len(kwargs.get(kwarg)): - message = 'There must be as many items in \'{}\' as there '\ - 'are left plots'.format(kwarg) + for kwarg in ["left_labels", "left_colors"]: + if kwargs.get(kwarg) is not None and len(left_plotlist) != len(kwargs.get(kwarg)): + message = ( + "There must be as many items in '{}' as there " + "are left plots".format(kwarg) + ) # Check left thickness if it is a list - if kwargs.get('left_thickness') is not None and\ - isinstance(kwargs['left_thickness'], collections.Iterable) and\ - len(left_plotlist) != len(kwargs['left_thickness']): - message = '\'left_thickness\' must either be an int or a iterable'\ - ' with as many ints as there are left plots' + if ( + kwargs.get("left_thickness") is not None + and isinstance(kwargs["left_thickness"], collections.Iterable) + and len(left_plotlist) != len(kwargs["left_thickness"]) + ): + message = ( + "'left_thickness' must either be an int or a iterable" + " with as many ints as there are left plots" + ) return message @staticmethod @@ -153,56 +171,65 @@ def _init_check_right(right_plotlist, kwargs): """Check input related to the right curves""" message = None if right_plotlist is not None: - for kwarg in ['right_labels', 'right_colors']: - if kwargs.get(kwarg) is not None and\ - len(right_plotlist) != len(kwargs.get(kwarg)): - message = 'There must be as many items in \'{}\' as '\ - 'there are right plots'.format(kwarg) - if kwargs.get('right_thickness') is not None and\ - isinstance(kwargs['right_thickness'], collections.Iterable)\ - and len(right_plotlist) != len(kwargs['right_thickness']): - message = '\'right_thickness\' must either be an int or a'\ - ' iterable with as many ints as there are left plots' + for kwarg in ["right_labels", "right_colors"]: + if kwargs.get(kwarg) is not None and len(right_plotlist) != len( + kwargs.get(kwarg) + ): + message = ( + "There must be as many items in '{}' as " + "there are right plots".format(kwarg) + ) + if ( + kwargs.get("right_thickness") is not None + and isinstance(kwargs["right_thickness"], collections.Iterable) + and len(right_plotlist) != len(kwargs["right_thickness"]) + ): + message = ( + "'right_thickness' must either be an int or a" + " iterable with as many ints as there are left plots" + ) return message @staticmethod def _init_check_legends_plots(all_plots, kwargs): """Check legend name and for duplicate plot names""" message = None - if kwargs.get('legend') is not None and not kwargs['legend'] in\ - ['left', 'right', 'bottom', 'top']: - message = 'legend must be one of: \'left\', \'right\', '\ - '\'bottom\', \'top\'' + if kwargs.get("legend") is not None and not kwargs["legend"] in [ + "left", + "right", + "bottom", + "top", + ]: + message = "legend must be one of: 'left', 'right', " "'bottom', 'top'" # Check for duplicate plot names for plot in all_plots: if all_plots.count(plot) > 1: - message = 'Duplicate codename {} not allowed'.format(plot) + message = "Duplicate codename {} not allowed".format(plot) return message def _init_background(self, kwargs): """Init the background color of the graph""" - if kwargs.get('background_color') is not None: - self.setCanvasBackground( - QtGui.QColor(kwargs['background_color'])) + if kwargs.get("background_color") is not None: + self.setCanvasBackground(QtGui.QColor(kwargs["background_color"])) def _init_left_curves(self, left_plotlist, kwargs): """Init the curves on the left axis""" for index, plot in enumerate(left_plotlist): label = plot - if kwargs.get('left_labels') is not None: - label = kwargs['left_labels'][index] + if kwargs.get("left_labels") is not None: + label = kwargs["left_labels"][index] curve = Qwt.QwtPlotCurve(label) - if kwargs.get('left_colors') is not None: - color = kwargs['left_colors'][index] + if kwargs.get("left_colors") is not None: + color = kwargs["left_colors"][index] else: color = self.colors.get_color() - if kwargs.get('left_thickness') is not None: - if isinstance(kwargs['left_thickness'], collections.Iterable): - thickness = kwargs['left_thickness'][index] + if kwargs.get("left_thickness") is not None: + if isinstance(kwargs["left_thickness"], collections.Iterable): + thickness = kwargs["left_thickness"][index] else: - thickness = kwargs['left_thickness'] + thickness = kwargs["left_thickness"] curve.setPen(Qt.QPen(QtGui.QColor(color), thickness)) else: curve.setPen(Qt.QPen(QtGui.QColor(color))) @@ -217,22 +244,22 @@ def _init_right_curves(self, right_plotlist, kwargs): # Form the right axis curves for index, plot in enumerate(right_plotlist): label = plot - if kwargs.get('right_labels') is not None: - label = kwargs['right_labels'][index] + if kwargs.get("right_labels") is not None: + label = kwargs["right_labels"][index] curve = Qwt.QwtPlotCurve(label) curve.setYAxis(QwtPlot.yRight) - if kwargs.get('right_colors') is not None: - color = kwargs['right_colors'][index] + if kwargs.get("right_colors") is not None: + color = kwargs["right_colors"][index] else: color = self.colors.get_color() curve.setPen(Qt.QPen(QtGui.QColor(color))) - if kwargs.get('right_thickness') is not None: - if isinstance(kwargs['right_thickness'], collections.Iterable): - thickness = kwargs['right_thickness'][index] + if kwargs.get("right_thickness") is not None: + if isinstance(kwargs["right_thickness"], collections.Iterable): + thickness = kwargs["right_thickness"][index] else: - thickness = kwargs['right_thickness'] + thickness = kwargs["right_thickness"] curve.setPen(Qt.QPen(QtGui.QColor(color), thickness)) else: curve.setPen(Qt.QPen(QtGui.QColor(color))) @@ -249,25 +276,21 @@ def _init_logscales(self, left_log, right_log, right_plotlist): def _init_title_label_legend(self, right_plotlist, kwargs): """Init the title, axis labels and legends""" - if kwargs.get('legend') is not None: - legend_name = kwargs['legend'].title() + 'Legend' - self.insertLegend(Qwt.QwtLegend(), - getattr(Qwt.QwtPlot, legend_name)) - if kwargs.get('title') is not None: - self.setTitle(kwargs['title']) - if kwargs.get('xaxis_label') is not None: - self.setAxisTitle(Qwt.QwtPlot.xBottom, kwargs['xaxis_label']) - if kwargs.get('yaxis_left_label') is not None: - self.setAxisTitle(Qwt.QwtPlot.yLeft, - kwargs['yaxis_left_label']) - if kwargs.get('yaxis_right_label') is not None and \ - right_plotlist is not None: - self.setAxisTitle(Qwt.QwtPlot.yRight, - kwargs['yaxis_right_label']) + if kwargs.get("legend") is not None: + legend_name = kwargs["legend"].title() + "Legend" + self.insertLegend(Qwt.QwtLegend(), getattr(Qwt.QwtPlot, legend_name)) + if kwargs.get("title") is not None: + self.setTitle(kwargs["title"]) + if kwargs.get("xaxis_label") is not None: + self.setAxisTitle(Qwt.QwtPlot.xBottom, kwargs["xaxis_label"]) + if kwargs.get("yaxis_left_label") is not None: + self.setAxisTitle(Qwt.QwtPlot.yLeft, kwargs["yaxis_left_label"]) + if kwargs.get("yaxis_right_label") is not None and right_plotlist is not None: + self.setAxisTitle(Qwt.QwtPlot.yRight, kwargs["yaxis_right_label"]) def update(self, data): """Update the plot with new values and possibly move the xaxis - + :param data: The data to plot. Should be a dict, where keys are plot code names and values are data series as an iterable of (x, y) iterables. E.g. {'plot1': [(1, 1), (2, 2)]} @@ -276,6 +299,5 @@ def update(self, data): for key, dataseries in data.items(): if len(dataseries) > 0: values_array = np.array(dataseries) - self._curves[key].setData(values_array[:, 0], - values_array[:, 1]) + self._curves[key].setData(values_array[:, 0], values_array[:, 1]) self.replot() diff --git a/PyExpLabSys/common/pressure_controller_xgs600.py b/PyExpLabSys/common/pressure_controller_xgs600.py index 36ce00b8..4934ca4a 100644 --- a/PyExpLabSys/common/pressure_controller_xgs600.py +++ b/PyExpLabSys/common/pressure_controller_xgs600.py @@ -11,8 +11,11 @@ class XGS600Control(threading.Thread): """Read all pressures and control states of - setpoints (ON/OFF/AUTO) in xgs600""" - def __init__(self, port, socket_name, codenames, user_labels, valve_properties, db_saver=None): + setpoints (ON/OFF/AUTO) in xgs600""" + + def __init__( + self, port, socket_name, codenames, user_labels, valve_properties, db_saver=None + ): """ Args: port (int): directory to port of USB to RS232 communication @@ -33,31 +36,31 @@ def __init__(self, port, socket_name, codenames, user_labels, valve_properties, self.xgs600 = xgs600(port=port) time.sleep(0.2) threading.Thread.__init__(self) - #setting the initial setup specific values + # setting the initial setup specific values self.codenames = codenames self.devices = user_labels self.valve_properties = valve_properties self.valve_names = list(self.valve_properties.keys()) - #setting the initial program specific values + # setting the initial program specific values self.pressures = None self.setpointstates = None self.quit = False - self.update_time = [time.time()]*len(self.valve_names) - self.updated = [0]*len(self.valve_names) + self.update_time = [time.time()] * len(self.valve_names) + self.updated = [0] * len(self.valve_names) names = self.codenames + self.devices print(names) - #starting push-, pull-, and live- sockets + # starting push-, pull-, and live- sockets self.pullsocket = DateDataPullSocket( socket_name, names, timeouts=3.0, port=9000, - ) + ) self.pullsocket.start() - self.pushsocket = DataPushSocket(socket_name, action='enqueue') + self.pushsocket = DataPushSocket(socket_name, action="enqueue") self.pushsocket.start() self.livesocket = LiveSocket(socket_name, names) @@ -70,8 +73,8 @@ def __init__(self, port, socket_name, codenames, user_labels, valve_properties, def value(self): """Return two lists - 1. list of floats which is pressures from top down on xgs600 unit - 2. list of string representing the state of each valve connnected""" + 1. list of floats which is pressures from top down on xgs600 unit + 2. list of string representing the state of each valve connnected""" return self.pressures, self.setpointstates def update_new_setpoint(self): @@ -87,9 +90,8 @@ def update_new_setpoint(self): self.livesocket.set_point_now(self.codenames[0], self.pressures) self.livesocket.set_point_now(self.codenames[1], self.setpointstates) - print('press:', self.pressures) - print('state:', self.setpointstates) - + print("press:", self.pressures) + print("state:", self.setpointstates) def database_saver(self): """ @@ -99,34 +101,47 @@ def database_saver(self): for valve in self.valve_names: channel = self.valve_properties[valve][0] try: - valve_state = self.setpointstates[channel-1] + valve_state = self.setpointstates[channel - 1] valve_setpoint = self.xgs600.read_setpoint(channel) except TimeoutError: - print('Oops, could not read setpoint of channel, valve_state,\ - valve_setpoint: ', channel, valve_state, valve_setpoint) - - if valve_setpoint is not 'OFF' and valve_state == False: - print('Holy Crap it is closed and should be open') - print('Channel, Valve_state, Valve_setpoint',\ - channel, valve_state, valve_setpoint) - if self.updated[channel-1] == 0: - print('Saved to Database', channel, valve_state, valve_setpoint) - self.db_saver.save_point_now('microreactorng_valve_'+valve, -1) - self.updated[channel-1] = 1 + print( + "Oops, could not read setpoint of channel, valve_state,\ + valve_setpoint: ", + channel, + valve_state, + valve_setpoint, + ) + + if valve_setpoint is not "OFF" and valve_state == False: + print("Holy Crap it is closed and should be open") + print( + "Channel, Valve_state, Valve_setpoint", + channel, + valve_state, + valve_setpoint, + ) + if self.updated[channel - 1] == 0: + print("Saved to Database", channel, valve_state, valve_setpoint) + self.db_saver.save_point_now("microreactorng_valve_" + valve, -1) + self.updated[channel - 1] = 1 else: - print('Not saved to database, due to earlier being saved',\ - channel, valve_state, valve_setpoint) - if time.time() - self.update_time[channel-1] > 300: - self.update_time[channel-1] = time.time() - self.updated[channel-1] = 0 + print( + "Not saved to database, due to earlier being saved", + channel, + valve_state, + valve_setpoint, + ) + if time.time() - self.update_time[channel - 1] > 300: + self.update_time[channel - 1] = time.time() + self.updated[channel - 1] = 0 else: - if time.time() - self.update_time[channel-1] > 300: - self.db_saver.save_point_now('microreactorng_valve_'+valve, -1) - self.update_time[channel-1] = time.time() - self.updated[channel-1] = 0 - print('Saved to Database', channel, valve_state, valve_setpoint) + if time.time() - self.update_time[channel - 1] > 300: + self.db_saver.save_point_now("microreactorng_valve_" + valve, -1) + self.update_time[channel - 1] = time.time() + self.updated[channel - 1] = 0 + print("Saved to Database", channel, valve_state, valve_setpoint) - self.updated[channel-1] = 0 + self.updated[channel - 1] = 0 print(channel, valve_state, valve_setpoint) def run(self): @@ -143,27 +158,27 @@ def run(self): setpoint_on = self.valve_properties[valve][2] setpoint_off = self.valve_properties[valve][3] - if state.lower() == 'off' or state == 0: + if state.lower() == "off" or state == 0: self.xgs600.set_setpoint(channel, state) else: self.xgs600.set_setpoint_on( channel, - sensor_code='user_label', + sensor_code="user_label", sensor_count=user_label, pressure_on=setpoint_on, - ) + ) self.xgs600.set_setpoint_off( channel, - sensor_code='user_label', + sensor_code="user_label", sensor_count=user_label, pressure_off=setpoint_off, - ) - + ) + self.xgs600.set_setpoint(channel, state) - #Read values of pressures and states of setpoint + # Read values of pressures and states of setpoint self.pressures = self.xgs600.read_all_pressures() time.sleep(0.1) self.setpointstates = self.xgs600.read_setpoint_state() diff --git a/PyExpLabSys/common/socket_clients.py b/PyExpLabSys/common/socket_clients.py index 2c5b9585..351f52ff 100644 --- a/PyExpLabSys/common/socket_clients.py +++ b/PyExpLabSys/common/socket_clients.py @@ -8,7 +8,7 @@ import json -OLD_DATA = 'OLD_DATA' +OLD_DATA = "OLD_DATA" CHUNK_SIZE = 1024 @@ -18,7 +18,14 @@ class DateDataPullClient(object): codenames and name are available as attributes """ - def __init__(self, host, expected_socket_name, port=9000, exception_on_old_data=True, timeout=None): + def __init__( + self, + host, + expected_socket_name, + port=9000, + exception_on_old_data=True, + timeout=None, + ): """Initialize the DateDataPullClient object""" self.exception_on_old_data = exception_on_old_data self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -27,45 +34,44 @@ def __init__(self, host, expected_socket_name, port=9000, exception_on_old_data= self.host_port = (host, port) # Read name and test expected_socket_name - self.name = self._communicate('name') + self.name = self._communicate("name") if self.name != expected_socket_name: msg = 'The name of the socket at {} was not "{}" as expected, but "{}"' raise ValueError(msg.format(self.host_port, expected_socket_name, self.name)) # Read codenames - codenames_json = self._communicate('codenames_json') + codenames_json = self._communicate("codenames_json") self.codenames = json.loads(codenames_json) self.codenames_set = set(self.codenames) - def _communicate(self, command): """Encode, send and decode a command for a socket""" - self.socket_.sendto(command.encode('utf-8'), self.host_port) + self.socket_.sendto(command.encode("utf-8"), self.host_port) # Received all - received = b'' + received = b"" while True: chunk = self.socket_.recv(CHUNK_SIZE) received += chunk if len(chunk) < CHUNK_SIZE: break - return received.decode('utf-8') - + return received.decode("utf-8") + def get_field(self, fieldname): """Return field by name""" # Check for valud fieldname if fieldname not in self.codenames_set: - msg = 'Unknown fieldnames, valid fields are: {}'.format(self.codenames) + msg = "Unknown fieldnames, valid fields are: {}".format(self.codenames) raise ValueError(msg) - - data_json = self._communicate('{}#json'.format(fieldname)) + + data_json = self._communicate("{}#json".format(fieldname)) data = json.loads(data_json) if data == OLD_DATA and self.exception_on_old_data: - raise ValueError('Old data') + raise ValueError("Old data") return data def get_all_fields(self): """Return all fields""" - data_json = self._communicate('json_wn') + data_json = self._communicate("json_wn") data = json.loads(data_json) for fieldname, value in data.items(): if value == OLD_DATA and self.exception_on_old_data: @@ -84,22 +90,21 @@ def __getattr__(self, name): msg = "{} object has no attribute '{}'".format(self.__class__.__name__, name) raise AttributeError(msg) - def module_demo(): - date_data_pull_client = DateDataPullClient('127.0.0.1', 'testsocket') + date_data_pull_client = DateDataPullClient("127.0.0.1", "testsocket") print("Name:", date_data_pull_client.name) print("Codenames:", date_data_pull_client.codenames) - print("new:", date_data_pull_client.get_field('new')) + print("new:", date_data_pull_client.get_field("new")) try: - print(date_data_pull_client.get_field('old')) + print(date_data_pull_client.get_field("old")) except ValueError as exp: - print('old raises', exp, 'as expected') + print("old raises", exp, "as expected") try: date_data_pull_client.get_all_fields() except ValueError as exp: - print('all raises', exp, 'as expected') + print("all raises", exp, "as expected") date_data_pull_client.exception_on_old_data = False print("All:", date_data_pull_client.get_all_fields()) @@ -107,8 +112,9 @@ def module_demo(): print(date_data_pull_client.new) from pprint import pprint + pprint(date_data_pull_client.get_status()) -if __name__ == '__main__': +if __name__ == "__main__": module_demo() diff --git a/PyExpLabSys/common/sockets.py b/PyExpLabSys/common/sockets.py index 150e0c27..b6a10d1d 100644 --- a/PyExpLabSys/common/sockets.py +++ b/PyExpLabSys/common/sockets.py @@ -42,6 +42,7 @@ import sys import threading import socket + try: import SocketServer except ImportError: @@ -49,6 +50,7 @@ import socketserver as SocketServer import time import json + try: import Queue except ImportError: @@ -79,11 +81,13 @@ def bool_translate(string): """Returns boolean value from strings 'True' or 'False'""" - if str(string) not in ['True', 'False']: - message = 'Cannot translate the string \'{}\' to a boolean. Only the '\ - 'strings \'True\' or \'False\' are allowed'.format(string) + if str(string) not in ["True", "False"]: + message = ( + "Cannot translate the string '{}' to a boolean. Only the " + "strings 'True' or 'False' are allowed".format(string) + ) raise ValueError(message) - return True if str(string) == 'True' else False + return True if str(string) == "True" else False def socket_server_status(): @@ -95,28 +99,29 @@ def socket_server_status(): """ status_dict = {} for port, data in DATA.items(): - if data['activity']['check_activity']: - since_last_activity = time.time() -\ - data['activity']['last_activity'] - if since_last_activity < data['activity']['activity_timeout']: - status = 'OK' + if data["activity"]["check_activity"]: + since_last_activity = time.time() - data["activity"]["last_activity"] + if since_last_activity < data["activity"]["activity_timeout"]: + status = "OK" else: - status = 'INACTIVE' + status = "INACTIVE" else: - status = 'DISABLED' + status = "DISABLED" since_last_activity = None status_dict[port] = { - 'name': data['name'], - 'type': data['type'], - 'status': status, - 'since_last_activity': since_last_activity + "name": data["name"], + "type": data["type"], + "status": status, + "since_last_activity": since_last_activity, } return status_dict -PULLUHLOG = logging.getLogger(__name__ + '.PullUDPHandler') +PULLUHLOG = logging.getLogger(__name__ + ".PullUDPHandler") PULLUHLOG.addHandler(logging.NullHandler()) + + class PullUDPHandler(SocketServer.BaseRequestHandler): """Request handler for the :class:`.DateDataPullSocket` and :class:`.DateDataPullSocket` socket servers. The commands this request @@ -155,21 +160,25 @@ def handle(self): * **status** (*str*): Return the system status and status for all socket servers. """ - command = self.request[0].decode('ascii') + command = self.request[0].decode("ascii") # pylint: disable=attribute-defined-outside-init self.port = self.server.server_address[1] sock = self.request[1] - PULLUHLOG.debug('Request \'%s\' received from %s on port %s', - command, self.client_address, self.port) + PULLUHLOG.debug( + "Request '%s' received from %s on port %s", + command, + self.client_address, + self.port, + ) - if command.count('#') == 1: + if command.count("#") == 1: data = self._single_value(command) else: # The "name" and "status" commands are also handled here data = self._all_values(command) - sock.sendto(data.encode('ascii'), self.client_address) - PULLUHLOG.debug('Sent back \'%s\' to %s', data, self.client_address) + sock.sendto(data.encode("ascii"), self.client_address) + PULLUHLOG.debug("Sent back '%s' to %s", data, self.client_address) def _single_value(self, command): """Returns a string for a single point @@ -180,20 +189,20 @@ def _single_value(self, command): Returns: str: The data as a string (or an error) to be sent back """ - PULLUHLOG.debug('Parsing single value command: %s', command) - name, command = command.split('#') + PULLUHLOG.debug("Parsing single value command: %s", command) + name, command = command.split("#") # Return as raw string - if command == 'raw' and name in DATA[self.port]['data']: + if command == "raw" and name in DATA[self.port]["data"]: if self._old_data(name): out = OLD_DATA else: - out = '{},{}'.format(*DATA[self.port]['data'][name]) + out = "{},{}".format(*DATA[self.port]["data"][name]) - elif command == 'json' and name in DATA[self.port]['data']: + elif command == "json" and name in DATA[self.port]["data"]: if self._old_data(name): out = six.text_type(json.dumps(OLD_DATA)) else: - out = six.text_type(json.dumps(DATA[self.port]['data'][name])) + out = six.text_type(json.dumps(DATA[self.port]["data"][name])) # The command is unknown else: out = UNKNOWN_COMMAND @@ -210,61 +219,63 @@ def _all_values(self, command): Returns: str: The data as a string (or an error) to be sent back """ - PULLUHLOG.debug('Parsing all-values command: %s', command) + PULLUHLOG.debug("Parsing all-values command: %s", command) # Return a raw string with all measurements in codenames order - if command == 'raw': + if command == "raw": strings = [] - for codename in DATA[self.port]['codenames']: + for codename in DATA[self.port]["codenames"]: if self._old_data(codename): string = OLD_DATA else: - string = '{},{}'.format(*DATA[self.port]['data'][codename]) + string = "{},{}".format(*DATA[self.port]["data"][codename]) strings.append(string) - out = ';'.join(strings) + out = ";".join(strings) # Return a json encoded string with list of all measurements - elif command == 'json': + elif command == "json": points = [] - for codename in DATA[self.port]['codenames']: + for codename in DATA[self.port]["codenames"]: if self._old_data(codename): data = OLD_DATA else: - data = DATA[self.port]['data'][codename] + data = DATA[self.port]["data"][codename] points.append(data) out = six.text_type(json.dumps(points)) # Return a raw string with all measurements in codenames order including names - elif command == 'raw_wn': + elif command == "raw_wn": strings = [] - for codename in DATA[self.port]['codenames']: + for codename in DATA[self.port]["codenames"]: if self._old_data(codename): - string = '{}:{}'.format(codename, OLD_DATA) + string = "{}:{}".format(codename, OLD_DATA) else: - string = '{}:{},{}'.format( - codename, *DATA[self.port]['data'][codename] - ) + string = "{}:{},{}".format(codename, *DATA[self.port]["data"][codename]) strings.append(string) - out = ';'.join(strings) + out = ";".join(strings) # Return a copy of the data dict encoded as a json string - elif command == 'json_wn': - datacopy = dict(DATA[self.port]['data']) - for codename in DATA[self.port]['codenames']: + elif command == "json_wn": + datacopy = dict(DATA[self.port]["data"]) + for codename in DATA[self.port]["codenames"]: if self._old_data(codename): datacopy[codename] = OLD_DATA out = six.text_type(json.dumps(datacopy)) # Return all codesnames in a raw string - elif command == 'codenames_raw': - out = ','.join(DATA[self.port]['codenames']) + elif command == "codenames_raw": + out = ",".join(DATA[self.port]["codenames"]) # Return a list with all codenames encoded as a json string - elif command == 'codenames_json': - out = six.text_type(json.dumps(DATA[self.port]['codenames'])) + elif command == "codenames_json": + out = six.text_type(json.dumps(DATA[self.port]["codenames"])) # Return the socket server name - elif command == 'name': - out = DATA[self.port]['name'] + elif command == "name": + out = DATA[self.port]["name"] # Return status of system and all socket servers - elif command == 'status': - out = six.text_type(json.dumps({ - 'system_status': SYSTEM_STATUS.complete_status(), - 'socket_server_status': socket_server_status() - })) + elif command == "status": + out = six.text_type( + json.dumps( + { + "system_status": SYSTEM_STATUS.complete_status(), + "socket_server_status": socket_server_status(), + } + ) + ) # The command is not known else: out = UNKNOWN_COMMAND @@ -281,33 +292,36 @@ def _old_data(self, codename): Returns: bool: Whether the data is too old or not """ - PULLUHLOG.debug('Check if data for \'%s\' is too old', codename) + PULLUHLOG.debug("Check if data for '%s' is too old", codename) now = time.time() - if DATA[self.port]['type'] == 'date': - timeout = DATA[self.port]['timeouts'].get(codename) + if DATA[self.port]["type"] == "date": + timeout = DATA[self.port]["timeouts"].get(codename) if timeout is None: out = False else: - point_time = DATA[self.port]['data'][codename][0] + point_time = DATA[self.port]["data"][codename][0] out = now - point_time > timeout - elif DATA[self.port]['type'] == 'data': - timeout = DATA[self.port]['timeouts'].get(codename) + elif DATA[self.port]["type"] == "data": + timeout = DATA[self.port]["timeouts"].get(codename) if timeout is None: out = False else: - timestamp = DATA[self.port]['timestamps'][codename] + timestamp = DATA[self.port]["timestamps"][codename] out = now - timestamp > timeout else: - message = 'Checking for timeout is not yet implemented for type '\ - '\'{}\''.format(DATA[self.port]['type']) + message = "Checking for timeout is not yet implemented for type " "'{}'".format( + DATA[self.port]["type"] + ) PULLUHLOG.error(message) raise NotImplementedError(message) return out -CDPULLSLOG = logging.getLogger(__name__ + '.CommonDataPullSocket') +CDPULLSLOG = logging.getLogger(__name__ + ".CommonDataPullSocket") CDPULLSLOG.addHandler(logging.NullHandler()) + + class CommonDataPullSocket(threading.Thread): """Abstract class that implements common data pull socket functionality. @@ -320,9 +334,19 @@ class CommonDataPullSocket(threading.Thread): """ # pylint: disable=too-many-branches - def __init__(self, name, codenames, port, default_x, default_y, timeouts, - check_activity, activity_timeout, init_timeouts=True, - handler_class=PullUDPHandler): + def __init__( + self, + name, + codenames, + port, + default_x, + default_y, + timeouts, + check_activity, + activity_timeout, + init_timeouts=True, + handler_class=PullUDPHandler, + ): """Initializes internal variables and data structure in the :data:`.DATA` module variable @@ -353,7 +377,7 @@ def __init__(self, name, codenames, port, default_x, default_y, timeouts, activity_timeout (float or int): The timespan in seconds which constitutes in-activity """ - CDPULLSLOG.info('Initialize with: %s', call_spec_string()) + CDPULLSLOG.info("Initialize with: %s", call_spec_string()) # Init thread super(CommonDataPullSocket, self).__init__() self.daemon = True @@ -362,14 +386,16 @@ def __init__(self, name, codenames, port, default_x, default_y, timeouts, # Check for existing servers on this port if port in DATA: - message = 'A UDP server already exists on port: {}'.format(port) + message = "A UDP server already exists on port: {}".format(port) CDPULLSLOG.error(message) raise ValueError(message) # Check and possibly convert timeout - if hasattr(timeouts, '__len__'): + if hasattr(timeouts, "__len__"): if len(timeouts) != len(codenames): - message = 'If a list of timeouts is supplied, it must have '\ - 'as many items as there are in codenames' + message = ( + "If a list of timeouts is supplied, it must have " + "as many items as there are in codenames" + ) CDPULLSLOG.error(message) raise ValueError(message) timeouts = list(timeouts) @@ -379,53 +405,55 @@ def __init__(self, name, codenames, port, default_x, default_y, timeouts, # Prepare DATA DATA[port] = { - 'codenames': list(codenames), - 'data': {}, - 'name': name, - 'activity': { - 'check_activity': check_activity, - 'activity_timeout': activity_timeout, - 'last_activity': time.time() - } + "codenames": list(codenames), + "data": {}, + "name": name, + "activity": { + "check_activity": check_activity, + "activity_timeout": activity_timeout, + "last_activity": time.time(), + }, } if init_timeouts: - DATA[port]['timeouts'] = {} + DATA[port]["timeouts"] = {} for name, timeout in zip(codenames, timeouts): # Check for duplicates if codenames.count(name) > 1: - message = 'Codenames must be unique; \'{}\' is present more '\ - 'than once'.format(name) + message = "Codenames must be unique; '{}' is present more " "than once".format( + name + ) CDPULLSLOG.error(message) raise ValueError(message) # Check for bad characters in the name for char in BAD_CHARS: if char in name: - message = 'The character \'{}\' is not allowed in the '\ - 'codenames'.format(char) + message = "The character '{}' is not allowed in the " "codenames".format( + char + ) CDPULLSLOG.error(message) raise ValueError(message) # Init the point - DATA[port]['data'][name] = (default_x, default_y) + DATA[port]["data"][name] = (default_x, default_y) if init_timeouts: - DATA[port]['timeouts'][name] = timeout + DATA[port]["timeouts"][name] = timeout # Setup server try: - self.server = SocketServer.UDPServer(('', port), handler_class) + self.server = SocketServer.UDPServer(("", port), handler_class) except socket.error as error: if error.errno == 98: # See custom exception message to understand this - CDPULLSLOG.error('Port \'%s\' still reserved', port) + CDPULLSLOG.error("Port '%s' still reserved", port) raise PortStillReserved() else: raise error - CDPULLSLOG.debug('Initialized') + CDPULLSLOG.debug("Initialized") def run(self): """Starts the UPD socket server""" - CDPULLSLOG.info('Run') + CDPULLSLOG.info("Run") self.server.serve_forever() - CDPULLSLOG.info('Run ended') + CDPULLSLOG.info("Run ended") def stop(self): """Stops the UDP server @@ -433,25 +461,27 @@ def stop(self): .. note:: Closing the server **and** deleting the socket server instance is necessary to free up the port for other usage """ - CDPULLSLOG.debug('Stop requested') + CDPULLSLOG.debug("Stop requested") self.server.shutdown() # Wait 0.1 sec to prevent the interpreter from destroying the # environment before we are done if this is the last thread time.sleep(0.1) # Delete the data, to allow forming another socket on this port - #print(DATA) + # print(DATA) del DATA[self.port] - #print(DATA) - CDPULLSLOG.info('Stopped') + # print(DATA) + CDPULLSLOG.info("Stopped") def poke(self): """Pokes the socket server to let it know that there is activity""" - if DATA[self.port]['activity']['check_activity']: - DATA[self.port]['activity']['last_activity'] = time.time() + if DATA[self.port]["activity"]["check_activity"]: + DATA[self.port]["activity"]["last_activity"] = time.time() -DPULLSLOG = logging.getLogger(__name__ + '.DataPullSocket') +DPULLSLOG = logging.getLogger(__name__ + ".DataPullSocket") DPULLSLOG.addHandler(logging.NullHandler()) + + class DataPullSocket(CommonDataPullSocket): """This class implements a UDP socket server for serving x, y type data. The UDP server uses the :class:`.PullUDPHandler` class to handle @@ -459,9 +489,18 @@ class DataPullSocket(CommonDataPullSocket): documented in the :meth:`.PullUDPHandler.handle()` method. """ - def __init__(self, name, codenames, port=9010, default_x=0.0, - default_y=0.0, timeouts=None, check_activity=True, - activity_timeout=900, poke_on_set=True): + def __init__( + self, + name, + codenames, + port=9010, + default_x=0.0, + default_y=0.0, + timeouts=None, + check_activity=True, + activity_timeout=900, + poke_on_set=True, + ): """Initializes internal variables and UPD server For parameter description of ``name``, ``codenames``, ``port``, @@ -472,19 +511,24 @@ def __init__(self, name, codenames, port=9010, default_x=0.0, poke_on_set (bool): Whether to poke the socket server when a point is set, to let it know there is activity """ - DPULLSLOG.info('Initialize with: %s', call_spec_string()) + DPULLSLOG.info("Initialize with: %s", call_spec_string()) # Run super init to initialize thread, check input and initialize data super(DataPullSocket, self).__init__( - name, codenames, port=port, default_x=default_x, - default_y=default_y, timeouts=timeouts, - check_activity=check_activity, activity_timeout=activity_timeout + name, + codenames, + port=port, + default_x=default_x, + default_y=default_y, + timeouts=timeouts, + check_activity=check_activity, + activity_timeout=activity_timeout, ) - DATA[port]['type'] = 'data' + DATA[port]["type"] = "data" # Init timestamps - DATA[port]['timestamps'] = {} + DATA[port]["timestamps"] = {} for name in codenames: - DATA[port]['timestamps'][name] = 0.0 - DPULLSLOG.debug('Initialized') + DATA[port]["timestamps"][name] = 0.0 + DPULLSLOG.debug("Initialized") # Init poke_on_set self.poke_on_set = poke_on_set @@ -501,18 +545,20 @@ def set_point(self, codename, point, timestamp=None): value is used to evaluate if the point is new enough if timeouts are set. """ - DATA[self.port]['data'][codename] = tuple(point) + DATA[self.port]["data"][codename] = tuple(point) if timestamp is None: timestamp = time.time() - DATA[self.port]['timestamps'][codename] = timestamp - DPULLSLOG.debug('Point %s for \'%s\' set', tuple(point), codename) + DATA[self.port]["timestamps"][codename] = timestamp + DPULLSLOG.debug("Point %s for '%s' set", tuple(point), codename) # Poke if required - if DATA[self.port]['activity']['check_activity'] and self.poke_on_set: + if DATA[self.port]["activity"]["check_activity"] and self.poke_on_set: self.poke() -DDPULLSLOG = logging.getLogger(__name__ + '.DateDataPullSocket') +DDPULLSLOG = logging.getLogger(__name__ + ".DateDataPullSocket") DDPULLSLOG.addHandler(logging.NullHandler()) + + class DateDataPullSocket(CommonDataPullSocket): """This class implements a UDP socket server for serving data as a function of time. The UDP server uses the :class:`.PullUDPHandler` class to handle @@ -521,9 +567,18 @@ class DateDataPullSocket(CommonDataPullSocket): """ - def __init__(self, name, codenames, port=9000, default_x=0.0, - default_y=0.0, timeouts=None, check_activity=True, - activity_timeout=900, poke_on_set=True): + def __init__( + self, + name, + codenames, + port=9000, + default_x=0.0, + default_y=0.0, + timeouts=None, + check_activity=True, + activity_timeout=900, + poke_on_set=True, + ): """Init internal variavles and UPD server For parameter description of ``name``, ``codenames``, ``port``, @@ -534,16 +589,21 @@ def __init__(self, name, codenames, port=9000, default_x=0.0, poke_on_set (bool): Whether to poke the socket server when a point is set, to let it know there is activity """ - DDPULLSLOG.info('Initialize with: %s', call_spec_string()) + DDPULLSLOG.info("Initialize with: %s", call_spec_string()) # Run super init to initialize thread, check input and initialize data super(DateDataPullSocket, self).__init__( - name, codenames, port=port, default_x=default_x, - default_y=default_y, timeouts=timeouts, - check_activity=check_activity, activity_timeout=activity_timeout + name, + codenames, + port=port, + default_x=default_x, + default_y=default_y, + timeouts=timeouts, + check_activity=check_activity, + activity_timeout=activity_timeout, ) # Set the type - DATA[port]['type'] = 'date' - DDPULLSLOG.debug('Initialized') + DATA[port]["type"] = "date" + DDPULLSLOG.debug("Initialized") # Init poke_on_set self.poke_on_set = poke_on_set @@ -556,7 +616,7 @@ def set_point_now(self, codename, value): value (float): y-value """ self.set_point(codename, (time.time(), value)) - DDPULLSLOG.debug('Added time to value and called set_point') + DDPULLSLOG.debug("Added time to value and called set_point") def set_point(self, codename, point): """Sets the current point for codename @@ -567,15 +627,17 @@ def set_point(self, codename, point): point (iterable): Current point as a list (or tuple) of 2 floats: [x, y] """ - DATA[self.port]['data'][codename] = tuple(point) - DDPULLSLOG.debug('Point %s for \'%s\' set', tuple(point), codename) + DATA[self.port]["data"][codename] = tuple(point) + DDPULLSLOG.debug("Point %s for '%s' set", tuple(point), codename) # Poke if required - if DATA[self.port]['activity']['check_activity'] and self.poke_on_set: + if DATA[self.port]["activity"]["check_activity"] and self.poke_on_set: self.poke() -PUSHUHLOG = logging.getLogger(__name__ + '.PushUDPHandler') +PUSHUHLOG = logging.getLogger(__name__ + ".PushUDPHandler") PUSHUHLOG.addHandler(logging.NullHandler()) + + class PushUDPHandler(SocketServer.BaseRequestHandler): """This class handles the UDP requests for the :class:`.DataPushSocket`""" @@ -606,75 +668,81 @@ def handle(self): * **commands** (*str*): Return a json encoded list of commands. The returns value is is prefixed with :data:`.PUSH_RET` and '#' so e.g. 'RET#actual_date' """ - request = self.request[0].decode('ascii') - PUSHUHLOG.debug('Request \'%s\'received', request) + request = self.request[0].decode("ascii") + PUSHUHLOG.debug("Request '%s'received", request) # pylint: disable=attribute-defined-outside-init self.port = self.server.server_address[1] sock = self.request[1] # Parse the request and call the appropriate helper methods - if request == 'name': - return_value = '{}#{}'.format(PUSH_RET, DATA[self.port]['name']) - elif request == 'commands': - commands = ['json_wn#', 'raw_wn#', 'name', 'status', 'commands'] - return_value = '{}#{}'.format(PUSH_RET, json.dumps(commands)) - elif request == 'status': - return_value = six.text_type(json.dumps({ - 'system_status': SYSTEM_STATUS.complete_status(), - 'socket_server_status': socket_server_status() - })) - elif request.count('#') != 1: - return_value = '{}#{}'.format(PUSH_ERROR, UNKNOWN_COMMAND) + if request == "name": + return_value = "{}#{}".format(PUSH_RET, DATA[self.port]["name"]) + elif request == "commands": + commands = ["json_wn#", "raw_wn#", "name", "status", "commands"] + return_value = "{}#{}".format(PUSH_RET, json.dumps(commands)) + elif request == "status": + return_value = six.text_type( + json.dumps( + { + "system_status": SYSTEM_STATUS.complete_status(), + "socket_server_status": socket_server_status(), + } + ) + ) + elif request.count("#") != 1: + return_value = "{}#{}".format(PUSH_ERROR, UNKNOWN_COMMAND) else: # Parse a command on the form command#data - command, data = request.split('#') + command, data = request.split("#") try: - if command == 'json_wn': + if command == "json_wn": return_value = self._json_with_names(data) - elif command == 'raw_wn': + elif command == "raw_wn": return_value = self._raw_with_names(data) else: - return_value = '{}#{}'.format(PUSH_ERROR, UNKNOWN_COMMAND) + return_value = "{}#{}".format(PUSH_ERROR, UNKNOWN_COMMAND) # Several of the helper methods will raise ValueError on wrong # input except ValueError as exception: - return_value = '{}#{}'.format(PUSH_ERROR, str(exception)) + return_value = "{}#{}".format(PUSH_ERROR, str(exception)) - PUSHUHLOG.debug('Send back: %s', return_value) - sock.sendto(return_value.encode('ascii'), self.client_address) + PUSHUHLOG.debug("Send back: %s", return_value) + sock.sendto(return_value.encode("ascii"), self.client_address) def _raw_with_names(self, data): """Adds raw data to the queue""" - PUSHUHLOG.debug('Parse raw with names: %s', data) + PUSHUHLOG.debug("Parse raw with names: %s", data) data_out = {} # Split in data parts e.g: 'codenam1:type:dat1,dat2'. NOTE if no data # is passed (data is ''), the split will return '', which will make # .split(':') fail - for part in data.split(';'): + for part in data.split(";"): # Split the part up try: - codename, data_type, data_string = part.split(':') + codename, data_type, data_string = part.split(":") except ValueError: - message = 'The data part \'{}\' did not match the expected '\ - 'format of 3 parts divided by \':\''.format(part) + message = ( + "The data part '{}' did not match the expected " + "format of 3 parts divided by ':'".format(part) + ) PUSHUHLOG.error(message) raise ValueError(message) # Parse the type try: type_function = TYPE_FROM_STRING[data_type] except KeyError: - message = 'The data type \'{}\' is unknown. Only {} are '\ - 'allowed'.format(data_type, TYPE_FROM_STRING.keys()) + message = "The data type '{}' is unknown. Only {} are " "allowed".format( + data_type, TYPE_FROM_STRING.keys() + ) PUSHUHLOG.error(message) raise ValueError(message) # Convert the data try: - data_converted = list( - [type_function(dat) for dat in data_string.split(',')] - ) + data_converted = list([type_function(dat) for dat in data_string.split(",")]) except ValueError as exception: - message = 'Unable to convert values to \'{}\'. Error is: {}'\ - .format(data_type, str(exception)) + message = "Unable to convert values to '{}'. Error is: {}".format( + data_type, str(exception) + ) PUSHUHLOG.error(message) raise ValueError(message) # Remove list for length 1 data @@ -688,18 +756,19 @@ def _raw_with_names(self, data): def _json_with_names(self, data): """Adds json encoded data to the data queue""" - PUSHUHLOG.debug('Parse json with names: %s', data) + PUSHUHLOG.debug("Parse json with names: %s", data) try: data_dict = json.loads(data) except ValueError: - message = 'The string \'{}\' could not be decoded as JSON'.\ - format(data) + message = "The string '{}' could not be decoded as JSON".format(data) PUSHUHLOG.error(message) raise ValueError(message) # Check type (normally not done, but we want to be sure) if not isinstance(data_dict, dict): - message = 'The object \'{}\' returned after decoding the JSON '\ - 'string is not a dict'.format(data_dict) + message = ( + "The object '{}' returned after decoding the JSON " + "string is not a dict".format(data_dict) + ) PUSHUHLOG.error(message) raise ValueError(message) @@ -716,42 +785,42 @@ def _set_data(self, data): Returns: (str): The request return value """ - PUSHUHLOG.debug('Set data: %s', data) + PUSHUHLOG.debug("Set data: %s", data) timestamp = time.time() - DATA[self.port]['last'] = data - DATA[self.port]['last_time'] = timestamp - DATA[self.port]['updated'].update(data) - DATA[self.port]['updated_time'] = timestamp + DATA[self.port]["last"] = data + DATA[self.port]["last_time"] = timestamp + DATA[self.port]["updated"].update(data) + DATA[self.port]["updated_time"] = timestamp # Put the data in queue for actions that require that - if DATA[self.port]['action'] in ['enqueue', 'callback_async']: - DATA[self.port]['queue'].put(data) + if DATA[self.port]["action"] in ["enqueue", "callback_async"]: + DATA[self.port]["queue"].put(data) # Execute the callback for actions that require that. Notice, the # different branches determines which output format gets send back # ACKnowledge or RETurn (value on callback) - if DATA[self.port]['action'] == 'callback_direct': + if DATA[self.port]["action"] == "callback_direct": try: # Call the callback - return_value = DATA[self.port]['callback'](data) + return_value = DATA[self.port]["callback"](data) # Format the return value depending on return_format - if DATA[self.port]['return_format'] == 'json': + if DATA[self.port]["return_format"] == "json": out = self._format_return_json(return_value) - elif DATA[self.port]['return_format'] == 'raw': + elif DATA[self.port]["return_format"] == "raw": out = self._format_return_raw(return_value) - elif DATA[self.port]['return_format'] == 'string': + elif DATA[self.port]["return_format"] == "string": out = self._format_return_string(return_value) else: # The return format values should be checked on # instantiation - message = 'Bad return format. REPORT AS BUG.' - out = '{}#{}'.format(PUSH_ERROR, message) + message = "Bad return format. REPORT AS BUG." + out = "{}#{}".format(PUSH_ERROR, message) # pylint: disable=broad-except except Exception as exception: # Catch anything it might raise - out = '{}#{}'.format(PUSH_EXCEP, str(exception)) + out = "{}#{}".format(PUSH_EXCEP, str(exception)) else: # Return the ACK message with the interpreted data - out = '{}#{}'.format(PUSH_ACK, data) + out = "{}#{}".format(PUSH_ACK, data) return out @@ -766,11 +835,11 @@ def _format_return_json(value): Returns: (str): The request return value """ - PUSHUHLOG.debug('Format return json: %s', value) + PUSHUHLOG.debug("Format return json: %s", value) try: - out = '{}#{}'.format(PUSH_RET, json.dumps(value)) + out = "{}#{}".format(PUSH_RET, json.dumps(value)) except TypeError as exception: - out = '{}#{}'.format(PUSH_EXCEP, str(exception)) + out = "{}#{}".format(PUSH_EXCEP, str(exception)) return out @staticmethod @@ -784,12 +853,12 @@ def _format_return_string(value): Returns: (str): The request return value """ - PUSHUHLOG.debug('Format return string: %s', value) + PUSHUHLOG.debug("Format return string: %s", value) try: - out = '{}#{}'.format(PUSH_RET, str(value)) + out = "{}#{}".format(PUSH_RET, str(value)) except Exception as exception: # pylint: disable=broad-except # Have no idea, maybe attribute error - out = '{}#{}'.format(PUSH_EXCEP, str(exception)) + out = "{}#{}".format(PUSH_EXCEP, str(exception)) return out def _format_return_raw(self, argument): @@ -819,22 +888,21 @@ def _format_return_raw(self, argument): Returns: str: The request return value """ - PUSHUHLOG.debug('Format return raw: %s', argument) + PUSHUHLOG.debug("Format return raw: %s", argument) try: if argument is None: - out = '{}#{}'.format(PUSH_RET, 'None') + out = "{}#{}".format(PUSH_RET, "None") elif isinstance(argument, dict): out = self._format_return_raw_dict(argument) elif isinstance(argument, list): out = self._format_return_raw_list(argument) else: - message = 'Return value must be a dict or list with return '\ - 'format \'raw\'' + message = "Return value must be a dict or list with return " "format 'raw'" raise ValueError(message) # pylint: disable=broad-except except Exception as exception: - message = 'Raw conversion failed with message' - out = '{}#{}:{}'.format(PUSH_EXCEP, message, str(exception)) + message = "Raw conversion failed with message" + out = "{}#{}:{}".format(PUSH_EXCEP, message, str(exception)) return out @@ -849,7 +917,7 @@ def _format_return_raw_dict(argument): Returns: str: The dict raw serialization string """ - PUSHUHLOG.debug('Format return raw dict: %s', argument) + PUSHUHLOG.debug("Format return raw dict: %s", argument) # Items holds the strings for each key, value pair e.g. # 'codename1:type:data' items = [] @@ -863,36 +931,37 @@ def _format_return_raw_dict(argument): element_type = types[0] element_type_name = six.text_type(element_type.__name__) if types != len(types) * [element_type]: - message = 'With return format raw, value in list must have same type' + message = "With return format raw, value in list must have same type" raise ValueError(message) - value_string = ','.join([str(element) for element in value]) + value_string = ",".join([str(element) for element in value]) elif isinstance(value, list) and len(value) == 0: # An empty list gets turned into type='', value='' - element_type_name = '' - value_string = '' + element_type_name = "" + value_string = "" else: # Single element conversion element_type_name = six.text_type(type(value).__name__) - value_string = '{}'.format(str(value)) + value_string = "{}".format(str(value)) # We always call it str - if sys.version_info[0] == 2 and element_type_name == 'unicode': - element_type_name = 'str' + if sys.version_info[0] == 2 and element_type_name == "unicode": + element_type_name = "str" # Check that the element type makes sense for raw conversion - if element_type_name not in ['int', 'float', 'bool', 'str']: - message = 'With return format raw, the item type can '\ - 'only be one of \'int\', \'float\', \'bool\' and '\ - '\'str\'. Object: \'{}\' is of type: {}'.format( - value, element_type_name) + if element_type_name not in ["int", "float", "bool", "str"]: + message = ( + "With return format raw, the item type can " + "only be one of 'int', 'float', 'bool' and " + "'str'. Object: '{}' is of type: {}".format(value, element_type_name) + ) raise TypeError(message) # pylint: disable=maybe-no-member - item_str = '{}:{}:{}'.format(str(key), element_type_name, value_string) + item_str = "{}:{}:{}".format(str(key), element_type_name, value_string) items.append(item_str) - return '{}#{}'.format(PUSH_RET, ';'.join(items)) + return "{}#{}".format(PUSH_RET, ";".join(items)) @staticmethod def _format_return_raw_list(argument): @@ -905,48 +974,61 @@ def _format_return_raw_list(argument): Returns str: The raw serialized string """ - PUSHUHLOG.debug('Format return raw list: %s', argument) + PUSHUHLOG.debug("Format return raw list: %s", argument) types = [] # List of strings with points as x,y items = [] for item in argument: if not isinstance(item, list): - message = 'With return format raw on a list, the elements '\ - 'themselves be lists' + message = ( + "With return format raw on a list, the elements " "themselves be lists" + ) raise ValueError(message) types += [type(element) for element in item] converted = [str(element) for element in item] - items.append(','.join(converted)) + items.append(",".join(converted)) # Check that they are all of same type element_type = types[0] if types != len(types) * [element_type]: - message = 'With return format raw on a list of lists, all values '\ - ' in list must have same type. Types are: {}'.format(types) + message = ( + "With return format raw on a list of lists, all values " + " in list must have same type. Types are: {}".format(types) + ) raise ValueError(message) # Check that the element type makes sense for raw conversion if element_type not in [int, float, bool, str]: - message = 'With return format raw, the item type can only be one '\ - 'of \'int\', \'float\', \'bool\' and \'str\'. The type is: {}'\ - .format(element_type) + message = ( + "With return format raw, the item type can only be one " + "of 'int', 'float', 'bool' and 'str'. The type is: {}".format(element_type) + ) raise TypeError(message) - return '{}#{}:{}'.format(PUSH_RET, element_type.__name__, - '&'.join(items)) + return "{}#{}:{}".format(PUSH_RET, element_type.__name__, "&".join(items)) -DPUSHSLOG = logging.getLogger(__name__ + '.DataPushSocket') +DPUSHSLOG = logging.getLogger(__name__ + ".DataPushSocket") DPUSHSLOG.addHandler(logging.NullHandler()) + + class DataPushSocket(threading.Thread): """This class implements a data push socket and provides options for enqueuing, calling back or doing nothing on reciept of data """ # pylint: disable=too-many-branches - def __init__(self, name, port=8500, action='store_last', queue=None, - callback=None, return_format='json', check_activity=False, - activity_timeout=900): + def __init__( + self, + name, + port=8500, + action="store_last", + queue=None, + callback=None, + return_format="json", + check_activity=False, + activity_timeout=900, + ): """Initializes the DataPushSocket Arguments: @@ -1005,7 +1087,7 @@ def __init__(self, name, port=8500, action='store_last', queue=None, them """ - DPUSHSLOG.info('Initialize with: %s', call_spec_string()) + DPUSHSLOG.info("Initialize with: %s", call_spec_string()) # Init thread super(DataPushSocket, self).__init__() self.daemon = True @@ -1016,57 +1098,65 @@ def __init__(self, name, port=8500, action='store_last', queue=None, self.action = action # Raise exception on invalid argument combinations - if queue is not None and action != 'enqueue': - message = 'The \'queue\' argument can only be used when the '\ - 'action is \'enqueue\'' + if queue is not None and action != "enqueue": + message = "The 'queue' argument can only be used when the " "action is 'enqueue'" raise ValueError(message) - if callback is not None and action not in\ - ['callback_async', 'callback_direct']: - message = 'The \'callback\' argument can only be used when the '\ - 'action is \'callback_async\' or \'callback_direct\'' + if callback is not None and action not in ["callback_async", "callback_direct"]: + message = ( + "The 'callback' argument can only be used when the " + "action is 'callback_async' or 'callback_direct'" + ) raise ValueError(message) - if action in ['callback_async', 'callback_direct']: + if action in ["callback_async", "callback_direct"]: if not callable(callback): - message = 'Value for callback: \'{}\' is not callable'\ - .format(callback) + message = "Value for callback: '{}' is not callable".format(callback) raise ValueError(message) - if return_format not in ['json', 'raw', 'string']: - message = 'The \'return_format\' argument may only be one of the '\ - '\'json\', \'raw\' or \'string\' values' + if return_format not in ["json", "raw", "string"]: + message = ( + "The 'return_format' argument may only be one of the " + "'json', 'raw' or 'string' values" + ) raise ValueError(message) # Set callback and queue depending on action self._callback_thread = None content = { - 'action': action, 'last': None, 'type': 'push', 'updated': {}, - 'last_time': None, 'updated_time': None, 'name': name, - 'activity': { - 'check_activity': check_activity, - 'activity_timeout': activity_timeout, - 'last_activity': time.time(), - } + "action": action, + "last": None, + "type": "push", + "updated": {}, + "last_time": None, + "updated_time": None, + "name": name, + "activity": { + "check_activity": check_activity, + "activity_timeout": activity_timeout, + "last_activity": time.time(), + }, } - if action == 'store_last': + if action == "store_last": pass - elif action == 'enqueue': + elif action == "enqueue": if queue is None: - content['queue'] = Queue.Queue() + content["queue"] = Queue.Queue() else: - content['queue'] = queue - elif action == 'callback_async': - content['queue'] = Queue.Queue() - self._callback_thread = CallBackThread(content['queue'], callback) - elif action == 'callback_direct': - content['callback'] = callback - content['return_format'] = return_format + content["queue"] = queue + elif action == "callback_async": + content["queue"] = Queue.Queue() + self._callback_thread = CallBackThread(content["queue"], callback) + elif action == "callback_direct": + content["callback"] = callback + content["return_format"] = return_format else: - message = 'Unknown action \'{}\'. Must be one of: [\'store_last\', \'enqueue\', '\ - '\'callback_async\', \'callback_direct\']'.format(action) + message = ( + "Unknown action '{}'. Must be one of: ['store_last', 'enqueue', " + "'callback_async', 'callback_direct']".format(action) + ) raise ValueError(message) # Setup server try: - self.server = SocketServer.UDPServer(('', port), PushUDPHandler) + self.server = SocketServer.UDPServer(("", port), PushUDPHandler) except socket.error as error: if error.errno == 98: # See custom exception message to understand this @@ -1077,15 +1167,15 @@ def __init__(self, name, port=8500, action='store_last', queue=None, # Only put this socket in the DATA variable, if we succeed in # initializing it DATA[port] = content - DPUSHSLOG.debug('DPS: Initialized') + DPUSHSLOG.debug("DPS: Initialized") def run(self): """Starts the UPD socket server""" - DPUSHSLOG.info('DPS: Start') + DPUSHSLOG.info("DPS: Start") if self._callback_thread is not None: self._callback_thread.start() self.server.serve_forever() - DPUSHSLOG.info('DPS: Run ended') + DPUSHSLOG.info("DPS: Run ended") def stop(self): """Stops the UDP socket server @@ -1094,7 +1184,7 @@ def stop(self): :py:class:`SocketServer.UDPServer` socket instance is necessary to free up the port for other usage """ - DPUSHSLOG.debug('DPS: Stop requested') + DPUSHSLOG.debug("DPS: Stop requested") if self._callback_thread is not None: self._callback_thread.stop() time.sleep(0.1) @@ -1104,15 +1194,15 @@ def stop(self): time.sleep(0.1) # Delete the data, to allow forming another socket on this port del DATA[self.port] - DPUSHSLOG.info('DPS: Stopped') + DPUSHSLOG.info("DPS: Stopped") @property def queue(self): """Gets the queue, returns None if ``action`` is ``'store_last'`` or ``'callback_direct'`` """ - DPUSHSLOG.debug('DPS: queue property used') - return DATA[self.port].get('queue') + DPUSHSLOG.debug("DPS: queue property used") + return DATA[self.port].get("queue") @property def last(self): @@ -1124,12 +1214,12 @@ def last(self): timestamp of that reception. Returns ``(None, None)`` if no data has been recieved. """ - DPUSHSLOG.debug('DPS: last property used') - if DATA[self.port]['last'] is None: - last = DATA[self.port]['last'] + DPUSHSLOG.debug("DPS: last property used") + if DATA[self.port]["last"] is None: + last = DATA[self.port]["last"] else: - last = DATA[self.port]['last'].copy() - return DATA[self.port]['last_time'], last + last = DATA[self.port]["last"].copy() + return DATA[self.port]["last_time"], last @property def updated(self): @@ -1142,32 +1232,33 @@ def updated(self): ``updated_data_time`` is the Unix timestamp of that reception. Returns ``(None, {})`` if no data has been recieved. """ - DPUSHSLOG.debug('DPS: updated property used') - return (DATA[self.port]['updated_time'], - DATA[self.port]['updated'].copy()) + DPUSHSLOG.debug("DPS: updated property used") + return (DATA[self.port]["updated_time"], DATA[self.port]["updated"].copy()) def set_last_to_none(self): """Sets the last data point and last data point time to None""" - DPUSHSLOG.debug('DPS: Set last to none') - DATA[self.port]['last'] = None - DATA[self.port]['last_time'] = None + DPUSHSLOG.debug("DPS: Set last to none") + DATA[self.port]["last"] = None + DATA[self.port]["last_time"] = None def clear_updated(self): """Clears the total updated data and set the time of last update to None """ - DPUSHSLOG.debug('DPS: Clear updated') - DATA[self.port]['updated'].clear() - DATA[self.port]['updated_time'] = None + DPUSHSLOG.debug("DPS: Clear updated") + DATA[self.port]["updated"].clear() + DATA[self.port]["updated_time"] = None def poke(self): """Pokes the socket server to let it know that there is activity""" - if DATA[self.port]['activity']['check_activity']: - DATA[self.port]['activity']['last_activity'] = time.time() + if DATA[self.port]["activity"]["check_activity"]: + DATA[self.port]["activity"]["last_activity"] = time.time() -CBTLOG = logging.getLogger(__name__ + '.CallBackThread') +CBTLOG = logging.getLogger(__name__ + ".CallBackThread") CBTLOG.addHandler(logging.NullHandler()) + + class CallBackThread(threading.Thread): """Class to handle the calling back for a DataReceiveSocket""" @@ -1180,7 +1271,7 @@ def __init__(self, queue, callback): callback (callable): The callable that will be called when there are items in the queue """ - CBTLOG.info('Initialize with: %s', call_spec_string()) + CBTLOG.info("Initialize with: %s", call_spec_string()) # Initialize the thread super(CallBackThread, self).__init__() self.daemon = True @@ -1189,11 +1280,11 @@ def __init__(self, queue, callback): # Set variables self.queue = queue self.callback = callback - CBTLOG.debug('CBT: Initialized') + CBTLOG.debug("CBT: Initialized") def run(self): """Starts the calling back""" - CBTLOG.info('CBT: Run') + CBTLOG.info("CBT: Run") while not self._stop: # get a item from the queue and call back try: @@ -1201,37 +1292,42 @@ def run(self): # can be shut down item = self.queue.get(True, 1) self.callback(item) - CBTLOG.debug('CBT: Callback called with arg: %s', item) + CBTLOG.debug("CBT: Callback called with arg: %s", item) except Queue.Empty: pass - CBTLOG.info('CBT: Run stopped') + CBTLOG.info("CBT: Run stopped") def stop(self): """Stops the calling back""" - CBTLOG.debug('CBT: Stop requested') + CBTLOG.debug("CBT: Stop requested") self._stop = True - CBTLOG.info('CBT: Stopped') + CBTLOG.info("CBT: Stopped") class PortStillReserved(Exception): """Custom exception to explain socket server port still reserved even after closing the port """ + def __init__(self): - message = 'Even when a socket server has been requested '\ - 'closed, the socket module will still keep it reserved for some '\ - 'time (maybe up to a minute), to allow for clearing up lower '\ - 'level networking components. If it is required to open and '\ - 'close socket servers fast on the same ports, this behavior can '\ - 'be changed by invoking:'\ - '\n import SocketServer'\ - '\n SocketServer.UDPServer.allow_reuse_address = True'\ - '\nbefore instantiation.' + message = ( + "Even when a socket server has been requested " + "closed, the socket module will still keep it reserved for some " + "time (maybe up to a minute), to allow for clearing up lower " + "level networking components. If it is required to open and " + "close socket servers fast on the same ports, this behavior can " + "be changed by invoking:" + "\n import SocketServer" + "\n SocketServer.UDPServer.allow_reuse_address = True" + "\nbefore instantiation." + ) super(PortStillReserved, self).__init__(message) -LSLOG = logging.getLogger(__name__ + '.LiveSocket') +LSLOG = logging.getLogger(__name__ + ".LiveSocket") LSLOG.addHandler(logging.NullHandler()) + + class LiveSocket(object): """This class implements a Live Socket @@ -1245,8 +1341,14 @@ class LiveSocket(object): """ - def __init__(self, name, codenames, live_server=None, no_internal_data_pull_socket=False, - internal_data_pull_socket_port=8000): + def __init__( + self, + name, + codenames, + live_server=None, + no_internal_data_pull_socket=False, + internal_data_pull_socket_port=8000, + ): """Intialize the LiveSocket Args: @@ -1269,10 +1371,13 @@ def __init__(self, name, codenames, live_server=None, no_internal_data_pull_sock as before. """ - LSLOG.info('Init') + LSLOG.info("Init") self.codename_set = set(codenames) if live_server is None: - live_server = (SETTINGS.common_liveserver_host, SETTINGS.common_liveserver_port) + live_server = ( + SETTINGS.common_liveserver_host, + SETTINGS.common_liveserver_port, + ) liveserver_hostname, self.liveserver_port = live_server # Translate live server hostname to IP-address to avoid DNS lookup on every # transmission @@ -1320,15 +1425,15 @@ def set_batch(self, data): # Set the values on the DataPullSocket for key, value in data.items(): if key not in self.codename_set: - message = 'The codename: \'{}\' is not registered'.format(key) + message = "The codename: '{}' is not registered".format(key) LSLOG.error(message) raise RuntimeError(message) if self._internal_pull_socket: self._internal_pull_socket.set_point(key, value) # Send the data to the live socket proxy - dump = json.dumps({'host': self.hostname, 'data': data}) - self.socket.sendto(dump.encode('utf-8'), (self.liveserver_ip, self.liveserver_port)) + dump = json.dumps({"host": self.hostname, "data": data}) + self.socket.sendto(dump.encode("utf-8"), (self.liveserver_ip, self.liveserver_port)) def set_batch_now(self, data): """Set a batch of point now @@ -1354,7 +1459,7 @@ def set_point_now(self, codename, value): value (float, int, bool or str): value """ self.set_batch({codename: (time.time(), value)}) - LSLOG.debug('Added time to value and called set_point') + LSLOG.debug("Added time to value and called set_point") def set_point(self, codename, point): """Sets the current point for codename @@ -1366,7 +1471,7 @@ def set_point(self, codename, point): the first must be a float, the second can be float, int, bool or str """ self.set_batch({codename: point}) - LSLOG.debug('Point %s for \'%s\' set', tuple(point), codename) + LSLOG.debug("Point %s for '%s' set", tuple(point), codename) def reset(self, codenames): """Send the reset signal for codenames @@ -1374,25 +1479,25 @@ def reset(self, codenames): Args: codenames (list): List of codenames """ - self.set_batch({codename: 'RESET' for codename in codenames}) + self.set_batch({codename: "RESET" for codename in codenames}) ### Module variables #: The list of characters that are not allowed in code names -BAD_CHARS = ['#', ',', ';', ':', '&'] +BAD_CHARS = ["#", ",", ";", ":", "&"] #: The string returned if an unknown command is sent to the socket -UNKNOWN_COMMAND = 'UNKNOWN_COMMMAND' +UNKNOWN_COMMAND = "UNKNOWN_COMMMAND" #: The string used to indicate old or obsoleted data -OLD_DATA = 'OLD_DATA' +OLD_DATA = "OLD_DATA" #: The answer prefix used when a push failed -PUSH_ERROR = 'ERROR' +PUSH_ERROR = "ERROR" #: The answer prefix used when a push succeds -PUSH_ACK = 'ACK' +PUSH_ACK = "ACK" #: The answer prefix for when a callback or callback value formatting produces #: an exception -PUSH_EXCEP = 'EXCEP' +PUSH_EXCEP = "EXCEP" #: The answer prefix for a callback return value -PUSH_RET = 'RET' +PUSH_RET = "RET" #:The variable used to contain all the data. #: #:The format of the DATA variable is the following. The DATA variable is a @@ -1445,8 +1550,7 @@ def reset(self, codenames): #: DATA = {} #: The dict that transforms strings to convertion functions -TYPE_FROM_STRING = {'int': int, 'float': float, 'str': str, - 'bool': bool_translate} +TYPE_FROM_STRING = {"int": int, "float": float, "str": str, "bool": bool_translate} def run_module(): @@ -1455,35 +1559,35 @@ def run_module(): def push_callback(incoming): """Calback function for the push socket""" - print('Push socket got:', incoming) + print("Push socket got:", incoming) push_socket = DataPushSocket( - 'module_test_push_socket', action='callback_direct', callback=push_callback + "module_test_push_socket", action="callback_direct", callback=push_callback ) push_socket.start() print('Started DataPushSocket on port 8500 with name "module_test_push_socket"') live_socket = LiveSocket( - 'dummy_live_socket', - codenames=('dummy_sine_one', 'dummy_sine_two'), + "dummy_live_socket", + codenames=("dummy_sine_one", "dummy_sine_two"), no_internal_data_pull_socket=True, ) live_socket.start() - print('Started live_socket. Using hostname {}'.format(live_socket.hostname)) + print("Started live_socket. Using hostname {}".format(live_socket.hostname)) try: while True: - print('Send new points live socket') - live_socket.set_point_now('dummy_sine_one', math.sin(time.time())) - live_socket.set_point_now('dummy_sine_two', math.sin(time.time() + math.pi)) + print("Send new points live socket") + live_socket.set_point_now("dummy_sine_one", math.sin(time.time())) + live_socket.set_point_now("dummy_sine_two", math.sin(time.time() + math.pi)) time.sleep(1) except KeyboardInterrupt: push_socket.stop() print('Stopped DataPushSocket on port 8500 with name "module_test_push_socket"') live_socket.stop() - print('Stopped LiveSocket') + print("Stopped LiveSocket") time.sleep(2) -if __name__ == '__main__': +if __name__ == "__main__": run_module() diff --git a/PyExpLabSys/common/supported_versions.py b/PyExpLabSys/common/supported_versions.py index e59dddf4..11ded239 100644 --- a/PyExpLabSys/common/supported_versions.py +++ b/PyExpLabSys/common/supported_versions.py @@ -5,7 +5,7 @@ import sys import warnings -DOCRUN = os.environ.get('READTHEDOCS') == 'True' or 'sphinx' in sys.modules +DOCRUN = os.environ.get("READTHEDOCS") == "True" or "sphinx" in sys.modules # We support 2.7 and above (which will never happen) or 3.3 and above PY2_MIN_VERSION = (2, 7) @@ -18,20 +18,22 @@ PY2_OR_3_CHECK = PY2_CHECK or PY3_CHECK # Warnings texts -WARNING2OR3 = ('\n' - '========================================================================\n' - '# The module: \'{filepath}\'\n' - '# only supports Python {0}.{1} or above in the Python {0} series.\n' - '# Your milages may vary!!!\n' - '========================================================================\n' +WARNING2OR3 = ( + "\n" + "========================================================================\n" + "# The module: '{filepath}'\n" + "# only supports Python {0}.{1} or above in the Python {0} series.\n" + "# Your milages may vary!!!\n" + "========================================================================\n" ) -WARNING2AND3 = ('\n' - '========================================================================\n' - '# The module: \'{filepath}\'\n' - '# only supports Python {0}.{1} or above in the Python {0} series\n' - '# OR Pythons {2}.{3} or above in the Python {2} series.\n' - '# Your milages may vary!!!\n' - '========================================================================\n' +WARNING2AND3 = ( + "\n" + "========================================================================\n" + "# The module: '{filepath}'\n" + "# only supports Python {0}.{1} or above in the Python {0} series\n" + "# OR Pythons {2}.{3} or above in the Python {2} series.\n" + "# Your milages may vary!!!\n" + "========================================================================\n" ) @@ -62,14 +64,16 @@ def python2_and_3(filepath): if PY2_OR_3_CHECK: return if not DOCRUN: - warnings.warn(WARNING2AND3.format(*PY2_MIN_VERSION + PY3_MIN_VERSION, filepath=filepath)) + warnings.warn( + WARNING2AND3.format(*PY2_MIN_VERSION + PY3_MIN_VERSION, filepath=filepath) + ) # Mark this file as being Python 2 and 3 compatible python2_and_3(__file__) -if __name__ == '__main__': +if __name__ == "__main__": python2_only(__file__) python3_only(__file__) python2_and_3(__file__) diff --git a/PyExpLabSys/common/system_status.py b/PyExpLabSys/common/system_status.py index fe41935a..b5756b9e 100644 --- a/PyExpLabSys/common/system_status.py +++ b/PyExpLabSys/common/system_status.py @@ -10,6 +10,7 @@ import sys import socket import codecs + try: import fcntl except ImportError: @@ -17,6 +18,7 @@ import struct import threading import subprocess + try: import resource except ImportError: @@ -25,40 +27,40 @@ # Source: http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/ RPI_REVISIONS = { - '0002': 'Model B 256MB Revision 1.0', - '0003': 'Model B 256MB Revision 1.0 + ECN0001 (no fuses, D14 removed)', - '0004': 'Model B 256MB Revision 2.0 Mounting holes', - '0005': 'Model B 256MB Revision 2.0 Mounting holes', - '0006': 'Model B 256MB Revision 2.0 Mounting holes', - '0007': 'Model A 256MB Mounting holes', - '0008': 'Model A 256MB Mounting holes', - '0009': 'Model A 256MB Mounting holes', - '000d': 'Model B 512MB Revision 2.0 Mounting holes', - '000e': 'Model B 512MB Revision 2.0 Mounting holes', - '000f': 'Model B 512MB Revision 2.0 Mounting holes', - '0010': 'Model B+ 512MB', - '0013': 'Model B+ 512MB', - '900032': 'Model B+ 512MB', - '0011': 'Compute Module', - '0012': 'Model A+ 256MB', - 'a01041': 'Pi 2 Model B v1.1 1GB', - 'a21041': 'Pi 2 Model B v1.1 1GB (Embest, China)', - 'a22042': 'Pi 2 Model B v1.2 1GB', - '900092': 'Pi Zero v1.2 512MB', - '900093': 'Pi Zero v1.3 512MB', - '9000C1': 'Pi Zero W rev 1.1 512MB', - 'a02082': 'Pi 3 Model B 1GB rev 1.2', - 'a020d3': 'Pi 3 Model B+ 1GB rev 1.3', - 'a03111': 'Pi 4 1GB rev 1.1', - 'b03111': 'Pi 4 2GB rev 1.1', - 'b03112': 'Pi 4 2GB rev 1.2', - 'b03114': 'Pi 4 2GB rev 1.2', - 'c03111': 'Pi 4 4GB rev 1.1', - 'c03112': 'Pi 4 4GB rev 1.2', - 'c03114': 'Pi 4 4GB rev 1.4', - 'd03114': 'Pi 4 8GB rev 1.4', - 'c03130': 'Pi 400 4GB rev 1.0', - '902120': 'Pi Zero 2 W 1GB rev 1.0', + "0002": "Model B 256MB Revision 1.0", + "0003": "Model B 256MB Revision 1.0 + ECN0001 (no fuses, D14 removed)", + "0004": "Model B 256MB Revision 2.0 Mounting holes", + "0005": "Model B 256MB Revision 2.0 Mounting holes", + "0006": "Model B 256MB Revision 2.0 Mounting holes", + "0007": "Model A 256MB Mounting holes", + "0008": "Model A 256MB Mounting holes", + "0009": "Model A 256MB Mounting holes", + "000d": "Model B 512MB Revision 2.0 Mounting holes", + "000e": "Model B 512MB Revision 2.0 Mounting holes", + "000f": "Model B 512MB Revision 2.0 Mounting holes", + "0010": "Model B+ 512MB", + "0013": "Model B+ 512MB", + "900032": "Model B+ 512MB", + "0011": "Compute Module", + "0012": "Model A+ 256MB", + "a01041": "Pi 2 Model B v1.1 1GB", + "a21041": "Pi 2 Model B v1.1 1GB (Embest, China)", + "a22042": "Pi 2 Model B v1.2 1GB", + "900092": "Pi Zero v1.2 512MB", + "900093": "Pi Zero v1.3 512MB", + "9000C1": "Pi Zero W rev 1.1 512MB", + "a02082": "Pi 3 Model B 1GB rev 1.2", + "a020d3": "Pi 3 Model B+ 1GB rev 1.3", + "a03111": "Pi 4 1GB rev 1.1", + "b03111": "Pi 4 2GB rev 1.1", + "b03112": "Pi 4 2GB rev 1.2", + "b03114": "Pi 4 2GB rev 1.2", + "c03111": "Pi 4 4GB rev 1.1", + "c03112": "Pi 4 4GB rev 1.2", + "c03114": "Pi 4 4GB rev 1.4", + "d03114": "Pi 4 8GB rev 1.4", + "c03130": "Pi 400 4GB rev 1.0", + "902120": "Pi Zero 2 W 1GB rev 1.0", } # Temperature regular expression RPI_TEMP_RE = re.compile(r"temp=([0-9\.]*)'C") @@ -66,10 +68,12 @@ def works_on(platform): """Return a decorator that attaches a _works_on (platform) attribute to methods""" + def decorator(function): """Decorate a method with a _works_on attribute""" function._works_on = platform # pylint: disable=protected-access return function + return decorator @@ -84,10 +88,10 @@ def __init__(self, machinename=None): socket.gethostname() """ # Form the list of which items to measure on different platforms - if 'linux' in sys.platform: - platforms = {'all', 'linux', 'linux2'} + if "linux" in sys.platform: + platforms = {"all", "linux", "linux2"} else: - platforms = {'all', sys.platform} + platforms = {"all", sys.platform} # Set the machine name (as used to find purpose) if machinename is None: @@ -104,7 +108,7 @@ def __init__(self, machinename=None): for attribute_name in dir(self): method = getattr(self, attribute_name) # pylint: disable=W0212 - if hasattr(method, '_works_on') and method._works_on in platforms: + if hasattr(method, "_works_on") and method._works_on in platforms: self.methods_on_this_platform.append(method) def complete_status(self): @@ -113,7 +117,7 @@ def complete_status(self): # All platforms @staticmethod - @works_on('all') + @works_on("all") def last_git_fetch_unixtime(): """Returns the unix timestamp and author time zone offset in seconds of the last git commit @@ -122,8 +126,7 @@ def last_git_fetch_unixtime(): # .git and a FETCH_HEAD in a hopefullt crossplatform manner, result: # /home/pi/PyExpLabSys/PyExpLabSys/common/../../.git/FETCH_HEAD fetch_head_file = os.path.join( - os.path.dirname(__file__), - *[os.path.pardir] * 2 + ['.git', 'FETCH_HEAD'] + os.path.dirname(__file__), *[os.path.pardir] * 2 + [".git", "FETCH_HEAD"] ) # Check for last change if os.access(fetch_head_file, os.F_OK): @@ -131,154 +134,151 @@ def last_git_fetch_unixtime(): else: return None - @staticmethod - @works_on('all') + @works_on("all") def number_of_python_threads(): """Returns the number of threads in Python""" return threading.activeCount() @staticmethod - @works_on('all') + @works_on("all") def python_version(): """Returns the Python version""" - return '{}.{}.{}'.format(*sys.version_info) + return "{}.{}.{}".format(*sys.version_info) # Linux only @staticmethod - @works_on('linux2') + @works_on("linux2") def uptime(): """Returns the system uptime""" - sysfile = '/proc/uptime' + sysfile = "/proc/uptime" if os.access(sysfile, os.R_OK): - with open(sysfile, 'r') as file_: + with open(sysfile, "r") as file_: # Line looks like: 10954694.52 10584141.11 line = file_.readline() - names = ['uptime_sec', 'idle_sec'] + names = ["uptime_sec", "idle_sec"] values = [float(value) for value in line.split()] return dict(zip(names, values)) else: return None @staticmethod - @works_on('linux2') + @works_on("linux2") def last_apt_cache_change_unixtime(): """Returns the unix timestamp of the last apt-get upgrade""" - apt_cache_dir = '/var/cache/apt' + apt_cache_dir = "/var/cache/apt" if os.access(apt_cache_dir, os.F_OK): - return os.path.getmtime('/proc/uptime') + return os.path.getmtime("/proc/uptime") else: return None @staticmethod - @works_on('linux2') + @works_on("linux2") def load_average(): """Returns the system load average""" - sysfile = '/proc/loadavg' + sysfile = "/proc/loadavg" if os.access(sysfile, os.R_OK): - with open(sysfile, 'r') as file_: + with open(sysfile, "r") as file_: line = file_.readline() # The line looks like this: # 0.41 0.33 0.26 1/491 21659 loads = (float(load) for load in line.split()[0:3]) - return dict(zip(['1m', '5m', '15m'], loads)) + return dict(zip(["1m", "5m", "15m"], loads)) else: return None @staticmethod - @works_on('linux2') + @works_on("linux2") def filesystem_usage(): - """Return the total and free number of bytes in the current filesystem - """ + """Return the total and free number of bytes in the current filesystem""" statvfs = os.statvfs(__file__) status = { - 'total_bytes': statvfs.f_frsize * statvfs.f_blocks, - 'free_bytes': statvfs.f_frsize * statvfs.f_bfree + "total_bytes": statvfs.f_frsize * statvfs.f_blocks, + "free_bytes": statvfs.f_frsize * statvfs.f_bfree, } return status @staticmethod - @works_on('linux2') + @works_on("linux2") def max_python_mem_usage_bytes(): """Returns the python memory usage""" pagesize = resource.getpagesize() - this_process = resource.getrusage( - resource.RUSAGE_SELF).ru_maxrss - children = resource.getrusage( - resource.RUSAGE_CHILDREN).ru_maxrss + this_process = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss + children = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss return (this_process + children) * pagesize @staticmethod - @works_on('linux2') + @works_on("linux2") def mac_address(): """Return the mac address of the currently connected interface""" # This procedure has given us problems in the past, so sorround with try-except try: # Get the IP of servcinf-sql - sql_ip = socket.gethostbyname('servcinf-sql.fysik.dtu.dk') + sql_ip = socket.gethostbyname("servcinf-sql.fysik.dtu.dk") # Get the route for the servcinf-sql ip, it will look like one of these: - #10.54.6.26 dev eth0 src 10.54.6.43 \ cache - #130.225.86.27 via 10.54.6.1 dev eth0 src 10.54.6.43 \ cache + # 10.54.6.26 dev eth0 src 10.54.6.43 \ cache + # 130.225.86.27 via 10.54.6.1 dev eth0 src 10.54.6.43 \ cache interface_string = subprocess.check_output( - ['ip', '-o', 'route', 'get', sql_ip] + ["ip", "-o", "route", "get", sql_ip] ).split() # The interface name e.g. "eth0" is the first item after "dev" - ifname = interface_string[interface_string.index('dev') + 1] + ifname = interface_string[interface_string.index("dev") + 1] # Get an infostring for a socket connection of this interface name sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - info = fcntl.ioctl(sock.fileno(), 0x8927, struct.pack(b'256s', ifname[:15])) - if sys.version < '3': - return ':'.join(['%02x' % ord(char) for char in info[18:24]]) + info = fcntl.ioctl(sock.fileno(), 0x8927, struct.pack(b"256s", ifname[:15])) + if sys.version < "3": + return ":".join(["%02x" % ord(char) for char in info[18:24]]) else: - return ':'.join(['%02x' % char for char in info[18:24]]) + return ":".join(["%02x" % char for char in info[18:24]]) except: # pylint: disable=bare-except - return 'MAC ADDRESS UNKNOWN' + return "MAC ADDRESS UNKNOWN" @staticmethod - @works_on('linux2') + @works_on("linux2") def rpi_model(): """Return the Raspberry Pi""" - with open('/proc/cpuinfo') as file_: + with open("/proc/cpuinfo") as file_: for line in file_: - if line.startswith('Revision'): + if line.startswith("Revision"): # The line looks like this: - #Revision : 0002 - revision = line.strip().split(': ')[1] + # Revision : 0002 + revision = line.strip().split(": ")[1] break else: return None - return RPI_REVISIONS.get(revision, 'Undefined revision') + return RPI_REVISIONS.get(revision, "Undefined revision") @staticmethod - @works_on('linux2') + @works_on("linux2") def os_version(): """Return the Linux OS version""" - with open('/etc/os-release') as file_: + with open("/etc/os-release") as file_: for line in file_: - if line.startswith('VERSION='): + if line.startswith("VERSION="): # The line looks like this: - #VERSION="9 (Stretch)" + # VERSION="9 (Stretch)" version = line.strip().split("=")[1].strip('"') return version else: return None @staticmethod - @works_on('linux2') + @works_on("linux2") def rpi_temperature(): """Return the temperature of a Raspberry Pi""" - #Firmware bug in Broadcom chip craches raspberry pi when reading temperature - #and using i2c at the same time - if os.path.exists('/dev/i2c-0') or os.path.exists('/dev/i2c-1'): + # Firmware bug in Broadcom chip craches raspberry pi when reading temperature + # and using i2c at the same time + if os.path.exists("/dev/i2c-0") or os.path.exists("/dev/i2c-1"): return None # Get temperature string - if os.path.exists('/sys/class/thermal/thermal_zone0/temp'): + if os.path.exists("/sys/class/thermal/thermal_zone0/temp"): try: - temp_str = subprocess.check_output(['cat', - '/sys/class/thermal/thermal_zone0/temp']) + temp_str = subprocess.check_output( + ["cat", "/sys/class/thermal/thermal_zone0/temp"] + ) except OSError: return None else: @@ -289,30 +289,35 @@ def rpi_temperature(): return temp @staticmethod - @works_on('linux2') + @works_on("linux2") def sd_card_serial(): """Return the SD card serial number""" try: - with open('/sys/block/mmcblk0/device/cid') as file_: + with open("/sys/block/mmcblk0/device/cid") as file_: serial = file_.read().strip() return serial except IOError: return None - @works_on('linux2') + @works_on("linux2") def purpose(self): """Returns the information from the purpose file""" - if 'purpose' in self._cache: - return self._cache['purpose'] + if "purpose" in self._cache: + return self._cache["purpose"] - purpose = {'id': None, 'purpose': None, 'long_description': None} - self._cache['purpose'] = purpose + purpose = {"id": None, "purpose": None, "long_description": None} + self._cache["purpose"] = purpose # Read the purpose file - filepath = path.join(path.expanduser('~'), 'PyExpLabSys', 'machines', - self._machinename, 'PURPOSE') + filepath = path.join( + path.expanduser("~"), + "PyExpLabSys", + "machines", + self._machinename, + "PURPOSE", + ) try: - with codecs.open(filepath, encoding='utf-8') as file_: + with codecs.open(filepath, encoding="utf-8") as file_: purpose_lines = file_.readlines() pass except IOError: @@ -321,31 +326,34 @@ def purpose(self): # New style purpose file if purpose_lines[0].startswith("id:"): # Get id - purpose['id'] = purpose_lines[0].split(':', 1)[1].strip() + purpose["id"] = purpose_lines[0].split(":", 1)[1].strip() # If there is id:, insist that there is also purpose: and parse it - if not purpose_lines[1].startswith('purpose:'): - message = ('With the new style purpose file (where first line starts ' - 'with "id:", the second line must start with "purpose:"') + if not purpose_lines[1].startswith("purpose:"): + message = ( + "With the new style purpose file (where first line starts " + 'with "id:", the second line must start with "purpose:"' + ) raise ValueError(message) - purpose['purpose'] = purpose_lines[1].split(':', 1)[1].strip() - purpose['long_description'] = ''.join(purpose_lines[2:]).strip() + purpose["purpose"] = purpose_lines[1].split(":", 1)[1].strip() + purpose["long_description"] = "".join(purpose_lines[2:]).strip() else: # With old stype purpose file, turn entire content into long_description - purpose['long_description'] = ''.join(purpose_lines) + purpose["long_description"] = "".join(purpose_lines) - if purpose['long_description'] == '': - purpose['long_description'] = None + if purpose["long_description"] == "": + purpose["long_description"] = None return purpose - @works_on('linux2') + @works_on("linux2") def machine_name(self): """Return the machine name""" return self._machinename -if __name__ == '__main__': +if __name__ == "__main__": from pprint import pprint + SYSTEM_STATUS = SystemStatus() pprint(SYSTEM_STATUS.complete_status()) diff --git a/PyExpLabSys/common/text_plot.py b/PyExpLabSys/common/text_plot.py index 7d56447b..5940371b 100644 --- a/PyExpLabSys/common/text_plot.py +++ b/PyExpLabSys/common/text_plot.py @@ -126,7 +126,7 @@ def plot(self, *args, **kwargs): """ data = self.ascii_plotter.plot(*args, **kwargs) self.win.clear() - for line_num, line in enumerate(data.split('\n')): + for line_num, line in enumerate(data.split("\n")): self.win.addstr(line_num, 0, line) self.win.refresh() @@ -134,8 +134,15 @@ def plot(self, *args, **kwargs): class AsciiPlot(object): """An Ascii Plot""" - def __init__(self, title=None, xlabel=None, ylabel=None, logscale=False, - size=(80, 24), debug=False): + def __init__( + self, + title=None, + xlabel=None, + ylabel=None, + logscale=False, + size=(80, 24), + debug=False, + ): """Initialize local varibles Args: @@ -151,24 +158,24 @@ def __init__(self, title=None, xlabel=None, ylabel=None, logscale=False, self.debug = debug # Open a process for gnuplot - self.process = Popen(['gnuplot'], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0) + self.process = Popen(["gnuplot"], stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=0) self.read = self.process.stdout.read # Setup ascii output and size self.write("set term dumb {} {}\n".format(*size)) # Set title and labels - for string, name in ((title, 'title'), (xlabel, 'xlabel'), (ylabel, 'ylabel')): + for string, name in ((title, "title"), (xlabel, "xlabel"), (ylabel, "ylabel")): if string: string = string.replace('"', "'") - self.write("set {} \"{}\"\n".format(name, string)) + self.write('set {} "{}"\n'.format(name, string)) # Set log scale if required if logscale: self.write("set logscale y\n") - self.write("set out\n") - + self.write("set out\n") + def write(self, string): r"""Write string to gnuplot @@ -177,8 +184,8 @@ def write(self, string): if self.debug: print(repr(string)) self.process.stdin.write(string.encode()) - - def plot(self, x, y, style='lines', legend=""): + + def plot(self, x, y, style="lines", legend=""): """Plot data Args: @@ -188,18 +195,18 @@ def plot(self, x, y, style='lines', legend=""): legend (str): The legend of the data (leave to empty string to skip) """ # Header for sending data to gnuplot inline - self.write("plot \"-\" with lines title \"{}\"\n".format(legend)) + self.write('plot "-" with lines title "{}"\n'.format(legend)) # Create data string and send - data = '\n'.join(["%s %s" % pair for pair in zip(x, y)]) - self.write(data + '\n') + data = "\n".join(["%s %s" % pair for pair in zip(x, y)]) + self.write(data + "\n") self.write("e\n") # Terminate column # The first char is a form feed self.read(1) - out = self.read(self.size[0] * self.size[1]).decode('ascii') - while out[-1] != '\n': - out += self.read(1).decode('ascii') + out = self.read(self.size[0] * self.size[1]).decode("ascii") + while out[-1] != "\n": + out += self.read(1).decode("ascii") return out @@ -221,7 +228,9 @@ def plot(self, x, y, style='lines', legend=""): try: # Create the Curses Ascii Plotter ap = CursesAsciiPlot( - win, title="Log of sine of time", xlabel="Time [s]", + win, + title="Log of sine of time", + xlabel="Time [s]", logscale=True, ) @@ -229,7 +238,7 @@ def plot(self, x, y, style='lines', legend=""): while True: stdscr.clear() t0 = time.time() - t_start - stdscr.addstr(1, 3, 'T0: {:.2f} '.format(t0)) + stdscr.addstr(1, 3, "T0: {:.2f} ".format(t0)) stdscr.refresh() x = np.linspace(t0, t0 + 10) y = np.sin(x) + 1.1 diff --git a/PyExpLabSys/common/utilities.py b/PyExpLabSys/common/utilities.py index d88760c3..53ae0deb 100644 --- a/PyExpLabSys/common/utilities.py +++ b/PyExpLabSys/common/utilities.py @@ -44,7 +44,7 @@ MAX_EMAILS_PER_PERIOD = SETTINGS.util_log_max_emails_per_period EMAIL_TIMES = { logging.WARNING: deque([0] * MAX_EMAILS_PER_PERIOD, maxlen=MAX_EMAILS_PER_PERIOD), - logging.ERROR: deque([0] * MAX_EMAILS_PER_PERIOD, maxlen=MAX_EMAILS_PER_PERIOD) + logging.ERROR: deque([0] * MAX_EMAILS_PER_PERIOD, maxlen=MAX_EMAILS_PER_PERIOD), } #: The time period that the numbers of emails will be limited within EMAIL_THROTTLE_TIME = SETTINGS.util_log_email_throttle_time @@ -67,8 +67,16 @@ def _numeric_log_level_from_name(level_name): # pylint: disable=too-many-arguments -def _create_handlers(name, terminal_log, file_log, file_name, file_max_bytes, - file_backup_count, email_on_warnings, email_on_errors): +def _create_handlers( + name, + terminal_log, + file_log, + file_name, + file_max_bytes, + file_backup_count, + email_on_warnings, + email_on_errors, +): """Build and create common handlers Build and create the following common handler of requested: @@ -86,17 +94,21 @@ def _create_handlers(name, terminal_log, file_log, file_name, file_max_bytes, # Create file handler if file_log: if file_name is None: - file_name = name + '.log' - file_handler = RotatingFileHandler(file_name, maxBytes=file_max_bytes, - backupCount=file_backup_count) + file_name = name + ".log" + file_handler = RotatingFileHandler( + file_name, maxBytes=file_max_bytes, backupCount=file_backup_count + ) handlers.append(file_handler) # Create email warning handler if email_on_warnings: # Note, the placeholder in the subject will be replaced by the hostname warning_email_handler = CustomSMTPWarningHandler( - mailhost=MAIL_HOST, fromaddr=WARNING_EMAIL, - toaddrs=[WARNING_EMAIL], subject='Warning from: {}') + mailhost=MAIL_HOST, + fromaddr=WARNING_EMAIL, + toaddrs=[WARNING_EMAIL], + subject="Warning from: {}", + ) warning_email_handler.setLevel(logging.WARNING) handlers.append(warning_email_handler) @@ -104,18 +116,30 @@ def _create_handlers(name, terminal_log, file_log, file_name, file_max_bytes, if email_on_errors: # Note, the placeholder in the subject will be replaced by the hostname error_email_handler = CustomSMTPHandler( - mailhost=MAIL_HOST, fromaddr=ERROR_EMAIL, - toaddrs=[ERROR_EMAIL], subject='Error from: {}') + mailhost=MAIL_HOST, + fromaddr=ERROR_EMAIL, + toaddrs=[ERROR_EMAIL], + subject="Error from: {}", + ) error_email_handler.setLevel(logging.ERROR) handlers.append(error_email_handler) return handlers + ### Public log functions # pylint: disable=too-many-arguments, too-many-locals -def get_logger(name, level='INFO', terminal_log=True, file_log=False, - file_name=None, file_max_bytes=1048576, file_backup_count=1, - email_on_warnings=True, email_on_errors=True): +def get_logger( + name, + level="INFO", + terminal_log=True, + file_log=False, + file_name=None, + file_max_bytes=1048576, + file_backup_count=1, + email_on_warnings=True, + email_on_errors=True, +): """Setup and return a program logger This is meant as a logger to be used in a top level program/script. The logger is set @@ -163,13 +187,18 @@ def get_logger(name, level='INFO', terminal_log=True, file_log=False, # Get handlers handlers = _create_handlers( - name=name, terminal_log=terminal_log, file_log=file_log, file_name=file_name, - file_max_bytes=file_max_bytes, file_backup_count=file_backup_count, - email_on_warnings=email_on_warnings, email_on_errors=email_on_errors, + name=name, + terminal_log=terminal_log, + file_log=file_log, + file_name=file_name, + file_max_bytes=file_max_bytes, + file_backup_count=file_backup_count, + email_on_warnings=email_on_warnings, + email_on_errors=email_on_errors, ) # Add formatters to the handlers and add the handlers to the root_logger - formatter = logging.Formatter('%(asctime)s:%(name)s: %(levelname)s: %(message)s') + formatter = logging.Formatter("%(asctime)s:%(name)s: %(levelname)s: %(message)s") for handler in handlers: handler.setFormatter(formatter) logger.addHandler(handler) @@ -182,22 +211,30 @@ def get_library_logger_names(): logger = logging.getLogger() # Somewhat undocumented way of getting all the loggers, let's hope it never changes logger_names = logger.manager.loggerDict.keys() - pyexplabsys_loggers = [ln for ln in logger_names if ln.startswith('PyExpLabSys')] + pyexplabsys_loggers = [ln for ln in logger_names if ln.startswith("PyExpLabSys")] return pyexplabsys_loggers def print_library_logger_names(): """Nicely printout all loggers currently configured in PyExpLabSys""" - print('Current PyExpLabSys loggers') - print('===========================') + print("Current PyExpLabSys loggers") + print("===========================") for log_name in get_library_logger_names(): print(" *", log_name) -def activate_library_logging(logger_name, logger_to_inherit_from=None, level=None, - terminal_log=True, file_log=False, file_name=None, - file_max_bytes=1048576, file_backup_count=1, - email_on_warnings=True, email_on_errors=True): +def activate_library_logging( + logger_name, + logger_to_inherit_from=None, + level=None, + terminal_log=True, + file_log=False, + file_name=None, + file_max_bytes=1048576, + file_backup_count=1, + email_on_warnings=True, + email_on_errors=True, +): """Activate logging for a PyExpLabSys library logger Args: @@ -221,11 +258,13 @@ def activate_library_logging(logger_name, logger_to_inherit_from=None, level=Non """ # Get hold of the logger to activate if logger_name not in get_library_logger_names(): - message = ('The logger "{}" is not among the currently configured PyExpLabSys ' - 'library loggers. Make sure you import the relevant PyExpLabSys ' - 'module *before* activating it. To get a list of all PyExpLabSys ' - 'library loggers call get_library_logger_names function from this ' - 'module') + message = ( + 'The logger "{}" is not among the currently configured PyExpLabSys ' + "library loggers. Make sure you import the relevant PyExpLabSys " + "module *before* activating it. To get a list of all PyExpLabSys " + "library loggers call get_library_logger_names function from this " + "module" + ) raise ValueError(message.format(logger_name)) logger = logging.getLogger(logger_name) @@ -246,19 +285,24 @@ def activate_library_logging(logger_name, logger_to_inherit_from=None, level=Non # Get level and set if level is None: # Set default - level = 'info' + level = "info" numeric_log_level = _numeric_log_level_from_name(level) logger.setLevel(numeric_log_level) # Create handlers handlers = _create_handlers( - name=logger_name, terminal_log=terminal_log, file_log=file_log, file_name=file_name, - file_max_bytes=file_max_bytes, file_backup_count=file_backup_count, - email_on_warnings=email_on_warnings, email_on_errors=email_on_errors, + name=logger_name, + terminal_log=terminal_log, + file_log=file_log, + file_name=file_name, + file_max_bytes=file_max_bytes, + file_backup_count=file_backup_count, + email_on_warnings=email_on_warnings, + email_on_errors=email_on_errors, ) # Set formatter and add handler - formatter = logging.Formatter('%(asctime)s:%(name)s: %(levelname)s: %(message)s') + formatter = logging.Formatter("%(asctime)s:%(name)s: %(levelname)s: %(message)s") for handler in handlers: handler.setFormatter(formatter) logger.addHandler(handler) @@ -282,10 +326,10 @@ def emit(self, record): email_log.append(now) # If there is a backlog, add it to the message before sending if len(email_backlog) > 0: - backlog = '\n'.join(email_backlog) + backlog = "\n".join(email_backlog) # Explicitly convert record.msg to str to allow for # logging.exception() with exception as arg instead of msg - record.msg = str(record.msg) + '\n\nBacklog:\n' + backlog + record.msg = str(record.msg) + "\n\nBacklog:\n" + backlog email_backlog.clear() super(CustomSMTPHandler, self).emit(record) @@ -299,7 +343,7 @@ def getSubject(self, record): hostname = platform.node() # pylint: disable=broad-except except Exception: - hostname = 'Unknown' + hostname = "Unknown" return base_subject.format(hostname) @@ -325,7 +369,7 @@ def call_spec_string(): # pylint: disable=protected-access frame = sys._getframe(1) argvals = inspect.getargvalues(frame) - if argvals.args[0] == 'self': + if argvals.args[0] == "self": return inspect.formatargvalues(argvals.args[1:], *argvals[1:]) else: return inspect.formatargvalues(*argvals) diff --git a/PyExpLabSys/common/value_logger.py b/PyExpLabSys/common/value_logger.py index ebf057eb..85e7ef7e 100644 --- a/PyExpLabSys/common/value_logger.py +++ b/PyExpLabSys/common/value_logger.py @@ -3,14 +3,25 @@ import threading import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class ValueLogger(threading.Thread): - """ Reads continuously updated values and decides - whether it is time to log a new point """ - def __init__(self, value_reader, maximumtime=600, low_comp=None, - comp_type='lin', comp_val=1, channel=None, - pre_trig=None, pre_trig_value=0.1): + """Reads continuously updated values and decides + whether it is time to log a new point""" + + def __init__( + self, + value_reader, + maximumtime=600, + low_comp=None, + comp_type="lin", + comp_val=1, + channel=None, + pre_trig=None, + pre_trig_value=0.1, + ): threading.Thread.__init__(self) self.daemon = True self.valuereader = value_reader @@ -20,62 +31,62 @@ def __init__(self, value_reader, maximumtime=600, low_comp=None, self.status = {} self.compare = {} self.last = {} - self.compare['type'] = comp_type - self.compare['val'] = comp_val - self.compare['low_comp'] = low_comp - self.status['quit'] = False - self.status['trigged'] = False - self.last['time'] = 0 - self.last['val'] = 0 + self.compare["type"] = comp_type + self.compare["val"] = comp_val + self.compare["low_comp"] = low_comp + self.status["quit"] = False + self.status["trigged"] = False + self.last["time"] = 0 + self.last["val"] = 0 # "Fancy" algorithm self.saved_points = [] self.pre_trig = pre_trig - #self.pre_trig_value = pre_trig_value * comp_val - self.compare['low val'] = pre_trig_value * comp_val + # self.pre_trig_value = pre_trig_value * comp_val + self.compare["low val"] = pre_trig_value * comp_val self.pre_trig_queue = [] self.low_time = self.maximumtime * pre_trig_value def read_value(self): - """ Read the current value """ + """Read the current value""" return self.value def read_trigged(self): - """ Ask if the class is trigged """ - return self.status['trigged'] + """Ask if the class is trigged""" + return self.status["trigged"] def clear_trigged(self): - """ Clear trigger """ - self.status['trigged'] = False + """Clear trigger""" + self.status["trigged"] = False if not self.pre_trig is None: self.pre_trig = False self.saved_points = [] def run(self): error_count = 0 - #previous_point = None + # previous_point = None - while not self.status['quit']: + while not self.status["quit"]: time.sleep(1) if self.channel is None: self.value = self.valuereader.value() else: self.value = self.valuereader.value(self.channel) - time_trigged = ((time.time() - self.last['time']) - > self.maximumtime) - pre_time_trigged = ((time.time() - self.last['time']) - > self.low_time) + time_trigged = (time.time() - self.last["time"]) > self.maximumtime + pre_time_trigged = (time.time() - self.last["time"]) > self.low_time try: - if self.compare['type'] == 'lin': - val_trigged = not (self.last['val'] - self.compare['val'] - < self.value - < self.last['val'] + self.compare['val']) - if self.compare['type'] == 'log': - val_trigged = not (self.last['val'] * - (1 - self.compare['val']) - < self.value - < self.last['val'] * - (1 + self.compare['val'])) + if self.compare["type"] == "lin": + val_trigged = not ( + self.last["val"] - self.compare["val"] + < self.value + < self.last["val"] + self.compare["val"] + ) + if self.compare["type"] == "log": + val_trigged = not ( + self.last["val"] * (1 - self.compare["val"]) + < self.value + < self.last["val"] * (1 + self.compare["val"]) + ) error_count = 0 except (UnboundLocalError, TypeError): # Happens when value is not yet ready from reader @@ -83,39 +94,41 @@ def run(self): time_trigged = False error_count = error_count + 1 if error_count > 15: - raise Exception('Error in ValueLogger') + raise Exception("Error in ValueLogger") # Will only trig on value if value is larger than low_comp - if self.compare['low_comp'] is not None: - if self.value < self.compare['low_comp']: + if self.compare["low_comp"] is not None: + if self.value < self.compare["low_comp"]: val_trigged = False if val_trigged and (self.value is not None): - self.status['trigged'] = True + self.status["trigged"] = True if pre_time_trigged: # Loop back through previous data points to find onset low_val_trigged = False for i in range(len(self.pre_trig_queue) - 1, -1, -1): t_i, y_i = self.pre_trig_queue[i] - if self.compare['type'] == 'lin': - low_val_trigged = not (self.last['val'] - self.compare['low val'] - < self.value - < self.last['val'] + self.compare['low val']) - if self.compare['type'] == 'log': - low_val_trigged = not (self.last['val'] * - (1 - self.compare['low val']) - < self.value - < self.last['val'] * - (1 + self.compare['low val'])) + if self.compare["type"] == "lin": + low_val_trigged = not ( + self.last["val"] - self.compare["low val"] + < self.value + < self.last["val"] + self.compare["low val"] + ) + if self.compare["type"] == "log": + low_val_trigged = not ( + self.last["val"] * (1 - self.compare["low val"]) + < self.value + < self.last["val"] * (1 + self.compare["low val"]) + ) if low_val_trigged: # Save extra point self.saved_points.append((t_i, y_i)) break - self.last['time'] = time.time() - self.last['val'] = self.value - self.saved_points.append((self.last['time'], self.last['val'])) + self.last["time"] = time.time() + self.last["val"] = self.value + self.saved_points.append((self.last["time"], self.last["val"])) self.pre_trig_queue = [] - #if val_trigged: + # if val_trigged: # try: # pre_time, pre_val = previous_point # if pre_val is not None: @@ -124,14 +137,14 @@ def run(self): # except TypeError: # pass elif time_trigged and (self.value is not None): - self.status['trigged'] = True - self.last['time'] = time.time() - self.last['val'] = self.value + self.status["trigged"] = True + self.last["time"] = time.time() + self.last["val"] = self.value self.saved_points.append((time.time(), self.value)) self.pre_trig_queue = [] else: self.pre_trig_queue.append((time.time(), self.value)) - #previous_point = (time.time(), self.value) + # previous_point = (time.time(), self.value) class LoggingCriteriumChecker(object): @@ -140,8 +153,14 @@ class LoggingCriteriumChecker(object): """ - def __init__(self, codenames=(()), types=(()), criteria=(()), time_outs=None, - low_compare_values=None): + def __init__( + self, + codenames=(()), + types=(()), + criteria=(()), + time_outs=None, + low_compare_values=None, + ): """Initialize the logging criterium checker .. note:: If given, codenames, types and criteria must be sequences with the same @@ -159,15 +178,19 @@ def __init__(self, codenames=(()), types=(()), criteria=(()), time_outs=None, """ error_message = None if len(types) != len(codenames): - error_message = 'The must be exactly as many types as codenames' + error_message = "The must be exactly as many types as codenames" if len(criteria) != len(codenames): - error_message = 'The must be exactly as many criteria as codenames' + error_message = "The must be exactly as many criteria as codenames" if low_compare_values is not None and len(low_compare_values) != len(codenames): - error_message = 'If low_compare_values is given, it must contain as many '\ - 'values as there are codenames' + error_message = ( + "If low_compare_values is given, it must contain as many " + "values as there are codenames" + ) if time_outs is not None and len(time_outs) != len(codenames): - error_message = 'If time_outs is given, it must contain as many '\ - 'values as there are codenames' + error_message = ( + "If time_outs is given, it must contain as many " + "values as there are codenames" + ) if error_message is not None: raise ValueError(error_message) @@ -180,9 +203,9 @@ def __init__(self, codenames=(()), types=(()), criteria=(()), time_outs=None, self.last_values = {} self.last_time = {} self.measurements = {} - for codename, type_, criterium, time_out, low_compare in zip(codenames, types, - criteria, time_outs, - low_compare_values): + for codename, type_, criterium, time_out, low_compare in zip( + codenames, types, criteria, time_outs, low_compare_values + ): self.add_measurement(codename, type_, criterium, time_out, low_compare) @property @@ -193,8 +216,10 @@ def codenames(self): def add_measurement(self, codename, type_, criterium, time_out=600, low_compare=None): """Add a measurement channel""" self.measurements[codename] = { - 'type': type_, 'criterium': criterium, 'low_compare': low_compare, - 'time_out': time_out, + "type": type_, + "criterium": criterium, + "low_compare": low_compare, + "time_out": time_out, } # Unix timestamp self.last_time[codename] = 0 @@ -204,7 +229,7 @@ def check(self, codename, value): try: measurement = self.measurements[codename] except KeyError: - raise KeyError('Codename \'{}\' is unknown'.format(codename)) + raise KeyError("Codename '{}' is unknown".format(codename)) # Pull out last value last = self.last_values.get(codename) @@ -220,24 +245,24 @@ def check(self, codename, value): return True # Always trigger on a timeout - if time.time() - self.last_time[codename] > measurement['time_out']: + if time.time() - self.last_time[codename] > measurement["time_out"]: self.last_time[codename] = time.time() self.last_values[codename] = value return True # Check if below lower compare value - if measurement['low_compare'] is not None and value < measurement['low_compare']: + if measurement["low_compare"] is not None and value < measurement["low_compare"]: return False # Compare abs_diff = abs(value - last) - if measurement['type'] == 'lin': - if abs_diff > measurement['criterium']: + if measurement["type"] == "lin": + if abs_diff > measurement["criterium"]: self.last_time[codename] = time.time() self.last_values[codename] = value return True - elif measurement['type'] == 'log': - if abs_diff / abs(last) > measurement['criterium']: + elif measurement["type"] == "log": + if abs_diff / abs(last) > measurement["criterium"]: self.last_time[codename] = time.time() self.last_values[codename] = value return True diff --git a/PyExpLabSys/common/valve_control.py b/PyExpLabSys/common/valve_control.py index 9a0c42cb..710996c8 100644 --- a/PyExpLabSys/common/valve_control.py +++ b/PyExpLabSys/common/valve_control.py @@ -8,17 +8,20 @@ import wiringpi as wp from PyExpLabSys.common.value_logger import LoggingCriteriumChecker from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class ValveControl(threading.Thread): - """ Keeps status of all valves """ + """Keeps status of all valves""" + def __init__(self, valves, pullsocket, pushsocket, db_saver=None, codenames=None): """Initialize local properties Args: - valves (): - pullsocket (): - pushsocket (): + valves (): + pullsocket (): + pushsocket (): db_saver (:class:`.ContinuousDataSaver`): (Optional) If db_saver and codenames is given, valves states will be logged as continuous values codenames (list): (Optional) Iterable of codenames for the valve @@ -37,14 +40,14 @@ def __init__(self, valves, pullsocket, pushsocket, db_saver=None, codenames=None self.pushsocket = pushsocket self.running = True self.valves = valves - + self.db_saver = db_saver self.codenames = codenames if codenames is not None: print(codenames) self.logging_criterium_checker = LoggingCriteriumChecker( codenames, - types=['lin'] * len(codenames), + types=["lin"] * len(codenames), criteria=[0.1] * len(codenames), time_outs=[600] * len(codenames), ) diff --git a/PyExpLabSys/drivers/ADS1115.py b/PyExpLabSys/drivers/ADS1115.py index 2af1a361..1f7db6b8 100644 --- a/PyExpLabSys/drivers/ADS1115.py +++ b/PyExpLabSys/drivers/ADS1115.py @@ -6,54 +6,81 @@ import time -class ADS1115(): +class ADS1115: def __init__(self): self.bus = smbus.SMBus(1) self.address = 0x48 - + # Full-scale resolution dictionary [V] - self.full_scale_dic = {'000': '6.144', '001': '4.096', '010': '2.048', '011': '1.024', - '100': '0.512', '101': '0.256', '110': '0.256', '111': '0.256'} - - self.resolution_dic = {'6.144': 0.0001875, '4.096': 0.000125, '2.048': 0.0000625, - '1.024': 0.00003125, '0.512': 0.000015625, '0.256': 0.0000078125} - - self.internal_register_dic = {'Conversion': 0x00, 'Config': 0x01, 'Lo_thresh': 0x02, 'Hi_thresh': 0x03} - + self.full_scale_dic = { + "000": "6.144", + "001": "4.096", + "010": "2.048", + "011": "1.024", + "100": "0.512", + "101": "0.256", + "110": "0.256", + "111": "0.256", + } + + self.resolution_dic = { + "6.144": 0.0001875, + "4.096": 0.000125, + "2.048": 0.0000625, + "1.024": 0.00003125, + "0.512": 0.000015625, + "0.256": 0.0000078125, + } + + self.internal_register_dic = { + "Conversion": 0x00, + "Config": 0x01, + "Lo_thresh": 0x02, + "Hi_thresh": 0x03, + } + # AINp=AIN0, AINn=AIN1, FS=+-2.048 V, 128 SPS, Continuous conversion mode self.init_config = [0x84, 0x83] self.write_config(self.init_config) time.sleep(0.5) - + def read_voltage(self): # Reads two bytes from the conversion register - data = self.bus.read_i2c_block_data(self.address, self.internal_register_dic['Conversion'], 2) + data = self.bus.read_i2c_block_data( + self.address, self.internal_register_dic["Conversion"], 2 + ) raw_adc = data[0] * 256 + data[1] - + if raw_adc > 32767: raw_adc = raw_adc - 65535 - + voltage = raw_adc * self.resolution return voltage - + def read_config(self): - config = self.bus.read_i2c_block_data(self.address, self.internal_register_dic['Config'], 2) + config = self.bus.read_i2c_block_data( + self.address, self.internal_register_dic["Config"], 2 + ) raw_config = config[0] * 256 + config[1] return raw_config - + def write_config(self, command): if type(command) == list: self.config_hex = command - self.config_binary = bin(self.config_hex[0])[2:].zfill(8) + bin(self.config_hex[1])[2:].zfill(8) + self.config_binary = bin(self.config_hex[0])[2:].zfill(8) + bin( + self.config_hex[1] + )[2:].zfill(8) self.resolution = self.resolution_dic[self.full_scale_dic[self.config_binary[4:7]]] - self.bus.write_i2c_block_data(self.address, self.internal_register_dic['Config'], self.config_hex) + self.bus.write_i2c_block_data( + self.address, self.internal_register_dic["Config"], self.config_hex + ) time.sleep(0.5) - + def default_config(self): self.write_config([0x85, 0x83]) time.sleep(0.5) - - + + if __name__ == "__main__": adc = ADS1115() print(adc.read_voltage()) diff --git a/PyExpLabSys/drivers/NGC2D.py b/PyExpLabSys/drivers/NGC2D.py index 0efd236b..f32324c1 100644 --- a/PyExpLabSys/drivers/NGC2D.py +++ b/PyExpLabSys/drivers/NGC2D.py @@ -1,20 +1,21 @@ import serial import time -class NGC2D_comm(): + +class NGC2D_comm: def __init__(self, device): self.f = serial.Serial( port=device, - #port='/dev/ttyUSB1', + # port='/dev/ttyUSB1', baudrate=9600, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, - xonxoff=False - ) + xonxoff=False, + ) time.sleep(1) - def comm(self,command): + def comm(self, command): if command == "Poll": comm = "*P0" elif command == "Control": @@ -25,28 +26,27 @@ def comm(self,command): comm = "*S0" else: print("Unknown Command") - return(None) # Remember to test for None return value + return None # Remember to test for None return value self.f.write(comm) time.sleep(1) - #number = self.f.inWaiting() + # number = self.f.inWaiting() complete_string = self.f.read(self.f.inWaiting()) - #self.f.close() - #print complete_string - return(complete_string) + # self.f.close() + # print complete_string + return complete_string def ReadPressure(self): - pressure_string = self.comm('Status') + pressure_string = self.comm("Status") pressure = pressure_string.split("\r\n")[0][9:16] try: if pressure[0] == " ": print("Pressure Gauge is Off") - return(-1) + return -1 except: print(pressure) - #print pressure - return(pressure) - + # print pressure + return pressure def ReadPressureUnit(self): unit_string = self.comm("Status") @@ -57,9 +57,10 @@ def ReadPressureUnit(self): unit = "Pa" elif unit_string == "M": unit = "mBar" - #print unit - return(unit) + # print unit + return unit + -if __name__ == '__main__': - NG = NGC2D_comm('/dev/ttyUSB1') +if __name__ == "__main__": + NG = NGC2D_comm("/dev/ttyUSB1") print("Pressure: " + str(NG.ReadPressure())) diff --git a/PyExpLabSys/drivers/agilent_34410A.py b/PyExpLabSys/drivers/agilent_34410A.py index db48674b..3fca1a85 100644 --- a/PyExpLabSys/drivers/agilent_34410A.py +++ b/PyExpLabSys/drivers/agilent_34410A.py @@ -4,6 +4,7 @@ from PyExpLabSys.drivers.scpi import SCPI import sys from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) @@ -11,49 +12,59 @@ class Agilent34410ADriver(SCPI): - """ Driver for Agilent 34410A DMM """ - def __init__(self, interface='lan', hostname='', connection_string=''): - if interface == 'lan': # the LAN interface - SCPI.__init__(self, interface=interface, hostname=hostname, line_ending='\n') - if interface == 'file': # For distributions that mounts usbtmc as a file (eg. ubuntu) - SCPI.__init__(self, interface=interface, device='/dev/usbtmc0') - if interface == 'usbtmc': # For python-usbtmc (preferred over file) + """Driver for Agilent 34410A DMM""" + + def __init__(self, interface="lan", hostname="", connection_string=""): + if interface == "lan": # the LAN interface + SCPI.__init__(self, interface=interface, hostname=hostname, line_ending="\n") + if interface == "file": # For distributions that mounts usbtmc as a file (eg. ubuntu) + SCPI.__init__(self, interface=interface, device="/dev/usbtmc0") + if interface == "usbtmc": # For python-usbtmc (preferred over file) SCPI.__init__(self, interface=interface, visa_string=connection_string) def config_current_measurement(self): - """ Configures the instrument to measure current. """ + """Configures the instrument to measure current.""" # FIXME: Take parameter to also be able to select AC self.scpi_comm("CONFIGURE:CURRENT:DC") return True def config_resistance_measurement(self): - """ Configures the instrument to measure resistance. """ + """Configures the instrument to measure resistance.""" # FIXME: Take parameter to also be able to select 4W self.scpi_comm("CONFIGURE:RESISTANCE") return True def select_measurement_function(self, function): - """ Select a measurement function. + """Select a measurement function. Keyword arguments: Function -- A string stating the wanted measurement function. """ - values = ['CAPACITANCE', 'CONTINUITY', 'CURRENT', 'DIODE', 'FREQUENCY', - 'RESISTANCE', 'FRESISTANCE', 'TEMPERATURE', 'VOLTAGE'] + values = [ + "CAPACITANCE", + "CONTINUITY", + "CURRENT", + "DIODE", + "FREQUENCY", + "RESISTANCE", + "FRESISTANCE", + "TEMPERATURE", + "VOLTAGE", + ] return_value = False if function in values: return_value = True - function_string = "FUNCTION " + "\"" + function + "\"" + function_string = "FUNCTION " + '"' + function + '"' self.scpi_comm(function_string) return return_value def read_configuration(self): - """ Read device configuration """ + """Read device configuration""" response = self.scpi_comm("CONFIGURE?") - response = response.replace(' ', ',') - conf = response.split(',') + response = response.replace(" ", ",") + conf = response.split(",") conf_string = "" conf_string += "Measurement type: " + conf[0] + "\n" conf_string += "Range: " + conf[1] + "\n" @@ -61,30 +72,32 @@ def read_configuration(self): return conf_string def set_auto_input_z(self, auto=False): - """ Change internal resitance """ + """Change internal resitance""" if auto: self.scpi_comm("VOLT:IMP:AUTO ON") else: self.scpi_comm("VOLT:IMP:AUTO OFF") def read(self): - """ Read a value from the device """ + """Read a value from the device""" value = float(self.scpi_comm("READ?")) return value + def main(): - """ Main function """ + """Main function""" if len(sys.argv) == 3: interface = sys.argv[1] device = sys.argv[2] driver = Agilent34410ADriver(interface, hostname=device, connection_string=device) print(driver.read()) - driver.select_measurement_function('VOLTAGE') + driver.select_measurement_function("VOLTAGE") print(driver.read()) - driver.select_measurement_function('FRESISTANCE') + driver.select_measurement_function("FRESISTANCE") print(driver.read()) else: - print('Please provide interface and connection information') - + print("Please provide interface and connection information") + + if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/agilent_34972A.py b/PyExpLabSys/drivers/agilent_34972A.py index 769a8659..9e9220e7 100644 --- a/PyExpLabSys/drivers/agilent_34972A.py +++ b/PyExpLabSys/drivers/agilent_34972A.py @@ -3,91 +3,94 @@ from PyExpLabSys.drivers.scpi import SCPI import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class Agilent34972ADriver(SCPI): - """ Driver for Agilent 34972A multiplexer """ - def __init__(self, interface='lan', hostname='', connection_string=''): - if interface == 'lan': # the LAN interface - SCPI.__init__(self, interface=interface, hostname=hostname, line_ending='\n') - if interface == 'file': # For distributions that mounts usbtmc as a file (eg. ubuntu) - SCPI.__init__(self, interface=interface, device='/dev/usbtmc0') - if interface == 'usbtmc': # For python-usbtmc (preferred over file) + """Driver for Agilent 34972A multiplexer""" + + def __init__(self, interface="lan", hostname="", connection_string=""): + if interface == "lan": # the LAN interface + SCPI.__init__(self, interface=interface, hostname=hostname, line_ending="\n") + if interface == "file": # For distributions that mounts usbtmc as a file (eg. ubuntu) + SCPI.__init__(self, interface=interface, device="/dev/usbtmc0") + if interface == "usbtmc": # For python-usbtmc (preferred over file) SCPI.__init__(self, interface=interface, visa_string=connection_string) def read_single_scan(self): - """ Read a single scan-line """ + """Read a single scan-line""" self.scpi_comm("TRIG:SOURCE TIMER") self.scpi_comm("TRIG:COUNT 1") self.scpi_comm("INIT") time.sleep(0.025) status = int(self.scpi_comm("STATUS:OPERATION:CONDITION?")) status_bin = bin(status)[2:].zfill(16) - while status_bin[11] == '1': + while status_bin[11] == "1": status = int(self.scpi_comm("STATUS:OPERATION:CONDITION?")) status_bin = bin(status)[2:].zfill(16) time.sleep(0.025) response = self.scpi_comm("FETCH?") - response = response.split(',') + response = response.split(",") return_values = [] for val in response: return_values.append(float(val)) return return_values def abort_scan(self): - """ Abort the scan """ + """Abort the scan""" self.scpi_comm("ABOR") def read_configuration(self): - """ Read device configuration """ + """Read device configuration""" scan_list = self.read_scan_list() response = self.scpi_comm("CONFIGURE?") - response = response.replace(' ', ',') - response = response.replace('\"', '') - response = response.replace('\n', '') - conf = response.split(',') + response = response.replace(" ", ",") + response = response.replace('"', "") + response = response.replace("\n", "") + conf = response.split(",") response = self.scpi_comm("VOLT:DC:NPLC?") - nplc_conf = response.split(',') + nplc_conf = response.split(",") i = 0 conf_string = "" for channel in scan_list: conf_string += str(channel) + "\n" + "Measurement type: " - conf_string += conf[3*i] + "\nRange: " + conf[3*i+1] - conf_string += "\nResolution: " + conf[3*i + 2] + "\nNPLC: " + conf_string += conf[3 * i] + "\nRange: " + conf[3 * i + 1] + conf_string += "\nResolution: " + conf[3 * i + 2] + "\nNPLC: " conf_string += str(float(nplc_conf[i])) + "\n \n" i += 1 return conf_string def set_scan_interval(self, interval): - """ Set the scan interval """ + """Set the scan interval""" self.scpi_comm("TRIG:TIMER " + str(interval)) def set_integration_time(self, channel, nplc): - """ Set integration time """ + """Set integration time""" comm_string = "VOLT:DC:NPLC " + str(nplc) + ",(@" + str(channel) + ")" self.scpi_comm(comm_string) def read_scan_interval(self): - """ Read the scan interval """ + """Read the scan interval""" response = self.scpi_comm("TRIG:TIMER?") print(response) def read_scan_list(self): - """ Return the scan list """ + """Return the scan list""" response = self.scpi_comm("ROUT:SCAN?") response = response.strip() - start = response.find('@') - response = response[start+1:-1] - return response.split(',') + start = response.find("@") + response = response[start + 1 : -1] + return response.split(",") def set_scan_list(self, channels): - """ Set the scan list """ + """Set the scan list""" comm = "ROUT:SCAN (@" for chn in channels: - comm += str(chn) + ',' + comm += str(chn) + "," comm = comm[:-1] comm += ")" self.scpi_comm(comm) @@ -100,4 +103,3 @@ def set_scan_list(self, channels): print(DEVICE.read_scan_list()) print(DEVICE.read_configuration()) print(DEVICE.read_single_scan()) - diff --git a/PyExpLabSys/drivers/analogdevices_ad5667.py b/PyExpLabSys/drivers/analogdevices_ad5667.py index be9a09c1..785673e5 100644 --- a/PyExpLabSys/drivers/analogdevices_ad5667.py +++ b/PyExpLabSys/drivers/analogdevices_ad5667.py @@ -1,4 +1,3 @@ - """Driver for the Analog Devices AD5667 2 channel analog output DAC Implemented from the manual located `here @@ -11,6 +10,7 @@ import time import smbus from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) @@ -25,34 +25,34 @@ def __init__(self, address=0x0E): self.address = address self.dac_and_input_register = { - 'both': 0x1F, - 'A': 0x18, - 'B': 0x19, + "both": 0x1F, + "A": 0x18, + "B": 0x19, } self.last_write = 0 self.waittime = 0.1 def reset_device(self): - data = [0x00, 0xff] + data = [0x00, 0xFF] command = 0b00101000 - self.bus.write_i2c_block_data(0x0c, command, data) + self.bus.write_i2c_block_data(0x0C, command, data) return True def enable_onbaord_reference(self): - """ Enable on-board reference voltage, if no voltage reference is externally - given, the onboard must be enabled. + """Enable on-board reference voltage, if no voltage reference is externally + given, the onboard must be enabled. """ - data = [0x00, 0xff] + data = [0x00, 0xFF] command = 0b00111000 - self.bus.write_i2c_block_data(0x0c, command, data) + self.bus.write_i2c_block_data(0x0C, command, data) return True def power_up_or_down(self): # Notice, default state is up, if you run this, device will turn off. - data = [0x00, 0xff] + data = [0x00, 0xFF] command = 0b00100000 - self.bus.write_i2c_block_data(0x0c, command, data) + self.bus.write_i2c_block_data(0x0C, command, data) return True def write_to_and_update_dac(self, dac, value): @@ -72,14 +72,14 @@ def write_to_and_update_dac(self, dac, value): try: dac = self.dac_and_input_register[dac] except KeyError: - message = 'Invalid dac setting \'{}\', must be on of: {}'.format( + message = "Invalid dac setting '{}', must be on of: {}".format( dac, list(self.dac_and_input_register.keys()), ) raise ValueError(message) if (not isinstance(value, float)) or value < 0.0 or value > 5.0: - message = 'Invalid value: {} Must be a float in range 0.0 -> 5.0' + message = "Invalid value: {} Must be a float in range 0.0 -> 5.0" raise ValueError(message.format(value)) # Scale by range and convert the value to 2 bytes @@ -99,7 +99,7 @@ def set_channel_A(self, voltage): # pylint: disable=invalid-name See :meth:`.write_to_and_update_dac` for details on exceptions """ - self.write_to_and_update_dac('A', voltage) + self.write_to_and_update_dac("A", voltage) def set_channel_B(self, voltage): # pylint: disable=invalid-name """Set a voltage of channel B @@ -109,7 +109,7 @@ def set_channel_B(self, voltage): # pylint: disable=invalid-name See :meth:`.write_to_and_update_dac` for details """ - self.write_to_and_update_dac('B', voltage) + self.write_to_and_update_dac("B", voltage) def set_both(self, voltage): """Set a voltage of both channels @@ -119,19 +119,19 @@ def set_both(self, voltage): See :meth:`.write_to_and_update_dac` for details """ - self.write_to_and_update_dac('both', voltage) + self.write_to_and_update_dac("both", voltage) def module_test(): """Simple module test""" adc = AD5667(0x08) for number in range(100): - adc.write_to_and_update_dac('A', number / 99.0 * 5.0) + adc.write_to_and_update_dac("A", number / 99.0 * 5.0) -if __name__ == '__main__': +if __name__ == "__main__": # module_test() - adc = AD5667(0x0c) + adc = AD5667(0x0C) adc.reset_device() time.sleep(0.05) diff --git a/PyExpLabSys/drivers/asair_aht20.py b/PyExpLabSys/drivers/asair_aht20.py index 2370a083..285f9027 100644 --- a/PyExpLabSys/drivers/asair_aht20.py +++ b/PyExpLabSys/drivers/asair_aht20.py @@ -3,8 +3,8 @@ class AsairAht20(object): - """ Class for reading pressure and temperature from Omron D6F-PH series - Ranging not implemented for all models """ + """Class for reading pressure and temperature from Omron D6F-PH series + Ranging not implemented for all models""" def __init__(self): self.bus = smbus.SMBus(1) @@ -18,14 +18,14 @@ def _read_status(self): return response def init_device(self): - """ Sensor needs to be initiated after power up """ + """Sensor needs to be initiated after power up""" # Reset sensor: - self.bus.write_byte(self.device_address, 0xba) + self.bus.write_byte(self.device_address, 0xBA) time.sleep(0.03) # Calibrate sensor: init_command = [0x08, 0x00] - self.bus.write_i2c_block_data(self.device_address, 0xbe, init_command) + self.bus.write_i2c_block_data(self.device_address, 0xBE, init_command) calibrated = False while not calibrated: @@ -36,9 +36,9 @@ def init_device(self): return True def read_value(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" read_command = [0x33, 0x00] - self.bus.write_i2c_block_data(self.device_address, 0xac, read_command) + self.bus.write_i2c_block_data(self.device_address, 0xAC, read_command) time.sleep(0.1) value = self.bus.read_i2c_block_data(self.device_address, 0x00, 6) # msg = 'State: {}, hex: {}, bin: {}' @@ -60,7 +60,7 @@ def read_value(self): return humidity_cal, temperature_cal -if __name__ == '__main__': +if __name__ == "__main__": ASAIR = AsairAht20() humidity, temperature = ASAIR.read_value() - print('Temperature: {:.2f}C, Humidity: {:.2f}%'.format(temperature, humidity)) + print("Temperature: {:.2f}C, Humidity: {:.2f}%".format(temperature, humidity)) diff --git a/PyExpLabSys/drivers/bio_logic.py b/PyExpLabSys/drivers/bio_logic.py index 24b7fe9e..706ad3b7 100644 --- a/PyExpLabSys/drivers/bio_logic.py +++ b/PyExpLabSys/drivers/bio_logic.py @@ -63,22 +63,24 @@ from ctypes import c_float, c_double, c_char from ctypes import Structure from ctypes import create_string_buffer, byref, POINTER, cast + try: from ctypes import WinDLL except ImportError: RUNNING_SPHINX = False for module in sys.modules: - if 'sphinx' in module: + if "sphinx" in module: RUNNING_SPHINX = True # Let the module continue after this fatal import error, if we are running # on read the docs or we can detect that sphinx is imported - if not (os.environ.get('READTHEDOCS', None) == 'True' or RUNNING_SPHINX): + if not (os.environ.get("READTHEDOCS", None) == "True" or RUNNING_SPHINX): raise # Numpy is optional and is only required if it is resired to get the data as # numpy arrays try: import numpy + GOT_NUMPY = True except ImportError: GOT_NUMPY = False @@ -100,9 +102,7 @@ ### Named tuples #:A named tuple used to defined a return data field for a technique -DataField = namedtuple( - 'DataField', ['name', 'type'] -) +DataField = namedtuple("DataField", ["name", "type"]) #:The TechniqueArgument instance, that are used as args arguments, are named #:tuples with the following fields (in order): #: @@ -119,7 +119,7 @@ #: float or int, for 'in' should be a sequence and for 'in_float_range' #: should be a tuple of two floats TechniqueArgument = namedtuple( - 'TechniqueArgument', ['label', 'type', 'value', 'check', 'check_argument'] + "TechniqueArgument", ["label", "type", "value", "check", "check_argument"] ) @@ -157,9 +157,9 @@ def __init__(self, type_, address, EClib_dll_path): """ self._type = type_ if type_ in SP300SERIES: - self.series = 'sp300' + self.series = "sp300" else: - self.series = 'vmp3' + self.series = "vmp3" self.address = address self._id = None @@ -167,18 +167,17 @@ def __init__(self, type_, address, EClib_dll_path): # Load the EClib dll if EClib_dll_path is None: - EClib_dll_path = \ - 'C:\\EC-Lab Development Package\\EC-Lab Development Package\\' + EClib_dll_path = "C:\\EC-Lab Development Package\\EC-Lab Development Package\\" # Appearently, this is the way to check whether this is 64 bit # Windows: http://stackoverflow.com/questions/2208828/ # detect-64bit-os-windows-in-python. NOTE: That it is not # sufficient to use platform.architecture(), since that will return # the 32/64 bit value of Python NOT the OS - if 'PROGRAMFILES(X86)' in os.environ: - EClib_dll_path += 'EClib64.dll' + if "PROGRAMFILES(X86)" in os.environ: + EClib_dll_path += "EClib64.dll" else: - EClib_dll_path += 'EClib.dll' + EClib_dll_path += "EClib.dll" self._eclib = WinDLL(EClib_dll_path) @@ -199,7 +198,7 @@ def device_info(self): """ if self._device_info is not None: out = structure_to_dict(self._device_info) - out['DeviceCode(translated)'] = DEVICE_CODES[out['DeviceCode']] + out["DeviceCode(translated)"] = DEVICE_CODES[out["DeviceCode"]] return out # General functions @@ -226,20 +225,18 @@ def get_error_message(self, error_code): """ message = create_string_buffer(255) number_of_chars = c_uint32(255) - ret = self._eclib.BL_GetErrorMsg( - error_code, - byref(message), - byref(number_of_chars) - ) + ret = self._eclib.BL_GetErrorMsg(error_code, byref(message), byref(number_of_chars)) # IMPORTANT, we cannot use, self.check_eclib_return_code here, since # that internally use this method, thus we have the potential for an # infinite loop if ret < 0: - err_msg = 'The error message is unknown, because it is the '\ - 'method to retrieve the error message with that fails. '\ - 'See the error codes sections (5.4) of the EC-Lab '\ - 'development package documentation to get the meaning '\ - 'of the error code.' + err_msg = ( + "The error message is unknown, because it is the " + "method to retrieve the error message with that fails. " + "See the error codes sections (5.4) of the EC-Lab " + "development package documentation to get the meaning " + "of the error code." + ) raise ECLibError(err_msg, ret) return message.value @@ -260,16 +257,16 @@ def connect(self, timeout=5): address = create_string_buffer(self.address) self._id = c_int32() device_info = DeviceInfos() - ret = self._eclib.BL_Connect(byref(address), timeout, - byref(self._id), - byref(device_info)) + ret = self._eclib.BL_Connect( + byref(address), timeout, byref(self._id), byref(device_info) + ) self.check_eclib_return_code(ret) if DEVICE_CODES[device_info.DeviceCode] != self._type: - message = 'The device type ({}) returned from the device '\ - 'on connect does not match the device type of '\ - 'the class ({})'.format( - DEVICE_CODES[device_info.DeviceCode], - self._type) + message = ( + "The device type ({}) returned from the device " + "on connect does not match the device type of " + "the class ({})".format(DEVICE_CODES[device_info.DeviceCode], self._type) + ) raise ECLibCustomException(-9000, message) self._device_info = device_info return self.device_info @@ -316,8 +313,8 @@ def load_firmware(self, channels, force_reload=False): p_channels = cast(c_channels, POINTER(c_uint8)) ret = self._eclib.BL_LoadFirmware( - self._id, p_channels, p_results, len(channels), False, - force_reload, None, None) + self._id, p_channels, p_results, len(channels), False, force_reload, None, None + ) self.check_eclib_return_code(ret) return list(c_results) @@ -362,27 +359,23 @@ class for a list of available dict items. Besides the items keys for those values are suffixed by (translated). """ channel_info = ChannelInfos() - self._eclib.BL_GetChannelInfos(self._id, channel, - byref(channel_info)) + self._eclib.BL_GetChannelInfos(self._id, channel, byref(channel_info)) out = structure_to_dict(channel_info) # Translate code to strings - out['FirmwareCode(translated)'] = \ - FIRMWARE_CODES[out['FirmwareCode']] - out['AmpCode(translated)'] = AMP_CODES.get(out['AmpCode']) - out['State(translated)'] = STATES.get(out['State']) - out['MaxIRange(translated)'] = I_RANGES.get(out['MaxIRange']) - out['MinIRange(translated)'] = I_RANGES.get(out['MinIRange']) - out['MaxBandwidth'] = BANDWIDTHS.get(out['MaxBandwidth']) + out["FirmwareCode(translated)"] = FIRMWARE_CODES[out["FirmwareCode"]] + out["AmpCode(translated)"] = AMP_CODES.get(out["AmpCode"]) + out["State(translated)"] = STATES.get(out["State"]) + out["MaxIRange(translated)"] = I_RANGES.get(out["MaxIRange"]) + out["MinIRange(translated)"] = I_RANGES.get(out["MinIRange"]) + out["MaxBandwidth"] = BANDWIDTHS.get(out["MaxBandwidth"]) return out def get_message(self, channel): - """ Return a message from the firmware of a channel """ + """Return a message from the firmware of a channel""" size = c_uint32(255) message = create_string_buffer(255) - ret = self._eclib.BL_GetMessage(self._id, channel, - byref(message), - byref(size)) + ret = self._eclib.BL_GetMessage(self._id, channel, byref(message), byref(size)) self.check_eclib_return_code(ret) return message.value @@ -399,13 +392,11 @@ def load_technique(self, channel, technique, first=True, last=True): Raises: ECLibError: On errors from the EClib communications library """ - if self.series == 'sp300': + if self.series == "sp300": filename, ext = os.path.splitext(technique.technique_filename) - c_technique_file = create_string_buffer(filename + '4' + ext) + c_technique_file = create_string_buffer(filename + "4" + ext) else: - c_technique_file = create_string_buffer( - technique.technique_filename - ) + c_technique_file = create_string_buffer(technique.technique_filename) # Init TECCParams c_tecc_params = TECCParams() @@ -459,7 +450,10 @@ def define_single_parameter(self, label, value, index, tecc_param): """ c_label = create_string_buffer(label) ret = self._eclib.BL_DefineSglParameter( - byref(c_label), c_float(value), index, byref(tecc_param), + byref(c_label), + c_float(value), + index, + byref(tecc_param), ) self.check_eclib_return_code(ret) @@ -511,15 +505,13 @@ def get_current_values(self, channel): dict: A dict of current values information """ current_values = CurrentValues() - ret = self._eclib.BL_GetCurrentValues( - self._id, channel, byref(current_values) - ) + ret = self._eclib.BL_GetCurrentValues(self._id, channel, byref(current_values)) self.check_eclib_return_code(ret) # Convert the struct to a dict and translate a few values out = structure_to_dict(current_values) - out['State(translated)'] = STATES[out['State']] - out['IRange(translated)'] = I_RANGES[out['IRange']] + out["State(translated)"] = STATES[out["State"]] + out["IRange(translated)"] = I_RANGES[out["IRange"]] return out def get_data(self, channel): @@ -550,7 +542,7 @@ def get_data(self, channel): # The KBIOData will ask the appropriate techniques for which data # fields they return data in data = KBIOData(c_databuffer, c_data_infos, c_current_values, self) - if data.technique == 'KBIO_TECHID_NONE': + if data.technique == "KBIO_TECHID_NONE": data = None return data @@ -578,10 +570,7 @@ def convert_numeric_into_single(self, numeric): """ c_out_float = c_float() - ret = self._eclib.BL_ConvertNumericIntoSingle( - numeric, - byref(c_out_float) - ) + ret = self._eclib.BL_ConvertNumericIntoSingle(numeric, byref(c_out_float)) self.check_eclib_return_code(ret) return c_out_float.value @@ -602,9 +591,7 @@ def __init__(self, address, EClib_dll_path=None): explanation of the arguments. """ super(SP150, self).__init__( - type_='KBIO_DEV_SP150', - address=address, - EClib_dll_path=EClib_dll_path + type_="KBIO_DEV_SP150", address=address, EClib_dll_path=EClib_dll_path ) @@ -628,8 +615,7 @@ class KBIOData(object): """ - def __init__(self, c_databuffer, c_data_infos, c_current_values, - instrument): + def __init__(self, c_databuffer, c_data_infos, c_current_values, instrument): """Initialize the KBIOData object Args: @@ -688,41 +674,47 @@ def _init_data_fields(self, instrument): # Get the data_fields class variable from the corresponding technique # class if self.technique not in TECHNIQUE_IDENTIFIERS_TO_CLASS: - message = \ - 'The technique \'{}\' has no entry in '\ - 'TECHNIQUE_IDENTIFIERS_TO_CLASS. The is required to be able '\ - 'to interpret the data'.format(self.technique) + message = ( + "The technique '{}' has no entry in " + "TECHNIQUE_IDENTIFIERS_TO_CLASS. The is required to be able " + "to interpret the data".format(self.technique) + ) raise ECLibCustomException(message, -20000) technique_class = TECHNIQUE_IDENTIFIERS_TO_CLASS[self.technique] - if 'data_fields' not in technique_class.__dict__: - message = 'The technique class {} does not defined a '\ - '\'data_fields\' class variable, which is required for '\ - 'data interpretation.'.format(technique_class.__name__) + if "data_fields" not in technique_class.__dict__: + message = ( + "The technique class {} does not defined a " + "'data_fields' class variable, which is required for " + "data interpretation.".format(technique_class.__name__) + ) raise ECLibCustomException(message, -20001) data_fields_complete = technique_class.data_fields if self.process == 1: # Process 1 means no special time field try: - data_fields_out = data_fields_complete['no_time'] + data_fields_out = data_fields_complete["no_time"] except KeyError: - message = 'Unable to get data_fields from technique class. '\ - 'The data_fields class variable in the technique '\ - 'class must have either a \'no_time\' key when '\ - 'returning data with process index 1' + message = ( + "Unable to get data_fields from technique class. " + "The data_fields class variable in the technique " + "class must have either a 'no_time' key when " + "returning data with process index 1" + ) raise ECLibCustomException(message, -20002) else: try: - data_fields_out = data_fields_complete['common'] + data_fields_out = data_fields_complete["common"] except KeyError: try: data_fields_out = data_fields_complete[instrument.series] except KeyError: - message =\ - 'Unable to get data_fields from technique class. '\ - 'The data_fields class variable in the technique '\ - 'class must have either a \'common\' or a \'{}\' '\ - 'key'.format(instrument.series) + message = ( + "Unable to get data_fields from technique class. " + "The data_fields class variable in the technique " + "class must have either a 'common' or a '{}' " + "key".format(instrument.series) + ) raise ECLibCustomException(message, -20002) return data_fields_out @@ -739,8 +731,9 @@ def _parse_data(self, c_databuffer, timebase, instrument): # amount of colums. Get the index of the first item of each point by # getting the range from 0 til n_point * n_columns in jumps of # n_columns - for index in range(0, self.number_of_points * self.number_of_columns, - self.number_of_columns): + for index in range( + 0, self.number_of_points * self.number_of_columns, self.number_of_columns + ): # If there is a special time variable if self.process == 0: # Calculate the time @@ -752,10 +745,7 @@ def _parse_data(self, c_databuffer, timebase, instrument): # figure out exactly how a bitshift operation is defined for # an int class that can change internal representation, so I # just do the explicit multiplication - self.time.append( - self.starttime +\ - timebase * ((t_high * 2 ** 32) + t_low) - ) + self.time.append(self.starttime + timebase * ((t_high * 2**32) + t_low)) # Only offset reading the rest of the variables if there is a # special conversion time variable time_variable_offset = 2 @@ -764,8 +754,7 @@ def _parse_data(self, c_databuffer, timebase, instrument): # Get remaining fields as defined in data fields for field_number, data_field in enumerate(self.data_fields): - value = c_databuffer[index + time_variable_offset + - field_number] + value = c_databuffer[index + time_variable_offset + field_number] # If the type is supposed to be float, convert the numeric to # float using the convinience function if data_field.type is c_float: @@ -775,8 +764,7 @@ def _parse_data(self, c_databuffer, timebase, instrument): getattr(self, data_field.name).append(value) # Check that the rest of the buffer is blank - for index in range(self.number_of_points * self.number_of_columns, - 1000): + for index in range(self.number_of_points * self.number_of_columns, 1000): assert c_databuffer[index] == 0 def __getattr__(self, key): @@ -787,15 +775,14 @@ def __getattr__(self, key): # __getattr__ is only called after the check of whether the key is in # the instance dict, therefore it is ok to raise attribute error at # this points if the key does not have the special form we expect - if key.endswith('_numpy'): + if key.endswith("_numpy"): # Get the requested field name e.g. Ewe - requested_field = key.split('_numpy')[0] - if requested_field in self.data_field_names or\ - requested_field == 'time': + requested_field = key.split("_numpy")[0] + if requested_field in self.data_field_names or requested_field == "time": if GOT_NUMPY: # Determin the numpy type to convert to dtype = None - if requested_field == 'time': + if requested_field == "time": dtype = float else: for field in self.data_fields: @@ -806,20 +793,21 @@ def __getattr__(self, key): dtype = int if dtype is None: - message = 'Unable to infer the numpy data type for '\ - 'requested field: {}'.format(requested_field) + message = ( + "Unable to infer the numpy data type for " + "requested field: {}".format(requested_field) + ) raise ValueError(message) # Convert the data and return the numpy array return numpy.array( # pylint: disable=no-member - getattr(self, requested_field), - dtype=dtype) + getattr(self, requested_field), dtype=dtype + ) else: - message = 'The numpy module is required to get the data '\ - 'as numpy arrays' + message = "The numpy module is required to get the data " "as numpy arrays" raise RuntimeError(message) - message = '{} object has no attribute {}'.format(self.__class__, key) + message = "{} object has no attribute {}".format(self.__class__, key) raise AttributeError(message) @property @@ -929,7 +917,7 @@ def _init_c_args(self, instrument): # steps step_number = 1 for arg in self.args: - if arg.label == 'Step_number': + if arg.label == "Step_number": step_number = arg.value constructed_args = [] @@ -949,21 +937,23 @@ def _init_c_args(self, instrument): continue # Get the appropriate conversion function, to populate the EccParam - stripped_type = arg.type.strip('[]') + stripped_type = arg.type.strip("[]") try: # Get the conversion method from the instrument instance, this # is named something like defined_bool_parameter conversion_function = getattr( - instrument, 'define_{}_parameter'.format(stripped_type) + instrument, "define_{}_parameter".format(stripped_type) ) except AttributeError: - message = 'Unable to find parameter definitions function for '\ - 'type: {}'.format(stripped_type) + message = ( + "Unable to find parameter definitions function for " + "type: {}".format(stripped_type) + ) raise ECLibCustomException(message, -10010) # If the parameter is not a multistep paramter, put the value in a # list so we can iterate over it - if arg.type.startswith('[') and arg.type.endswith(']'): + if arg.type.startswith("[") and arg.type.endswith("]"): values = arg.value else: values = [arg.value] @@ -975,9 +965,12 @@ def _init_c_args(self, instrument): try: conversion_function(arg.label, values[index], index, param) except ECLibError: - message = '{} is not a valid value for conversion to '\ - 'type {} for argument \'{}\''.format( - values[index], stripped_type, arg.label) + message = ( + "{} is not a valid value for conversion to " + "type {} for argument '{}'".format( + values[index], stripped_type, arg.label + ) + ) raise ECLibCustomException(message, -10011) constructed_args.append(param) @@ -992,45 +985,48 @@ def _check_arg(arg): return # If the type is not a dict (used for constants) and indicates an array - elif not isinstance(arg.type, dict) and\ - arg.type.startswith('[') and arg.type.endswith(']'): + elif ( + not isinstance(arg.type, dict) + and arg.type.startswith("[") + and arg.type.endswith("]") + ): values = arg.value else: values = [arg.value] # Check arguments with a list of accepted values - if arg.check == 'in': + if arg.check == "in": for value in values: if value not in arg.check_argument: - message = '{} is not among the valid values for \'{}\'. '\ - 'Valid values are: {}'.format( - value, arg.label, arg.check_argument) + message = ( + "{} is not among the valid values for '{}'. " + "Valid values are: {}".format(value, arg.label, arg.check_argument) + ) raise ECLibCustomException(message, -10000) return # Perform bounds check, if any - if arg.check == '>=': + if arg.check == ">=": for value in values: if not value >= arg.check_argument: - message = 'Value {} for parameter \'{}\' failed '\ - 'check >={}'.format( - value, arg.label, arg.check_argument) + message = "Value {} for parameter '{}' failed " "check >={}".format( + value, arg.label, arg.check_argument + ) raise ECLibCustomException(message, -10001) return # Perform in two parameter range check: A < value < B - if arg.check == 'in_float_range': + if arg.check == "in_float_range": for value in values: if not arg.check_argument[0] <= value <= arg.check_argument[1]: - message = 'Value {} for parameter \'{}\' failed '\ - 'check between {} and {}'.format( - value, arg.label, - *arg.check_argument - ) + message = ( + "Value {} for parameter '{}' failed " + "check between {} and {}".format(value, arg.label, *arg.check_argument) + ) raise ECLibCustomException(message, -10002) return - message = 'Unknown technique parameter check: {}'.format(arg.check) + message = "Unknown technique parameter check: {}".format(arg.check) raise ECLibCustomException(message, -10002) @@ -1047,12 +1043,17 @@ class OCV(Technique): #: Data fields definition data_fields = { - 'vmp3': [DataField('Ewe', c_float), DataField('Ece', c_float)], - 'sp300': [DataField('Ewe', c_float)], + "vmp3": [DataField("Ewe", c_float), DataField("Ece", c_float)], + "sp300": [DataField("Ewe", c_float)], } - def __init__(self, rest_time_T=10.0, record_every_dE=10.0, - record_every_dT=0.1, E_range='KBIO_ERANGE_AUTO'): + def __init__( + self, + rest_time_T=10.0, + record_every_dE=10.0, + record_every_dT=0.1, + E_range="KBIO_ERANGE_AUTO", + ): """Initialize the OCV technique Args: @@ -1063,15 +1064,12 @@ def __init__(self, rest_time_T=10.0, record_every_dE=10.0, :data:`E_RANGES` module variable for possible values """ args = ( - TechniqueArgument('Rest_time_T', 'single', rest_time_T, '>=', 0), - TechniqueArgument('Record_every_dE', 'single', record_every_dE, - '>=', 0), - TechniqueArgument('Record_every_dT', 'single', record_every_dT, - '>=', 0), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), + TechniqueArgument("Rest_time_T", "single", rest_time_T, ">=", 0), + TechniqueArgument("Record_every_dE", "single", record_every_dE, ">=", 0), + TechniqueArgument("Record_every_dT", "single", record_every_dT, ">=", 0), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), ) - super(OCV, self).__init__(args, 'ocv.ecc') + super(OCV, self).__init__(args, "ocv.ecc") # Section 7.3 in the specification @@ -1089,24 +1087,28 @@ class CV(Technique): #:Data fields definition data_fields = { - 'common': [ - DataField('Ec', c_float), - DataField('I', c_float), - DataField('Ewe', c_float), - DataField('cycle', c_uint32), + "common": [ + DataField("Ec", c_float), + DataField("I", c_float), + DataField("Ewe", c_float), + DataField("cycle", c_uint32), ] } - def __init__(self, vs_initial, voltage_step, scan_rate, - record_every_dE=0.1, - average_over_dE=True, - N_cycles=0, - begin_measuring_I=0.5, - end_measuring_I=1.0, - I_range='KBIO_IRANGE_AUTO', - E_range='KBIO_ERANGE_2_5', - bandwidth='KBIO_BW_5' - ): + def __init__( + self, + vs_initial, + voltage_step, + scan_rate, + record_every_dE=0.1, + average_over_dE=True, + N_cycles=0, + begin_measuring_I=0.5, + end_measuring_I=1.0, + I_range="KBIO_IRANGE_AUTO", + E_range="KBIO_ERANGE_2_5", + bandwidth="KBIO_BW_5", + ): r"""Initialize the CV technique:: E_we @@ -1145,35 +1147,31 @@ def __init__(self, vs_initial, voltage_step, scan_rate, ValueError: If vs_initial, voltage_step and scan_rate are not all of length 5 """ - for input_name in ('vs_initial', 'voltage_step', 'scan_rate'): + for input_name in ("vs_initial", "voltage_step", "scan_rate"): if len(locals()[input_name]) != 5: - message = 'Input \'{}\' must be of length 5, not {}'.format( - input_name, len(locals()[input_name])) + message = "Input '{}' must be of length 5, not {}".format( + input_name, len(locals()[input_name]) + ) raise ValueError(message) args = ( - TechniqueArgument('vs_initial', '[bool]', vs_initial, - 'in', [True, False]), - TechniqueArgument('Voltage_step', '[single]', voltage_step, - None, None), - TechniqueArgument('Scan_Rate', '[single]', scan_rate, '>=', 0.0), - TechniqueArgument('Scan_number', 'integer', 2, None, None), - TechniqueArgument('Record_every_dE', 'single', record_every_dE, - '>=', 0.0), - TechniqueArgument('Average_over_dE', 'bool', average_over_dE, 'in', - [True, False]), - TechniqueArgument('N_Cycles', 'integer', N_cycles, '>=', 0), - TechniqueArgument('Begin_measuring_I', 'single', begin_measuring_I, - 'in_float_range', (0.0, 1.0)), - TechniqueArgument('End_measuring_I', 'single', end_measuring_I, - 'in_float_range', (0.0, 1.0)), - TechniqueArgument('I_Range', I_RANGES, I_range, - 'in', I_RANGES.values()), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), - TechniqueArgument('Bandwidth', BANDWIDTHS, bandwidth, 'in', - BANDWIDTHS.values()), + TechniqueArgument("vs_initial", "[bool]", vs_initial, "in", [True, False]), + TechniqueArgument("Voltage_step", "[single]", voltage_step, None, None), + TechniqueArgument("Scan_Rate", "[single]", scan_rate, ">=", 0.0), + TechniqueArgument("Scan_number", "integer", 2, None, None), + TechniqueArgument("Record_every_dE", "single", record_every_dE, ">=", 0.0), + TechniqueArgument("Average_over_dE", "bool", average_over_dE, "in", [True, False]), + TechniqueArgument("N_Cycles", "integer", N_cycles, ">=", 0), + TechniqueArgument( + "Begin_measuring_I", "single", begin_measuring_I, "in_float_range", (0.0, 1.0) + ), + TechniqueArgument( + "End_measuring_I", "single", end_measuring_I, "in_float_range", (0.0, 1.0) + ), + TechniqueArgument("I_Range", I_RANGES, I_range, "in", I_RANGES.values()), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), + TechniqueArgument("Bandwidth", BANDWIDTHS, bandwidth, "in", BANDWIDTHS.values()), ) - super(CV, self).__init__(args, 'cv.ecc') + super(CV, self).__init__(args, "cv.ecc") # Section 7.4 in the specification @@ -1191,29 +1189,34 @@ class CVA(Technique): #:Data fields definition data_fields = { - 'common': [ - DataField('Ec', c_float), - DataField('I', c_float), - DataField('Ewe', c_float), - DataField('cycle', c_uint32), + "common": [ + DataField("Ec", c_float), + DataField("I", c_float), + DataField("Ewe", c_float), + DataField("cycle", c_uint32), ] } - def __init__(self, # pylint: disable=too-many-locals - vs_initial_scan, voltage_scan, scan_rate, - vs_initial_step, voltage_step, duration_step, - record_every_dE=0.1, - average_over_dE=True, - N_cycles=0, - begin_measuring_I=0.5, - end_measuring_I=1.0, - record_every_dT=0.1, - record_every_dI=1, - trig_on_off=False, - I_range='KBIO_IRANGE_AUTO', - E_range='KBIO_ERANGE_2_5', - bandwidth='KBIO_BW_5' - ): + def __init__( + self, # pylint: disable=too-many-locals + vs_initial_scan, + voltage_scan, + scan_rate, + vs_initial_step, + voltage_step, + duration_step, + record_every_dE=0.1, + average_over_dE=True, + N_cycles=0, + begin_measuring_I=0.5, + end_measuring_I=1.0, + record_every_dT=0.1, + record_every_dI=1, + trig_on_off=False, + I_range="KBIO_IRANGE_AUTO", + E_range="KBIO_ERANGE_2_5", + bandwidth="KBIO_BW_5", + ): r"""Initialize the CVA technique:: E_we @@ -1265,55 +1268,50 @@ def __init__(self, # pylint: disable=too-many-locals ValueError: If vs_initial, voltage_step and scan_rate are not all of length 5 """ - for input_name in ('vs_initial_scan', 'voltage_scan', 'scan_rate'): + for input_name in ("vs_initial_scan", "voltage_scan", "scan_rate"): if len(locals()[input_name]) != 4: - message = 'Input \'{}\' must be of length 4, not {}'.format( - input_name, len(locals()[input_name])) + message = "Input '{}' must be of length 4, not {}".format( + input_name, len(locals()[input_name]) + ) raise ValueError(message) - for input_name in ('vs_initial_step', 'voltage_step', 'duration_step'): + for input_name in ("vs_initial_step", "voltage_step", "duration_step"): if len(locals()[input_name]) != 2: - message = 'Input \'{}\' must be of length 2, not {}'.format( - input_name, len(locals()[input_name])) + message = "Input '{}' must be of length 2, not {}".format( + input_name, len(locals()[input_name]) + ) raise ValueError(message) args = ( - TechniqueArgument('vs_initial_scan', '[bool]', vs_initial_scan, - 'in', [True, False]), - TechniqueArgument('Voltage_scan', '[single]', voltage_scan, - None, None), - TechniqueArgument('Scan_Rate', '[single]', scan_rate, '>=', 0.0), - TechniqueArgument('Scan_number', 'integer', 2, None, None), - TechniqueArgument('Record_every_dE', 'single', record_every_dE, - '>=', 0.0), - TechniqueArgument('Average_over_dE', 'bool', average_over_dE, 'in', - [True, False]), - TechniqueArgument('N_Cycles', 'integer', N_cycles, '>=', 0), - TechniqueArgument('Begin_measuring_I', 'single', begin_measuring_I, - 'in_float_range', (0.0, 1.0)), - TechniqueArgument('End_measuring_I', 'single', end_measuring_I, - 'in_float_range', (0.0, 1.0)), - TechniqueArgument('vs_initial_step', '[bool]', vs_initial_step, - 'in', [True, False]), - TechniqueArgument('Voltage_step', '[single]', voltage_step, - None, None), - TechniqueArgument('Duration_step', '[single]', duration_step, - None, None), - TechniqueArgument('Step_number', 'integer', 1, None, None), - TechniqueArgument('Record_every_dT', 'single', record_every_dT, - '>=', 0.0), - TechniqueArgument('Record_every_dI', 'single', record_every_dI, - '>=', 0.0), - TechniqueArgument('Trig_on_off', 'bool', trig_on_off, - 'in', [True, False]), - TechniqueArgument('I_Range', I_RANGES, I_range, - 'in', I_RANGES.values()), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), - TechniqueArgument('Bandwidth', BANDWIDTHS, bandwidth, 'in', - BANDWIDTHS.values()), + TechniqueArgument( + "vs_initial_scan", "[bool]", vs_initial_scan, "in", [True, False] + ), + TechniqueArgument("Voltage_scan", "[single]", voltage_scan, None, None), + TechniqueArgument("Scan_Rate", "[single]", scan_rate, ">=", 0.0), + TechniqueArgument("Scan_number", "integer", 2, None, None), + TechniqueArgument("Record_every_dE", "single", record_every_dE, ">=", 0.0), + TechniqueArgument("Average_over_dE", "bool", average_over_dE, "in", [True, False]), + TechniqueArgument("N_Cycles", "integer", N_cycles, ">=", 0), + TechniqueArgument( + "Begin_measuring_I", "single", begin_measuring_I, "in_float_range", (0.0, 1.0) + ), + TechniqueArgument( + "End_measuring_I", "single", end_measuring_I, "in_float_range", (0.0, 1.0) + ), + TechniqueArgument( + "vs_initial_step", "[bool]", vs_initial_step, "in", [True, False] + ), + TechniqueArgument("Voltage_step", "[single]", voltage_step, None, None), + TechniqueArgument("Duration_step", "[single]", duration_step, None, None), + TechniqueArgument("Step_number", "integer", 1, None, None), + TechniqueArgument("Record_every_dT", "single", record_every_dT, ">=", 0.0), + TechniqueArgument("Record_every_dI", "single", record_every_dI, ">=", 0.0), + TechniqueArgument("Trig_on_off", "bool", trig_on_off, "in", [True, False]), + TechniqueArgument("I_Range", I_RANGES, I_range, "in", I_RANGES.values()), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), + TechniqueArgument("Bandwidth", BANDWIDTHS, bandwidth, "in", BANDWIDTHS.values()), ) - super(CVA, self).__init__(args, 'biovscan.ecc') + super(CVA, self).__init__(args, "biovscan.ecc") # Section 7.5 in the specification @@ -1330,18 +1328,25 @@ class CP(Technique): #: Data fields definition data_fields = { - 'common': [ - DataField('Ewe', c_float), - DataField('I', c_float), - DataField('cycle', c_uint32), + "common": [ + DataField("Ewe", c_float), + DataField("I", c_float), + DataField("cycle", c_uint32), ] } - def __init__(self, current_step=(50E-6,), vs_initial=(False,), - duration_step=(10.0,), - record_every_dT=0.1, record_every_dE=0.001, - N_cycles=0, I_range='KBIO_IRANGE_100uA', - E_range='KBIO_ERANGE_2_5', bandwidth='KBIO_BW_5'): + def __init__( + self, + current_step=(50e-6,), + vs_initial=(False,), + duration_step=(10.0,), + record_every_dT=0.1, + record_every_dE=0.001, + N_cycles=0, + I_range="KBIO_IRANGE_100uA", + E_range="KBIO_ERANGE_2_5", + bandwidth="KBIO_BW_5", + ): """Initialize the CP technique NOTE: The current_step, vs_initial and duration_step must be a list or @@ -1370,32 +1375,24 @@ def __init__(self, current_step=(50E-6,), vs_initial=(False,), ValueError: On bad lengths for the list arguments """ if not len(current_step) == len(vs_initial) == len(duration_step): - message = 'The length of current_step, vs_initial and '\ - 'duration_step must be the same' + message = ( + "The length of current_step, vs_initial and " "duration_step must be the same" + ) raise ValueError(message) args = ( - TechniqueArgument('Current_step', '[single]', current_step, - None, None), - TechniqueArgument('vs_initial', '[bool]', vs_initial, - 'in', [True, False]), - TechniqueArgument('Duration_step', '[single]', duration_step, - '>=', 0), - TechniqueArgument('Step_number', 'integer', len(current_step), - 'in', range(99)), - TechniqueArgument('Record_every_dT', 'single', record_every_dT, - '>=', 0), - TechniqueArgument('Record_every_dE', 'single', record_every_dE, - '>=', 0), - TechniqueArgument('N_Cycles', 'integer', N_cycles, '>=', 0), - TechniqueArgument('I_Range', I_RANGES, I_range, - 'in', I_RANGES.values()), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), - TechniqueArgument('Bandwidth', BANDWIDTHS, bandwidth, - 'in', BANDWIDTHS.values()), + TechniqueArgument("Current_step", "[single]", current_step, None, None), + TechniqueArgument("vs_initial", "[bool]", vs_initial, "in", [True, False]), + TechniqueArgument("Duration_step", "[single]", duration_step, ">=", 0), + TechniqueArgument("Step_number", "integer", len(current_step), "in", range(99)), + TechniqueArgument("Record_every_dT", "single", record_every_dT, ">=", 0), + TechniqueArgument("Record_every_dE", "single", record_every_dE, ">=", 0), + TechniqueArgument("N_Cycles", "integer", N_cycles, ">=", 0), + TechniqueArgument("I_Range", I_RANGES, I_range, "in", I_RANGES.values()), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), + TechniqueArgument("Bandwidth", BANDWIDTHS, bandwidth, "in", BANDWIDTHS.values()), ) - super(CP, self).__init__(args, 'cp.ecc') + super(CP, self).__init__(args, "cp.ecc") # Section 7.6 in the specification @@ -1412,16 +1409,25 @@ class CA(Technique): #:Data fields definition data_fields = { - 'common': [DataField('Ewe', c_float), - DataField('I', c_float), - DataField('cycle', c_uint32)] + "common": [ + DataField("Ewe", c_float), + DataField("I", c_float), + DataField("cycle", c_uint32), + ] } - def __init__(self, voltage_step=(0.35,), vs_initial=(False,), - duration_step=(10.0,), - record_every_dT=0.1, record_every_dI=5E-6, - N_cycles=0, I_range='KBIO_IRANGE_AUTO', - E_range='KBIO_ERANGE_2_5', bandwidth='KBIO_BW_5'): + def __init__( + self, + voltage_step=(0.35,), + vs_initial=(False,), + duration_step=(10.0,), + record_every_dT=0.1, + record_every_dI=5e-6, + N_cycles=0, + I_range="KBIO_IRANGE_AUTO", + E_range="KBIO_ERANGE_2_5", + bandwidth="KBIO_BW_5", + ): """Initialize the CA technique NOTE: The voltage_step, vs_initial and duration_step must be a list or @@ -1450,32 +1456,24 @@ def __init__(self, voltage_step=(0.35,), vs_initial=(False,), ValueError: On bad lengths for the list arguments """ if not len(voltage_step) == len(vs_initial) == len(duration_step): - message = 'The length of voltage_step, vs_initial and '\ - 'duration_step must be the same' + message = ( + "The length of voltage_step, vs_initial and " "duration_step must be the same" + ) raise ValueError(message) args = ( - TechniqueArgument('Voltage_step', '[single]', voltage_step, - None, None), - TechniqueArgument('vs_initial', '[bool]', vs_initial, - 'in', [True, False]), - TechniqueArgument('Duration_step', '[single]', duration_step, - '>=', 0.0), - TechniqueArgument('Step_number', 'integer', len(voltage_step), - 'in', range(99)), - TechniqueArgument('Record_every_dT', 'single', record_every_dT, - '>=', 0.0), - TechniqueArgument('Record_every_dI', 'single', record_every_dI, - '>=', 0.0), - TechniqueArgument('N_Cycles', 'integer', N_cycles, '>=', 0), - TechniqueArgument('I_Range', I_RANGES, I_range, - 'in', I_RANGES.values()), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), - TechniqueArgument('Bandwidth', BANDWIDTHS, bandwidth, 'in', - BANDWIDTHS.values()), + TechniqueArgument("Voltage_step", "[single]", voltage_step, None, None), + TechniqueArgument("vs_initial", "[bool]", vs_initial, "in", [True, False]), + TechniqueArgument("Duration_step", "[single]", duration_step, ">=", 0.0), + TechniqueArgument("Step_number", "integer", len(voltage_step), "in", range(99)), + TechniqueArgument("Record_every_dT", "single", record_every_dT, ">=", 0.0), + TechniqueArgument("Record_every_dI", "single", record_every_dI, ">=", 0.0), + TechniqueArgument("N_Cycles", "integer", N_cycles, ">=", 0), + TechniqueArgument("I_Range", I_RANGES, I_range, "in", I_RANGES.values()), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), + TechniqueArgument("Bandwidth", BANDWIDTHS, bandwidth, "in", BANDWIDTHS.values()), ) - super(CA, self).__init__(args, 'ca.ecc') + super(CA, self).__init__(args, "ca.ecc") # Section 7.12 in the specification @@ -1515,48 +1513,60 @@ class SPEIS(Technique): #:Data fields definition data_fields = { - 'common': [ - DataField('Ewe', c_float), - DataField('I', c_float), - DataField('step', c_uint32), + "common": [ + DataField("Ewe", c_float), + DataField("I", c_float), + DataField("step", c_uint32), ], - 'no_time': [ - DataField('freq', c_float), - DataField('abs_Ewe', c_float), - DataField('abs_I', c_float), - DataField('Phase_Zwe', c_float), - DataField('Ewe', c_float), - DataField('I', c_float), - DataField('Blank0', c_float), - DataField('abs_Ece', c_float), - DataField('abs_Ice', c_float), - DataField('Phase_Zce', c_float), - DataField('Ece', c_float), - DataField('Blank1', c_float), - DataField('Blank2', c_float), - DataField('t', c_float), + "no_time": [ + DataField("freq", c_float), + DataField("abs_Ewe", c_float), + DataField("abs_I", c_float), + DataField("Phase_Zwe", c_float), + DataField("Ewe", c_float), + DataField("I", c_float), + DataField("Blank0", c_float), + DataField("abs_Ece", c_float), + DataField("abs_Ice", c_float), + DataField("Phase_Zce", c_float), + DataField("Ece", c_float), + DataField("Blank1", c_float), + DataField("Blank2", c_float), + DataField("t", c_float), # The manual says this is a float, but playing around with # strongly suggests that it is an uint corresponding to a I_RANGE - DataField('Irange', c_uint32), + DataField("Irange", c_uint32), # The manual does not mention data conversion for step, but says # that cycle should be an uint, however, this technique does not # have a cycle field, so I assume that it should have been the # step field. Also, the data maskes sense it you interpret it as # an uint. - DataField('step', c_uint32), - ] + DataField("step", c_uint32), + ], } - def __init__(self, # pylint: disable=too-many-locals - vs_initial, vs_final, initial_voltage_step, - final_voltage_step, duration_step, step_number, - record_every_dT=0.1, record_every_dI=5E-6, - final_frequency=100.0E3, initial_frequency=100.0, - sweep=True, amplitude_voltage=0.1, - frequency_number=1, average_n_times=1, - correction=False, wait_for_steady=1.0, - I_range='KBIO_IRANGE_AUTO', - E_range='KBIO_ERANGE_2_5', bandwidth='KBIO_BW_5'): + def __init__( + self, # pylint: disable=too-many-locals + vs_initial, + vs_final, + initial_voltage_step, + final_voltage_step, + duration_step, + step_number, + record_every_dT=0.1, + record_every_dI=5e-6, + final_frequency=100.0e3, + initial_frequency=100.0, + sweep=True, + amplitude_voltage=0.1, + frequency_number=1, + average_n_times=1, + correction=False, + wait_for_steady=1.0, + I_range="KBIO_IRANGE_AUTO", + E_range="KBIO_ERANGE_2_5", + bandwidth="KBIO_BW_5", + ): """Initialize the SPEIS technique Args: @@ -1590,45 +1600,29 @@ def __init__(self, # pylint: disable=too-many-locals ValueError: On bad lengths for the list arguments """ args = ( - TechniqueArgument('vs_initial', 'bool', vs_initial, - 'in', [True, False]), - TechniqueArgument('vs_final', 'bool', vs_final, - 'in', [True, False]), - TechniqueArgument('Initial_Voltage_step', 'single', - initial_voltage_step, None, None), - TechniqueArgument('Final_Voltage_step', 'single', - final_voltage_step, None, None), - TechniqueArgument('Duration_step', 'single', duration_step, - None, None), - TechniqueArgument('Step_number', 'integer', step_number, - 'in', range(99)), - TechniqueArgument('Record_every_dT', 'single', record_every_dT, - '>=', 0.0), - TechniqueArgument('Record_every_dI', 'single', record_every_dI, - '>=', 0.0), - TechniqueArgument('Final_frequency', 'single', final_frequency, - '>=', 0.0), - TechniqueArgument('Initial_frequency', 'single', initial_frequency, - '>=', 0.0), - TechniqueArgument('sweep', 'bool', sweep, 'in', [True, False]), - TechniqueArgument('Amplitude_Voltage', 'single', amplitude_voltage, - None, None), - TechniqueArgument('Frequency_number', 'integer', frequency_number, - '>=', 1), - TechniqueArgument('Average_N_times', 'integer', average_n_times, - '>=', 1), - TechniqueArgument('Correction', 'bool', correction, - 'in', [True, False]), - TechniqueArgument('Wait_for_steady', 'single', wait_for_steady, - '>=', 0.0), - TechniqueArgument('I_Range', I_RANGES, I_range, - 'in', I_RANGES.values()), - TechniqueArgument('E_Range', E_RANGES, E_range, - 'in', E_RANGES.values()), - TechniqueArgument('Bandwidth', BANDWIDTHS, bandwidth, 'in', - BANDWIDTHS.values()), + TechniqueArgument("vs_initial", "bool", vs_initial, "in", [True, False]), + TechniqueArgument("vs_final", "bool", vs_final, "in", [True, False]), + TechniqueArgument( + "Initial_Voltage_step", "single", initial_voltage_step, None, None + ), + TechniqueArgument("Final_Voltage_step", "single", final_voltage_step, None, None), + TechniqueArgument("Duration_step", "single", duration_step, None, None), + TechniqueArgument("Step_number", "integer", step_number, "in", range(99)), + TechniqueArgument("Record_every_dT", "single", record_every_dT, ">=", 0.0), + TechniqueArgument("Record_every_dI", "single", record_every_dI, ">=", 0.0), + TechniqueArgument("Final_frequency", "single", final_frequency, ">=", 0.0), + TechniqueArgument("Initial_frequency", "single", initial_frequency, ">=", 0.0), + TechniqueArgument("sweep", "bool", sweep, "in", [True, False]), + TechniqueArgument("Amplitude_Voltage", "single", amplitude_voltage, None, None), + TechniqueArgument("Frequency_number", "integer", frequency_number, ">=", 1), + TechniqueArgument("Average_N_times", "integer", average_n_times, ">=", 1), + TechniqueArgument("Correction", "bool", correction, "in", [True, False]), + TechniqueArgument("Wait_for_steady", "single", wait_for_steady, ">=", 0.0), + TechniqueArgument("I_Range", I_RANGES, I_range, "in", I_RANGES.values()), + TechniqueArgument("E_Range", E_RANGES, E_range, "in", E_RANGES.values()), + TechniqueArgument("Bandwidth", BANDWIDTHS, bandwidth, "in", BANDWIDTHS.values()), ) - super(SPEIS, self).__init__(args, 'seisp.ecc') + super(SPEIS, self).__init__(args, "seisp.ecc") # Section 7.28 in the specification @@ -1647,167 +1641,172 @@ def __init__(self, rcmp_value): Args: rcmp_value (float): The R value to compensate """ - args = ( - TechniqueArgument('Rcmp_Value', 'single', rcmp_value, '>=', 0.0), - ) - super(MIR, self).__init__(args, 'IRcmp.ecc') + args = (TechniqueArgument("Rcmp_Value", "single", rcmp_value, ">=", 0.0),) + super(MIR, self).__init__(args, "IRcmp.ecc") ########## Structs class DeviceInfos(Structure): """Device information struct""" + _fields_ = [ # Translated to string with DEVICE_CODES - ('DeviceCode', c_int32), - ('RAMsize', c_int32), - ('CPU', c_int32), - ('NumberOfChannels', c_int32), - ('NumberOfSlots', c_int32), - ('FirmwareVersion', c_int32), - ('FirmwareDate_yyyy', c_int32), - ('FirmwareDate_mm', c_int32), - ('FirmwareDate_dd', c_int32), - ('HTdisplayOn', c_int32), - ('NbOfConnectedPC', c_int32)] + ("DeviceCode", c_int32), + ("RAMsize", c_int32), + ("CPU", c_int32), + ("NumberOfChannels", c_int32), + ("NumberOfSlots", c_int32), + ("FirmwareVersion", c_int32), + ("FirmwareDate_yyyy", c_int32), + ("FirmwareDate_mm", c_int32), + ("FirmwareDate_dd", c_int32), + ("HTdisplayOn", c_int32), + ("NbOfConnectedPC", c_int32), + ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) class ChannelInfos(Structure): """Channel information structure""" + _fields_ = [ - ('Channel', c_int32), - ('BoardVersion', c_int32), - ('BoardSerialNumber', c_int32), + ("Channel", c_int32), + ("BoardVersion", c_int32), + ("BoardSerialNumber", c_int32), # Translated to string with FIRMWARE_CODES - ('FirmwareCode', c_int32), - ('FirmwareVersion', c_int32), - ('XilinxVersion', c_int32), + ("FirmwareCode", c_int32), + ("FirmwareVersion", c_int32), + ("XilinxVersion", c_int32), # Translated to string with AMP_CODES - ('AmpCode', c_int32), + ("AmpCode", c_int32), # NbAmp is not mentioned in the documentation, but is in # in the examples and the info does not make sense # without it - ('NbAmp', c_int32), - ('LCboard', c_int32), - ('Zboard', c_int32), - ('MUXboard', c_int32), - ('GPRAboard', c_int32), - ('MemSize', c_int32), - ('MemFilled', c_int32), + ("NbAmp", c_int32), + ("LCboard", c_int32), + ("Zboard", c_int32), + ("MUXboard", c_int32), + ("GPRAboard", c_int32), + ("MemSize", c_int32), + ("MemFilled", c_int32), # Translated to string with STATES - ('State', c_int32), + ("State", c_int32), # Translated to string with MAX_I_RANGES - ('MaxIRange', c_int32), + ("MaxIRange", c_int32), # Translated to string with MIN_I_RANGES - ('MinIRange', c_int32), + ("MinIRange", c_int32), # Translated to string with MAX_BANDWIDTHS - ('MaxBandwidth', c_int32), - ('NbOfTechniques', c_int32), + ("MaxBandwidth", c_int32), + ("NbOfTechniques", c_int32), ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) class CurrentValues(Structure): """Current values structure""" + _fields_ = [ # Translate to string with STATES - ('State', c_int32), # Channel state - ('MemFilled', c_int32), # Memory filled (in Bytes) - ('TimeBase', c_float), # Time base (s) - ('Ewe', c_float), # Working electrode potential (V) - ('EweRangeMin', c_float), # Ewe min range (V) - ('EweRangeMax', c_float), # Ewe max range (V) - ('Ece', c_float), # Counter electrode potential (V) - ('EceRangeMin', c_float), # Ece min range (V) - ('EceRangeMax', c_float), # Ece max range (V) - ('Eoverflow', c_int32), # Potential overflow - ('I', c_float), # Current value (A) + ("State", c_int32), # Channel state + ("MemFilled", c_int32), # Memory filled (in Bytes) + ("TimeBase", c_float), # Time base (s) + ("Ewe", c_float), # Working electrode potential (V) + ("EweRangeMin", c_float), # Ewe min range (V) + ("EweRangeMax", c_float), # Ewe max range (V) + ("Ece", c_float), # Counter electrode potential (V) + ("EceRangeMin", c_float), # Ece min range (V) + ("EceRangeMax", c_float), # Ece max range (V) + ("Eoverflow", c_int32), # Potential overflow + ("I", c_float), # Current value (A) # Translate to string with IRANGE - ('IRange', c_int32), # Current range - ('Ioverflow', c_int32), # Current overflow - ('ElapsedTime', c_float), # Elapsed time - ('Freq', c_float), # Frequency (Hz) - ('Rcomp', c_float), # R-compenzation (Ohm) - ('Saturation', c_int32), # E or/and I saturation + ("IRange", c_int32), # Current range + ("Ioverflow", c_int32), # Current overflow + ("ElapsedTime", c_float), # Elapsed time + ("Freq", c_float), # Frequency (Hz) + ("Rcomp", c_float), # R-compenzation (Ohm) + ("Saturation", c_int32), # E or/and I saturation ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) class DataInfos(Structure): """DataInfos structure""" + _fields_ = [ - ('IRQskipped', c_int32), # Number of IRQ skipped - ('NbRaws', c_int32), # Number of raws into the data buffer, - # i.e. number of points saced in the - # data buffer - ('NbCols', c_int32), # Number of columns into the data - # buffer, i.e. number of variables - # defining a point in the data buffer - ('TechniqueIndex', c_int32), # Index (0-based) of the - # technique that has generated - # the data - ('TechniqueID', c_int32), # Identifier of the technique that - # has generated the data - ('ProcessIndex', c_int32), # Index (0-based) of the process - # of the technique that ahs - # generated the data - ('loop', c_int32), # Loop number - ('StartTime', c_double), # Start time (s) + ("IRQskipped", c_int32), # Number of IRQ skipped + ("NbRaws", c_int32), # Number of raws into the data buffer, + # i.e. number of points saced in the + # data buffer + ("NbCols", c_int32), # Number of columns into the data + # buffer, i.e. number of variables + # defining a point in the data buffer + ("TechniqueIndex", c_int32), # Index (0-based) of the + # technique that has generated + # the data + ("TechniqueID", c_int32), # Identifier of the technique that + # has generated the data + ("ProcessIndex", c_int32), # Index (0-based) of the process + # of the technique that ahs + # generated the data + ("loop", c_int32), # Loop number + ("StartTime", c_double), # Start time (s) ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) class TECCParam(Structure): """Technique parameter""" + _fields_ = [ - ('ParamStr', c_char * 64), - ('ParamType', c_int32), - ('ParamVal', c_int32), - ('ParamIndex', c_int32), + ("ParamStr", c_char * 64), + ("ParamType", c_int32), + ("ParamVal", c_int32), + ("ParamIndex", c_int32), ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) class TECCParams(Structure): """Technique parameters""" + _fields_ = [ - ('len', c_int32), - ('pParams', POINTER(TECCParam)), + ("len", c_int32), + ("pParams", POINTER(TECCParam)), ] # Hack to include the fields names in doc string (and Sphinx documentation) - __doc__ += '\n\n Fields:\n\n' + '\n'.join( - [' * {} {}'.format(*field) for field in _fields_] + __doc__ += "\n\n Fields:\n\n" + "\n".join( + [" * {} {}".format(*field) for field in _fields_] ) ########## Exceptions class ECLibException(Exception): """Base exception for all ECLib exceptions""" + def __init__(self, message, error_code): super(ECLibException, self).__init__(message) self.error_code = error_code def __str__(self): """__str__ representation of the ECLibException""" - string = '{} code: {}. Message \'{}\''.format( - self.__class__.__name__, - self.error_code, - self.message) + string = "{} code: {}. Message '{}'".format( + self.__class__.__name__, self.error_code, self.message + ) return string def __repr__(self): @@ -1817,12 +1816,14 @@ def __repr__(self): class ECLibError(ECLibException): """Exception for ECLib errors""" + def __init__(self, message, error_code): super(ECLibError, self).__init__(message, error_code) class ECLibCustomException(ECLibException): """Exceptions that does not originate from the lib""" + def __init__(self, message, error_code): super(ECLibCustomException, self).__init__(message, error_code) @@ -1844,202 +1845,202 @@ def reverse_dict(dict_): ########## Constants #:Device number to device name translation dict DEVICE_CODES = { - 0: 'KBIO_DEV_VMP', - 1: 'KBIO_DEV_VMP2', - 2: 'KBIO_DEV_MPG', - 3: 'KBIO_DEV_BISTA', - 4: 'KBIO_DEV_MCS200', - 5: 'KBIO_DEV_VMP3', - 6: 'KBIO_DEV_VSP', - 7: 'KBIO_DEV_HCP803', - 8: 'KBIO_DEV_EPP400', - 9: 'KBIO_DEV_EPP4000', - 10: 'KBIO_DEV_BISTAT2', - 11: 'KBIO_DEV_FCT150S', - 12: 'KBIO_DEV_VMP300', - 13: 'KBIO_DEV_SP50', - 14: 'KBIO_DEV_SP150', - 15: 'KBIO_DEV_FCT50S', - 16: 'KBIO_DEV_SP300', - 17: 'KBIO_DEV_CLB500', - 18: 'KBIO_DEV_HCP1005', - 19: 'KBIO_DEV_CLB2000', - 20: 'KBIO_DEV_VSP300', - 21: 'KBIO_DEV_SP200', - 22: 'KBIO_DEV_MPG2', - 23: 'KBIO_DEV_SP100', - 24: 'KBIO_DEV_MOSLED', - 27: 'KBIO_DEV_SP240', - 255: 'KBIO_DEV_UNKNOWN' + 0: "KBIO_DEV_VMP", + 1: "KBIO_DEV_VMP2", + 2: "KBIO_DEV_MPG", + 3: "KBIO_DEV_BISTA", + 4: "KBIO_DEV_MCS200", + 5: "KBIO_DEV_VMP3", + 6: "KBIO_DEV_VSP", + 7: "KBIO_DEV_HCP803", + 8: "KBIO_DEV_EPP400", + 9: "KBIO_DEV_EPP4000", + 10: "KBIO_DEV_BISTAT2", + 11: "KBIO_DEV_FCT150S", + 12: "KBIO_DEV_VMP300", + 13: "KBIO_DEV_SP50", + 14: "KBIO_DEV_SP150", + 15: "KBIO_DEV_FCT50S", + 16: "KBIO_DEV_SP300", + 17: "KBIO_DEV_CLB500", + 18: "KBIO_DEV_HCP1005", + 19: "KBIO_DEV_CLB2000", + 20: "KBIO_DEV_VSP300", + 21: "KBIO_DEV_SP200", + 22: "KBIO_DEV_MPG2", + 23: "KBIO_DEV_SP100", + 24: "KBIO_DEV_MOSLED", + 27: "KBIO_DEV_SP240", + 255: "KBIO_DEV_UNKNOWN", } #:Firmware number to firmware name translation dict FIRMWARE_CODES = { - 0: 'KBIO_FIRM_NONE', - 1: 'KBIO_FIRM_INTERPR', - 4: 'KBIO_FIRM_UNKNOWN', - 5: 'KBIO_FIRM_KERNEL', - 8: 'KBIO_FIRM_INVALID', - 10: 'KBIO_FIRM_ECAL' + 0: "KBIO_FIRM_NONE", + 1: "KBIO_FIRM_INTERPR", + 4: "KBIO_FIRM_UNKNOWN", + 5: "KBIO_FIRM_KERNEL", + 8: "KBIO_FIRM_INVALID", + 10: "KBIO_FIRM_ECAL", } #:Amplifier number to aplifier name translation dict AMP_CODES = { - 0: 'KBIO_AMPL_NONE', - 1: 'KBIO_AMPL_2A', - 2: 'KBIO_AMPL_1A', - 3: 'KBIO_AMPL_5A', - 4: 'KBIO_AMPL_10A', - 5: 'KBIO_AMPL_20A', - 6: 'KBIO_AMPL_HEUS', - 7: 'KBIO_AMPL_LC', - 8: 'KBIO_AMPL_80A', - 9: 'KBIO_AMPL_4AI', - 10: 'KBIO_AMPL_PAC', - 11: 'KBIO_AMPL_4AI_VSP', - 12: 'KBIO_AMPL_LC_VSP', - 13: 'KBIO_AMPL_UNDEF', - 14: 'KBIO_AMPL_MUIC', - 15: 'KBIO_AMPL_NONE_GIL', - 16: 'KBIO_AMPL_8AI', - 17: 'KBIO_AMPL_LB500', - 18: 'KBIO_AMPL_100A5V', - 19: 'KBIO_AMPL_LB2000', - 20: 'KBIO_AMPL_1A48V', - 21: 'KBIO_AMPL_4A10V' + 0: "KBIO_AMPL_NONE", + 1: "KBIO_AMPL_2A", + 2: "KBIO_AMPL_1A", + 3: "KBIO_AMPL_5A", + 4: "KBIO_AMPL_10A", + 5: "KBIO_AMPL_20A", + 6: "KBIO_AMPL_HEUS", + 7: "KBIO_AMPL_LC", + 8: "KBIO_AMPL_80A", + 9: "KBIO_AMPL_4AI", + 10: "KBIO_AMPL_PAC", + 11: "KBIO_AMPL_4AI_VSP", + 12: "KBIO_AMPL_LC_VSP", + 13: "KBIO_AMPL_UNDEF", + 14: "KBIO_AMPL_MUIC", + 15: "KBIO_AMPL_NONE_GIL", + 16: "KBIO_AMPL_8AI", + 17: "KBIO_AMPL_LB500", + 18: "KBIO_AMPL_100A5V", + 19: "KBIO_AMPL_LB2000", + 20: "KBIO_AMPL_1A48V", + 21: "KBIO_AMPL_4A10V", } #:I range number to I range name translation dict I_RANGES = { - 0: 'KBIO_IRANGE_100pA', - 1: 'KBIO_IRANGE_1nA', - 2: 'KBIO_IRANGE_10nA', - 3: 'KBIO_IRANGE_100nA', - 4: 'KBIO_IRANGE_1uA', - 5: 'KBIO_IRANGE_10uA', - 6: 'KBIO_IRANGE_100uA', - 7: 'KBIO_IRANGE_1mA', - 8: 'KBIO_IRANGE_10mA', - 9: 'KBIO_IRANGE_100mA', - 10: 'KBIO_IRANGE_1A', - 11: 'KBIO_IRANGE_BOOSTER', - 12: 'KBIO_IRANGE_AUTO', - 13: 'KBIO_IRANGE_10pA', # IRANGE_100pA + Igain x10 - 14: 'KBIO_IRANGE_1pA', # IRANGE_100pA + Igain x100 + 0: "KBIO_IRANGE_100pA", + 1: "KBIO_IRANGE_1nA", + 2: "KBIO_IRANGE_10nA", + 3: "KBIO_IRANGE_100nA", + 4: "KBIO_IRANGE_1uA", + 5: "KBIO_IRANGE_10uA", + 6: "KBIO_IRANGE_100uA", + 7: "KBIO_IRANGE_1mA", + 8: "KBIO_IRANGE_10mA", + 9: "KBIO_IRANGE_100mA", + 10: "KBIO_IRANGE_1A", + 11: "KBIO_IRANGE_BOOSTER", + 12: "KBIO_IRANGE_AUTO", + 13: "KBIO_IRANGE_10pA", # IRANGE_100pA + Igain x10 + 14: "KBIO_IRANGE_1pA", # IRANGE_100pA + Igain x100 } #:Bandwidth number to bandwidth name translation dict BANDWIDTHS = { - 1: 'KBIO_BW_1', - 2: 'KBIO_BW_2', - 3: 'KBIO_BW_3', - 4: 'KBIO_BW_4', - 5: 'KBIO_BW_5', - 6: 'KBIO_BW_6', - 7: 'KBIO_BW_7', - 8: 'KBIO_BW_8', - 9: 'KBIO_BW_9' + 1: "KBIO_BW_1", + 2: "KBIO_BW_2", + 3: "KBIO_BW_3", + 4: "KBIO_BW_4", + 5: "KBIO_BW_5", + 6: "KBIO_BW_6", + 7: "KBIO_BW_7", + 8: "KBIO_BW_8", + 9: "KBIO_BW_9", } #:E range number to E range name translation dict E_RANGES = { - 0: 'KBIO_ERANGE_2_5', - 1: 'KBIO_ERANGE_5', - 2: 'KBIO_ERANGE_10', - 3: 'KBIO_ERANGE_AUTO' + 0: "KBIO_ERANGE_2_5", + 1: "KBIO_ERANGE_5", + 2: "KBIO_ERANGE_10", + 3: "KBIO_ERANGE_AUTO", } #:State number to state name translation dict -STATES = { - 0: 'KBIO_STATE_STOP', - 1: 'KBIO_STATE_RUN', - 2: 'KBIO_STATE_PAUSE' -} +STATES = {0: "KBIO_STATE_STOP", 1: "KBIO_STATE_RUN", 2: "KBIO_STATE_PAUSE"} #:Technique number to technique name translation dict TECHNIQUE_IDENTIFIERS = { - 0: 'KBIO_TECHID_NONE', - 100: 'KBIO_TECHID_OCV', - 101: 'KBIO_TECHID_CA', - 102: 'KBIO_TECHID_CP', - 103: 'KBIO_TECHID_CV', - 104: 'KBIO_TECHID_PEIS', - 105: 'KBIO_TECHID_POTPULSE', - 106: 'KBIO_TECHID_GALPULSE', - 107: 'KBIO_TECHID_GEIS', - 108: 'KBIO_TECHID_STACKPEIS_SLAVE', - 109: 'KBIO_TECHID_STACKPEIS', - 110: 'KBIO_TECHID_CPOWER', - 111: 'KBIO_TECHID_CLOAD', - 112: 'KBIO_TECHID_FCT', - 113: 'KBIO_TECHID_SPEIS', - 114: 'KBIO_TECHID_SGEIS', - 115: 'KBIO_TECHID_STACKPDYN', - 116: 'KBIO_TECHID_STACKPDYN_SLAVE', - 117: 'KBIO_TECHID_STACKGDYN', - 118: 'KBIO_TECHID_STACKGEIS_SLAVE', - 119: 'KBIO_TECHID_STACKGEIS', - 120: 'KBIO_TECHID_STACKGDYN_SLAVE', - 121: 'KBIO_TECHID_CPO', - 122: 'KBIO_TECHID_CGA', - 123: 'KBIO_TECHID_COKINE', - 124: 'KBIO_TECHID_PDYN', - 125: 'KBIO_TECHID_GDYN', - 126: 'KBIO_TECHID_CVA', - 127: 'KBIO_TECHID_DPV', - 128: 'KBIO_TECHID_SWV', - 129: 'KBIO_TECHID_NPV', - 130: 'KBIO_TECHID_RNPV', - 131: 'KBIO_TECHID_DNPV', - 132: 'KBIO_TECHID_DPA', - 133: 'KBIO_TECHID_EVT', - 134: 'KBIO_TECHID_LP', - 135: 'KBIO_TECHID_GC', - 136: 'KBIO_TECHID_CPP', - 137: 'KBIO_TECHID_PDP', - 138: 'KBIO_TECHID_PSP', - 139: 'KBIO_TECHID_ZRA', - 140: 'KBIO_TECHID_MIR', - 141: 'KBIO_TECHID_PZIR', - 142: 'KBIO_TECHID_GZIR', - 150: 'KBIO_TECHID_LOOP', - 151: 'KBIO_TECHID_TO', - 152: 'KBIO_TECHID_TI', - 153: 'KBIO_TECHID_TOS', - 155: 'KBIO_TECHID_CPLIMIT', - 156: 'KBIO_TECHID_GDYNLIMIT', - 157: 'KBIO_TECHID_CALIMIT', - 158: 'KBIO_TECHID_PDYNLIMIT', - 159: 'KBIO_TECHID_LASV', - 167: 'KBIO_TECHID_MP', - 169: 'KBIO_TECHID_CASG', - 170: 'KBIO_TECHID_CASP', + 0: "KBIO_TECHID_NONE", + 100: "KBIO_TECHID_OCV", + 101: "KBIO_TECHID_CA", + 102: "KBIO_TECHID_CP", + 103: "KBIO_TECHID_CV", + 104: "KBIO_TECHID_PEIS", + 105: "KBIO_TECHID_POTPULSE", + 106: "KBIO_TECHID_GALPULSE", + 107: "KBIO_TECHID_GEIS", + 108: "KBIO_TECHID_STACKPEIS_SLAVE", + 109: "KBIO_TECHID_STACKPEIS", + 110: "KBIO_TECHID_CPOWER", + 111: "KBIO_TECHID_CLOAD", + 112: "KBIO_TECHID_FCT", + 113: "KBIO_TECHID_SPEIS", + 114: "KBIO_TECHID_SGEIS", + 115: "KBIO_TECHID_STACKPDYN", + 116: "KBIO_TECHID_STACKPDYN_SLAVE", + 117: "KBIO_TECHID_STACKGDYN", + 118: "KBIO_TECHID_STACKGEIS_SLAVE", + 119: "KBIO_TECHID_STACKGEIS", + 120: "KBIO_TECHID_STACKGDYN_SLAVE", + 121: "KBIO_TECHID_CPO", + 122: "KBIO_TECHID_CGA", + 123: "KBIO_TECHID_COKINE", + 124: "KBIO_TECHID_PDYN", + 125: "KBIO_TECHID_GDYN", + 126: "KBIO_TECHID_CVA", + 127: "KBIO_TECHID_DPV", + 128: "KBIO_TECHID_SWV", + 129: "KBIO_TECHID_NPV", + 130: "KBIO_TECHID_RNPV", + 131: "KBIO_TECHID_DNPV", + 132: "KBIO_TECHID_DPA", + 133: "KBIO_TECHID_EVT", + 134: "KBIO_TECHID_LP", + 135: "KBIO_TECHID_GC", + 136: "KBIO_TECHID_CPP", + 137: "KBIO_TECHID_PDP", + 138: "KBIO_TECHID_PSP", + 139: "KBIO_TECHID_ZRA", + 140: "KBIO_TECHID_MIR", + 141: "KBIO_TECHID_PZIR", + 142: "KBIO_TECHID_GZIR", + 150: "KBIO_TECHID_LOOP", + 151: "KBIO_TECHID_TO", + 152: "KBIO_TECHID_TI", + 153: "KBIO_TECHID_TOS", + 155: "KBIO_TECHID_CPLIMIT", + 156: "KBIO_TECHID_GDYNLIMIT", + 157: "KBIO_TECHID_CALIMIT", + 158: "KBIO_TECHID_PDYNLIMIT", + 159: "KBIO_TECHID_LASV", + 167: "KBIO_TECHID_MP", + 169: "KBIO_TECHID_CASG", + 170: "KBIO_TECHID_CASP", } #:Technique name to technique class translation dict. IMPORTANT. Add newly #:implemented techniques to this dictionary TECHNIQUE_IDENTIFIERS_TO_CLASS = { - 'KBIO_TECHID_OCV': OCV, - 'KBIO_TECHID_CP': CP, - 'KBIO_TECHID_CA': CA, - 'KBIO_TECHID_CV': CV, - 'KBIO_TECHID_CVA': CVA, - 'KBIO_TECHID_SPEIS': SPEIS, + "KBIO_TECHID_OCV": OCV, + "KBIO_TECHID_CP": CP, + "KBIO_TECHID_CA": CA, + "KBIO_TECHID_CV": CV, + "KBIO_TECHID_CVA": CVA, + "KBIO_TECHID_SPEIS": SPEIS, } #:List of devices in the WMP4/SP300 series SP300SERIES = [ - 'KBIO_DEV_SP100', 'KBIO_DEV_SP200', 'KBIO_DEV_SP300', 'KBIO_DEV_VSP300', - 'KBIO_DEV_VMP300', 'KBIO_DEV_SP240' + "KBIO_DEV_SP100", + "KBIO_DEV_SP200", + "KBIO_DEV_SP300", + "KBIO_DEV_VSP300", + "KBIO_DEV_VMP300", + "KBIO_DEV_SP240", ] # Hack to make links for classes in the documentation -__doc__ += '\n\nInstrument classes:\n' # pylint: disable=W0622 +__doc__ += "\n\nInstrument classes:\n" # pylint: disable=W0622 for name, klass in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(klass, GeneralPotentiostat) or klass is GeneralPotentiostat: - __doc__ += ' * :class:`.{.__name__}`\n'.format(klass) + __doc__ += " * :class:`.{.__name__}`\n".format(klass) -__doc__ += '\n\nTechniques:\n' +__doc__ += "\n\nTechniques:\n" for name, klass in inspect.getmembers(sys.modules[__name__], inspect.isclass): if issubclass(klass, Technique): - __doc__ += ' * :class:`.{.__name__}`\n'.format(klass) + __doc__ += " * :class:`.{.__name__}`\n".format(klass) diff --git a/PyExpLabSys/drivers/bosch_bme280.py b/PyExpLabSys/drivers/bosch_bme280.py index a1a9f037..36e61161 100644 --- a/PyExpLabSys/drivers/bosch_bme280.py +++ b/PyExpLabSys/drivers/bosch_bme280.py @@ -1,5 +1,6 @@ import time import smbus + # from ctypes import c_short # from ctypes import c_byte # from ctypes import c_ubyte @@ -11,6 +12,7 @@ class BoschBME280(object): Hint for implementation found at http://forum.arduino.cc/index.php?topic=285116.0 """ + def __init__(self): self.bus = smbus.SMBus(1) self.device_address = 0x77 @@ -19,21 +21,21 @@ def __init__(self): # oversample_hum = 0b00000011 # Oversampling x 4 # oversample_hum = 0b00000100 # Oversampling x 8 oversample_hum = 0b00000101 # Oversampling x 16 - self.bus.write_byte_data(self.device_address, 0xf2, oversample_hum) + self.bus.write_byte_data(self.device_address, 0xF2, oversample_hum) # Oversample setting - same as above oversample_temp = 0b00000101 oversample_pres = 0b00000101 mode = 0b11 # Normal mode control = oversample_temp << 5 | oversample_pres << 2 | mode - self.bus.write_byte_data(self.device_address, 0xf4, control) + self.bus.write_byte_data(self.device_address, 0xF4, control) # 0b111: 20ms, 0b010: 125ms, 0b011: 250ms, 0b101: 1000ms standby = 0b111 filter_set = 0b0 # off - 0b001: 2, 0b100: 16 config = standby << 5 | filter_set << 2 - self.bus.write_byte_data(self.device_address, 0xf5, config) + self.bus.write_byte_data(self.device_address, 0xF5, config) self._read_calibration() def _read_calibration(self): @@ -42,10 +44,10 @@ def _read_calibration(self): cal1 = self.bus.read_i2c_block_data(self.device_address, 0x88, 24) self.T = {} - self.T['1'] = cal1[1] * 256 + cal1[0] + self.T["1"] = cal1[1] * 256 + cal1[0] # Principally these are signed, consider check - self.T['2'] = cal1[3] * 256 + cal1[2] - self.T['3'] = cal1[5] * 256 + cal1[4] + self.T["2"] = cal1[3] * 256 + cal1[2] + self.T["3"] = cal1[5] * 256 + cal1[4] self.P = {} self.P[1] = cal1[7] * 256 + cal1[6] @@ -74,11 +76,14 @@ def _read_calibration(self): self.H[6] = cal3[6] def _calculate_temperature(self, temp_raw): - var1 = (temp_raw / 16384 - self.T['1'] / 1024) * self.T['2'] + var1 = (temp_raw / 16384 - self.T["1"] / 1024) * self.T["2"] # Simplify! var2 = ( - (temp_raw / 16 - self.T['1']) * (temp_raw / 16 - self.T['1']) - ) / 4096 * self.T['3'] / 2**14 + ((temp_raw / 16 - self.T["1"]) * (temp_raw / 16 - self.T["1"])) + / 4096 + * self.T["3"] + / 2**14 + ) t_fine = var1 + var2 # Which is correct?!? @@ -90,10 +95,11 @@ def _calculate_temperature(self, temp_raw): def _calculate_pressure(self, pres_raw, t_fine): v1 = t_fine / 2.0 - 64000.0 v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * self.P[6] - v2 += ((v1 * self.P[5]) * 2.0) + v2 += (v1 * self.P[5]) * 2.0 v2 = (v2 / 4.0) + (self.P[4] * 65536.0) - v1 = (((self.P[3] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8) + - ((self.P[2] * v1) / 2.0)) / 262144 + v1 = ( + ((self.P[3] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8) + ((self.P[2] * v1) / 2.0) + ) / 262144 v1 = ((32768 + v1) * self.P[1]) / 32768 if v1 == 0: @@ -112,9 +118,11 @@ def _calculate_pressure(self, pres_raw, t_fine): def _calculate_humidity(self, hum_raw, t_fine): humidity = t_fine - 76800.0 - humidity = ((hum_raw - (self.H[4] * 64.0 + self.H[5] / 2**14 * humidity)) * - (self.H[2] / 2**16 * (1.0 + self.H[6] / 2**26 * humidity * - (1.0 + self.H[3] / 2**26 * humidity)))) + humidity = (hum_raw - (self.H[4] * 64.0 + self.H[5] / 2**14 * humidity)) * ( + self.H[2] + / 2**16 + * (1.0 + self.H[6] / 2**26 * humidity * (1.0 + self.H[3] / 2**26 * humidity)) + ) humidity = humidity * (1.0 - self.H[1] * humidity / 2**19) if humidity > 100: humidity = 100 @@ -123,13 +131,12 @@ def _calculate_humidity(self, hum_raw, t_fine): return humidity def _read_ids(self): - (nr, version) = self.bus.read_i2c_block_data( - self.device_address, 0xD0, 2) + (nr, version) = self.bus.read_i2c_block_data(self.device_address, 0xD0, 2) # time.sleep(0.1) - return {'id_number:': nr, 'version': version} + return {"id_number:": nr, "version": version} def measuring(self): - data = self.bus.read_i2c_block_data(self.device_address, 0xf3, 1) + data = self.bus.read_i2c_block_data(self.device_address, 0xF3, 1) measure_bit = data[0] >> 3 return measure_bit @@ -139,7 +146,7 @@ def read_all_values(self): while self.measuring(): time.sleep(0.01) - data = self.bus.read_i2c_block_data(self.device_address, 0xf7, 8) + data = self.bus.read_i2c_block_data(self.device_address, 0xF7, 8) pres_raw = data[0] * 4096 + data[1] * 16 + data[2] // 16 temp_raw = data[3] * 4096 + data[4] * 16 + data[5] // 16 hum_raw = data[6] * 256 + data[7] @@ -149,24 +156,20 @@ def read_all_values(self): humidity = self._calculate_humidity(hum_raw, t_fine) return_dict = { - 'temperature': temperature, - 'humidity': humidity, - 'air_pressure': air_pressure + "temperature": temperature, + "humidity": humidity, + "air_pressure": air_pressure, } return return_dict -if __name__ == '__main__': +if __name__ == "__main__": BOSCH = BoschBME280() t = time.time() iterations = 20 for i in range(0, iterations): values = BOSCH.read_all_values() - msg = 'Temperature: {:.2f}C, Pressure: {:.2f}Pa, Humidity: {:.2f}%' - print(msg.format( - values['temperature'], - values['air_pressure'], - values['humidity']) - ) - print('Time pr iteration: {:.3f}s'.format((time.time() - t) / iterations)) + msg = "Temperature: {:.2f}C, Pressure: {:.2f}Pa, Humidity: {:.2f}%" + print(msg.format(values["temperature"], values["air_pressure"], values["humidity"])) + print("Time pr iteration: {:.3f}s".format((time.time() - t) / iterations)) diff --git a/PyExpLabSys/drivers/bronkhorst.py b/PyExpLabSys/drivers/bronkhorst.py index 0afe5d78..dfee92c8 100755 --- a/PyExpLabSys/drivers/bronkhorst.py +++ b/PyExpLabSys/drivers/bronkhorst.py @@ -4,40 +4,42 @@ import sys import serial from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) class Bronkhorst(object): - """ Driver for Bronkhorst flow controllers """ + """Driver for Bronkhorst flow controllers""" + def __init__(self, port, max_flow): self.ser = serial.Serial(port, 38400, timeout=2) self.max_setting = max_flow time.sleep(0.25) ser = self.read_serial() if len(ser) < 3: - raise Exception('MfcNotFound') + raise Exception("MfcNotFound") def comm(self, command): - """ Send commands to device and recieve reply """ - self.ser.write(command.encode('ascii')) + """Send commands to device and recieve reply""" + self.ser.write(command.encode("ascii")) time.sleep(0.1) return_string = self.ser.read(self.ser.inWaiting()) return_string = return_string.decode() return return_string def read_setpoint(self): - """ Read the current setpoint """ - read_setpoint = ':06800401210121\r\n' # Read setpoint + """Read the current setpoint""" + read_setpoint = ":06800401210121\r\n" # Read setpoint response = self.comm(read_setpoint) response = int(response[11:], 16) response = (response / 32000.0) * self.max_setting return response def read_flow(self): - """ Read the actual flow """ + """Read the actual flow""" error = 0 while error < 10: - read_pressure = ':06800401210120\r\n' # Read pressure + read_pressure = ":06800401210120\r\n" # Read pressure val = self.comm(read_pressure) try: val = val[-6:] @@ -50,86 +52,86 @@ def read_flow(self): return pressure def set_flow(self, setpoint): - """ Set the desired setpoint, which could be a pressure """ + """Set the desired setpoint, which could be a pressure""" setpoint = float(setpoint) if setpoint > 0: setpoint = (1.0 * setpoint / self.max_setting) * 32000 setpoint = hex(int(setpoint)) setpoint = setpoint.upper() - setpoint = setpoint[2:].rstrip('L') + setpoint = setpoint[2:].rstrip("L") if len(setpoint) == 3: - setpoint = '0' + setpoint + setpoint = "0" + setpoint else: - setpoint = '0000' - set_setpoint = ':0680010121' + setpoint + '\r\n' # Set setpoint + setpoint = "0000" + set_setpoint = ":0680010121" + setpoint + "\r\n" # Set setpoint response = self.comm(set_setpoint) response_check = response[5:].strip() - if response_check == '000005': - response = 'ok' + if response_check == "000005": + response = "ok" else: - response = 'error' + response = "error" return response def read_counter_value(self): - """ Read valve counter. Not fully implemented """ - read_counter = ':06030401210141\r\n' + """Read valve counter. Not fully implemented""" + read_counter = ":06030401210141\r\n" response = self.comm(read_counter) return str(response) def set_control_mode(self): - """ Set the control mode to accept rs232 setpoint """ - set_control = ':058001010412\r\n' + """Set the control mode to accept rs232 setpoint""" + set_control = ":058001010412\r\n" response = self.comm(set_control) return str(response) def read_serial(self): - """ Read the serial number of device """ - read_serial = ':1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n' + """Read the serial number of device""" + read_serial = ":1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n" error = 0 while error < 10: response = self.comm(read_serial) response = response[13:-84] - if sys.version_info[0] < 3: # Python2 + if sys.version_info[0] < 3: # Python2 try: - response = response.decode('hex') + response = response.decode("hex") except TypeError: - response = '' - else: # Python 3 + response = "" + else: # Python 3 try: - response = bytes.fromhex(response).decode('utf-8') + response = bytes.fromhex(response).decode("utf-8") except ValueError: - response = '' - if response == '': + response = "" + if response == "": error = error + 1 else: error = 10 return str(response) def read_unit(self): - """ Read the flow unit """ - read_capacity = ':1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n' + """Read the flow unit""" + read_capacity = ":1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n" response = self.comm(read_capacity) response = response[77:-26] try: - response = bytes.fromhex(response).decode('utf-8') - except AttributeError: # Python2 - response = response.decode('hex') + response = bytes.fromhex(response).decode("utf-8") + except AttributeError: # Python2 + response = response.decode("hex") return str(response) def read_capacity(self): - """ Read ?? from device """ - read_capacity = ':1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n' + """Read ?? from device""" + read_capacity = ":1A8004F1EC7163006D71660001AE0120CF014DF0017F077101710A\r\n" response = self.comm(read_capacity) response = response[65:-44] - #response = response.decode('hex') + # response = response.decode('hex') return str(response) -if __name__ == '__main__': - bh = Bronkhorst('/dev/ttyUSB3', 5) - #print bh.set_setpoint(1.0) - #time.sleep(1) +if __name__ == "__main__": + bh = Bronkhorst("/dev/ttyUSB3", 5) + # print bh.set_setpoint(1.0) + # time.sleep(1) print(bh.read_serial()) print(bh.read_flow()) print(bh.read_unit()) diff --git a/PyExpLabSys/drivers/brooks_s_protocol.py b/PyExpLabSys/drivers/brooks_s_protocol.py index 375fc46c..50bfa851 100644 --- a/PyExpLabSys/drivers/brooks_s_protocol.py +++ b/PyExpLabSys/drivers/brooks_s_protocol.py @@ -6,59 +6,60 @@ import serial from six import b, indexbytes from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class Brooks(object): - """ Driver for Brooks s-protocol """ - def __init__(self, device, port='/dev/ttyUSB0'): + """Driver for Brooks s-protocol""" + + def __init__(self, device, port="/dev/ttyUSB0"): self.ser = serial.Serial(port, 19200) self.ser.parity = serial.PARITY_ODD self.ser.bytesize = serial.EIGHTBITS self.ser.stopbits = serial.STOPBITS_ONE - deviceid = self.comm('8280000000000b06' - + self.pack(device[-8:])) - manufactor_code = '0a' + deviceid = self.comm("8280000000000b06" + self.pack(device[-8:])) + manufactor_code = "0a" device_type = deviceid[12:14] long_address = manufactor_code + device_type + deviceid[-6:] self.long_address = long_address def pack(self, input_string): - """ Turns a string in packed-ascii format """ - #This function lacks basic error checking.... - klaf = '' + """Turns a string in packed-ascii format""" + # This function lacks basic error checking.... + klaf = "" for s in input_string: klaf += bin((ord(s) % 128) % 64)[2:].zfill(6) - result = '' + result = "" for i in range(0, 6): - result = result + hex(int('' + klaf[i * 8:i * 8 + 8], - 2))[2:].zfill(2) + result = result + hex(int("" + klaf[i * 8 : i * 8 + 8], 2))[2:].zfill(2) return result def crc(self, command): - """ Calculate crc value of command """ + """Calculate crc value of command""" i = 0 - while command[i:i + 2] == 'FF': + while command[i : i + 2] == "FF": i += 2 command = command[i:] n = len(command) result = 0 - for i in range(0, (n//2)): - byte_string = command[i*2:i*2+2] + for i in range(0, (n // 2)): + byte_string = command[i * 2 : i * 2 + 2] byte = int(byte_string, 16) result = byte ^ result return hex(result) def comm(self, command): - """ Implements low-level details of the s-protocol """ + """Implements low-level details of the s-protocol""" check = str(self.crc(command)) check = check[2:].zfill(2) - final_com = 'FFFFFFFF' + command + check - bin_comm = '' + final_com = "FFFFFFFF" + command + check + bin_comm = "" for i in range(0, len(final_com) // 2): - bin_comm += chr(int(final_com[i * 2:i * 2 + 2], 16)) + bin_comm += chr(int(final_com[i * 2 : i * 2 + 2], 16)) bin_comm += chr(0) bytes_for_serial = b(bin_comm) error = 1 @@ -66,37 +67,37 @@ def comm(self, command): self.ser.write(bytes_for_serial) time.sleep(0.2) s = self.ser.read(self.ser.inWaiting()) - st = '' + st = "" for i in range(0, len(s)): - #char = hex(ord(s[i]))[2:].zfill(2) - #char = hex(s[i])[2:].zfill(2) + # char = hex(ord(s[i]))[2:].zfill(2) + # char = hex(s[i])[2:].zfill(2) char = hex(indexbytes(s, i))[2:].zfill(2) - if not char.upper() == 'FF': + if not char.upper() == "FF": st = st + char try: # delimiter = st[0:2] # address = st[2:12] command = st[12:14] byte_count = int(st[14:16], 16) - response = st[16:16 + 2 * byte_count] + response = st[16 : 16 + 2 * byte_count] error = 0 except ValueError: error = error + 1 - response = 'Error' + response = "Error" return response def read_flow(self): - """ Read the current flow-rate """ - response = self.comm('82' + self.long_address + '0100') + """Read the current flow-rate""" + response = self.comm("82" + self.long_address + "0100") try: # TODO: This should be handled be re-sending command - #status_code = response[0:4] + # status_code = response[0:4] unit_code = int(response[4:6], 16) flow_code = response[6:] byte0 = chr(int(flow_code[0:2], 16)) byte1 = chr(int(flow_code[2:4], 16)) byte2 = chr(int(flow_code[4:6], 16)) byte3 = chr(int(flow_code[6:8], 16)) - flow = struct.unpack('>f', b(byte0 + byte1 + byte2 + byte3)) + flow = struct.unpack(">f", b(byte0 + byte1 + byte2 + byte3)) value = flow[0] except ValueError: value = -1 @@ -109,38 +110,39 @@ def read_full_range(self): Report the full range of the device Apparantly this does not work for SLA-series... """ - response = self.comm('82' + self.long_address + '980106')#Command 152 + response = self.comm("82" + self.long_address + "980106") # Command 152 print(response) # Double check what gas-selection code really means... # currently 01 is used # status_code = response[0:4] unit_code = int(response[4:6], 16) - assert unit_code == 171 #Flow controller should always be set to mL/min + assert unit_code == 171 # Flow controller should always be set to mL/min flow_code = response[6:] byte0 = chr(int(flow_code[0:2], 16)) byte1 = chr(int(flow_code[2:4], 16)) byte2 = chr(int(flow_code[4:6], 16)) byte3 = chr(int(flow_code[6:8], 16)) - max_flow = struct.unpack('>f', byte0 + byte1 + byte2 + byte3) + max_flow = struct.unpack(">f", byte0 + byte1 + byte2 + byte3) return max_flow[0] def set_flow(self, flowrate): - """ Set the setpoint of the flow """ - ieee = struct.pack('>f', flowrate) - ieee_flowrate = '' + """Set the setpoint of the flow""" + ieee = struct.pack(">f", flowrate) + ieee_flowrate = "" for i in range(0, 4): ieee_flowrate += hex(ord(ieee[i]))[2:].zfill(2) - #39 = unit code for percent - #FA = unit code for 'same unit as flowrate measurement' - #response = self.comm('82' + self.long_address + + # 39 = unit code for percent + # FA = unit code for 'same unit as flowrate measurement' + # response = self.comm('82' + self.long_address + # 'ec05' + 'FA' + ieee_flowrate) # status_code = response[0:4] # unit_code = int(response[4:6], 16) return True -if __name__ == '__main__': - BROOKS = Brooks('3F2320902001') + +if __name__ == "__main__": + BROOKS = Brooks("3F2320902001") print(BROOKS.long_address) print(BROOKS.read_full_range()) print(BROOKS.read_flow()) diff --git a/PyExpLabSys/drivers/ccs811.py b/PyExpLabSys/drivers/ccs811.py index 278aa22c..c26214e5 100644 --- a/PyExpLabSys/drivers/ccs811.py +++ b/PyExpLabSys/drivers/ccs811.py @@ -8,27 +8,28 @@ class CCS811(object): Class for reading approximate values of eCO2 TVOC from a CC811 environmental sensor """ + def __init__(self): self.bus = smbus.SMBus(1) - self.device_address = 0x5a + self.device_address = 0x5A self.status = {} - self.status['error'] = [] + self.status["error"] = [] self.data = { - 'eCO2': -1, - 'TVOC': -1, - 'current': -1, - 'voltage': -1, - 'resistance': -1 + "eCO2": -1, + "TVOC": -1, + "current": -1, + "voltage": -1, + "resistance": -1, } if not self.verify_id(): - raise Exception('Not an CCS811!') + raise Exception("Not an CCS811!") self._activate() # Todo, check for True self._set_drive_mode() def _activate(self): - """ Take the device out of firmware update mode """ + """Take the device out of firmware update mode""" self.bus.write_byte(self.device_address, 0xF4) meas_mode = self.bus.read_byte_data(self.device_address, 0x01) return meas_mode == 0 @@ -46,12 +47,12 @@ def _set_drive_mode(self): return None def hw_version(self): - """ Read hardware version """ + """Read hardware version""" hw_version = self.bus.read_byte_data(self.device_address, 0x21) return hex(hw_version) def verify_id(self): - """ Verify that this is indeed a CCS811 """ + """Verify that this is indeed a CCS811""" hw_id = self.bus.read_byte_data(self.device_address, 0x20) return hw_id == 0x81 @@ -59,21 +60,21 @@ def update_status(self, status=None, error=None): if not status: status = self.bus.read_byte_data(self.device_address, 0x00) bit_values = bin(status)[2:].zfill(8) - if bit_values[-8] == '1': - self.status['Firmware mode'] = 'Application mode' + if bit_values[-8] == "1": + self.status["Firmware mode"] = "Application mode" else: - self.status['Firmware mode'] = 'Boot mode' + self.status["Firmware mode"] = "Boot mode" - if bit_values[-5] == '1': - self.status['Firmware status'] = 'Firmware loaded' + if bit_values[-5] == "1": + self.status["Firmware status"] = "Firmware loaded" else: - self.status['Firmware mode'] = 'Firmware not loaded' + self.status["Firmware mode"] = "Firmware not loaded" - self.status['Data ready'] = bit_values[-4] == '1' - if bit_values[-1] == '1': + self.status["Data ready"] = bit_values[-4] == "1" + if bit_values[-1] == "1": self.read_error(error) else: - self.status['error'] = [] + self.status["error"] = [] return self.status def read_error(self, error=None): @@ -81,31 +82,30 @@ def read_error(self, error=None): error = self.bus.read_byte_data(self.device_address, 0xE0) bit_values = bin(error)[2:].zfill(8) error = [] - if bit_values[-1] == '1': - error.append('Invalid write') - if bit_values[-2] == '1': - error.append('Invalid read') - if bit_values[-3] == '1': - error.append('Invalid MEAS_MODE') - if bit_values[-4] == '1': - error.append('Resistance too high, sensor not working') - if bit_values[-5] == '1': - error.append('Heater current error, sensor not working') - if bit_values[-6] == '1': - error.append('Heater voltage error, sensor not working') + if bit_values[-1] == "1": + error.append("Invalid write") + if bit_values[-2] == "1": + error.append("Invalid read") + if bit_values[-3] == "1": + error.append("Invalid MEAS_MODE") + if bit_values[-4] == "1": + error.append("Resistance too high, sensor not working") + if bit_values[-5] == "1": + error.append("Heater current error, sensor not working") + if bit_values[-6] == "1": + error.append("Heater voltage error, sensor not working") return self.status def update_data(self): - """ Read data and update internal state """ + """Read data and update internal state""" data = self.bus.read_i2c_block_data(self.device_address, 0x02, 8) - self.data['eCO2'] = 256 * data[0] + data[1] - self.data['TVOC'] = 256 * data[2] + data[3] + self.data["eCO2"] = 256 * data[0] + data[1] + self.data["TVOC"] = 256 * data[2] + data[3] raw_bits = bin(data[6])[2:].zfill(8) + bin(data[7])[2:].zfill(8) - self.data['current'] = int(raw_bits[0:6], 2) - self.data['voltage'] = int(raw_bits[6:16], 2) * 1.65 / 1023 - self.data['resistance'] = int(1000000 * self.data['voltage'] / - self.data['current']) + self.data["current"] = int(raw_bits[0:6], 2) + self.data["voltage"] = int(raw_bits[6:16], 2) * 1.65 / 1023 + self.data["resistance"] = int(1000000 * self.data["voltage"] / self.data["current"]) return self.data def set_environmental_data(self, humidity, temperature): @@ -113,41 +113,42 @@ def set_environmental_data(self, humidity, temperature): Set the environmental conditions to allow for correct compensation in the device """ - humidity_byte = '' + humidity_byte = "" for i in range(0, 16): bit_val = 64.0 / 2**i if humidity > bit_val: - humidity_byte += '1' + humidity_byte += "1" humidity -= bit_val else: - humidity_byte += '0' + humidity_byte += "0" hum_val = int(humidity_byte, 2) - hum_bytes = hum_val.to_bytes(2, byteorder='big') + hum_bytes = hum_val.to_bytes(2, byteorder="big") - temperature_byte = '' + temperature_byte = "" for i in range(0, 16): bit_val = 64.0 / 2**i if temperature + 25 > bit_val: - temperature_byte += '1' + temperature_byte += "1" temperature -= bit_val else: - temperature_byte += '0' + temperature_byte += "0" temperature_val = int(temperature_byte, 2) - temp_bytes = temperature_val.to_bytes(2, byteorder='big') + temp_bytes = temperature_val.to_bytes(2, byteorder="big") env_data = [hum_bytes[0], hum_bytes[1], temp_bytes[0], temp_bytes[1]] print(env_data) self.bus.wri2te_block_data(self.device_address, 0x05, env_data) time.sleep(0.5) -if __name__ == '__main__': + +if __name__ == "__main__": sensor = CCS811() time.sleep(5) - + for i in range(0, 2000): time.sleep(0.1) status = sensor.update_status() print(status) - if status['Data ready']: + if status["Data ready"]: print(sensor.update_data()) sensor.set_environmental_data(45.4, 27.3) diff --git a/PyExpLabSys/drivers/chemitec_s411.py b/PyExpLabSys/drivers/chemitec_s411.py index 9ee600e8..dc62115e 100644 --- a/PyExpLabSys/drivers/chemitec_s411.py +++ b/PyExpLabSys/drivers/chemitec_s411.py @@ -36,14 +36,14 @@ def __init__(self, tty, instrument_address=18, gpio_dir_pin=None): self.comm = self._setup_comm(address, tty=tty) try: value = self.comm.read_float(2, functioncode=4) - msg = 'Found instrument at {}, temperature is {:.1f}C' + msg = "Found instrument at {}, temperature is {:.1f}C" print(msg.format(address, value)) success = True break except minimalmodbus.NoResponseError: pass if not success: - print('No instrument found at {}'.format(address)) + print("No instrument found at {}".format(address)) def _setup_comm(self, instrument_address, tty): comm = minimalmodbus.Instrument(tty, instrument_address) @@ -57,7 +57,7 @@ def _setup_comm(self, instrument_address, tty): stopbits=1, timeout=0.05, write_timeout=2.0, - direction_pin=self.gpio_dir_pin + direction_pin=self.gpio_dir_pin, ) comm.serial = dir_serial @@ -82,7 +82,7 @@ def _read(self, register, code=3, floatread=False, keep_trying=False): error_count += 1 if error_count > 1000: if keep_trying: - print('Error: {}'.format(error_count)) + print("Error: {}".format(error_count)) else: break return value @@ -90,9 +90,7 @@ def _read(self, register, code=3, floatread=False, keep_trying=False): def _write(self, value, registeraddress): try: self.comm.write_register( - value=value, - registeraddress=registeraddress, - functioncode=6 + value=value, registeraddress=registeraddress, functioncode=6 ) except minimalmodbus.NoResponseError: pass # This exception is always raised after write @@ -112,12 +110,7 @@ def read_filter_value(self): return filter_code def read_range(self): - ranges = { - 0: '0-20', - 1: '0-200', - 2: '0-2000', - 3: '0-20000' - } + ranges = {0: "0-20", 1: "0-200", 2: "0-2000", 3: "0-20000"} range_val = self._read(5) # print('Range: {}'.format(ranges[range_val])) return_val = (range_val, ranges[range_val]) @@ -127,10 +120,12 @@ def read_serial(self): """ Returns the serial number of the unit. """ - serial_nr = '{}{}{}{}'.format(self._read(9, code=3), - self._read(10, code=3), - self._read(11, code=3), - self._read(12, code=3)) + serial_nr = "{}{}{}{}".format( + self._read(9, code=3), + self._read(10, code=3), + self._read(11, code=3), + self._read(12, code=3), + ) return serial_nr def set_instrument_address(self, instrument_address): @@ -162,11 +157,7 @@ def set_range(self, range_value): """ # Todo: check range is valid try: - self.comm.write_register( - value=range_value, - registeraddress=5, - functioncode=6 - ) + self.comm.write_register(value=range_value, registeraddress=5, functioncode=6) except minimalmodbus.NoResponseError: pass # This exception is always raised time.sleep(1) @@ -203,22 +194,22 @@ def read_calibrations(self): implemented. """ calibrations = { - 'manual_temperature': self._read(8, code=4, floatread=True), - 'temperature_offset': self._read(10, code=4, floatread=True), - 'cal2_val_user': self._read(12, code=4, floatread=True), - 'cal2_mis_user': self._read(14, code=4, floatread=True), - 'cal2_val_default': self._read(16, code=4, floatread=True), - 'cal2_mis_default': self._read(18, code=4, floatread=True) + "manual_temperature": self._read(8, code=4, floatread=True), + "temperature_offset": self._read(10, code=4, floatread=True), + "cal2_val_user": self._read(12, code=4, floatread=True), + "cal2_mis_user": self._read(14, code=4, floatread=True), + "cal2_val_default": self._read(16, code=4, floatread=True), + "cal2_mis_default": self._read(18, code=4, floatread=True), } return calibrations -if __name__ == '__main__': +if __name__ == "__main__": # s411 = ChemitecS411(instrument_address=0, tty='/dev/serial1') # s411 = ChemitecS411(instrument_address=0, tty='/dev/ttyUSB0') # exit() # s411 = ChemitecS411(instrument_address=15, tty='/dev/ttyUSB0') - s411 = ChemitecS411(instrument_address=15, tty='/dev/serial1', gpio_dir_pin=13) + s411 = ChemitecS411(instrument_address=15, tty="/dev/serial1", gpio_dir_pin=13) print(s411.read_serial()) print(s411.read_firmware_version()) @@ -235,5 +226,5 @@ def read_calibrations(self): time.sleep(2) conductivity = s411.read_conductivity() temperature = s411.read_temperature() - msg = 'Range is: {}, Value is: {}, temperature is: {}' + msg = "Range is: {}, Value is: {}, temperature is: {}" print(msg.format(range_val[1], conductivity, temperature)) diff --git a/PyExpLabSys/drivers/cpx400dp.py b/PyExpLabSys/drivers/cpx400dp.py index 550cb656..a5f03bef 100644 --- a/PyExpLabSys/drivers/cpx400dp.py +++ b/PyExpLabSys/drivers/cpx400dp.py @@ -5,13 +5,16 @@ import logging from PyExpLabSys.drivers.scpi import SCPI from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class InterfaceOutOfBoundsError(Exception): - """ Error class for CPX400DP Driver """ + """Error class for CPX400DP Driver""" + def __init__(self, value): super(InterfaceOutOfBoundsError, self).__init__(value) self.value = value @@ -21,139 +24,139 @@ def __str__(self): class CPX400DPDriver(SCPI): - """Actual driver for the CPX400DP """ + """Actual driver for the CPX400DP""" - def __init__(self, output, interface, hostname='', device='', tcp_port=0): + def __init__(self, output, interface, hostname="", device="", tcp_port=0): self.hostname = hostname - if interface == 'lan': - SCPI.__init__(self, 'lan', tcp_port=tcp_port, hostname=hostname) - if interface == 'serial': - SCPI.__init__(self, 'serial', device=device, line_ending='\n') + if interface == "lan": + SCPI.__init__(self, "lan", tcp_port=tcp_port, hostname=hostname) + if interface == "serial": + SCPI.__init__(self, "serial", device=device, line_ending="\n") if not (output == 1 or output == 2): raise InterfaceOutOfBoundsError(output) else: self.output = str(output) def set_voltage(self, value): - """Sets the voltage """ - function_string = 'V' + self.output + ' ' + str(value) + """Sets the voltage""" + function_string = "V" + self.output + " " + str(value) return self.scpi_comm(function_string) def set_current_limit(self, value): """Sets the current limit""" - function_string = 'I' + self.output + ' ' + str(value) + function_string = "I" + self.output + " " + str(value) return self.scpi_comm(function_string) def read_set_voltage(self): """Reads the set voltage""" - function_string = 'V' + self.output + '?' + function_string = "V" + self.output + "?" value_string = self.scpi_comm(function_string) try: - value = float(value_string.replace('V' + self.output, '')) + value = float(value_string.replace("V" + self.output, "")) except ValueError: value = -9997 return value def read_current_limit(self): """Reads the current limit""" - function_string = 'I' + self.output + '?' + function_string = "I" + self.output + "?" value_string = self.scpi_comm(function_string) try: - value = float(value_string.replace('I' + self.output, '')) + value = float(value_string.replace("I" + self.output, "")) except ValueError: value = -999999 return value def read_configuration_mode(self): - """ Return the depency mode between the channels """ - configuration_mode = self.scpi_comm('CONFIG?').strip() - mode = 'Unknown' - if configuration_mode == '0': - mode = 'Voltage tracking' - if configuration_mode == '2': - mode = 'Dual output' - if configuration_mode in ('3', '4'): - mode = 'Track Voltage and Current' + """Return the depency mode between the channels""" + configuration_mode = self.scpi_comm("CONFIG?").strip() + mode = "Unknown" + if configuration_mode == "0": + mode = "Voltage tracking" + if configuration_mode == "2": + mode = "Dual output" + if configuration_mode in ("3", "4"): + mode = "Track Voltage and Current" return mode def set_dual_output(self, dual_output=True): - """ Sets voltage tracking or dual output + """Sets voltage tracking or dual output If dual_output is True, Dual output will be activated. - If dual_output is False, Voltage tracking will be enabled """ + If dual_output is False, Voltage tracking will be enabled""" if dual_output: - self.scpi_comm('CONFIG 2') + self.scpi_comm("CONFIG 2") else: - self.scpi_comm('CONFIG 3') + self.scpi_comm("CONFIG 3") status = self.read_configuration_mode() return status def read_actual_voltage(self): """Reads the actual output voltage""" - function_string = 'V' + self.output + 'O?' + function_string = "V" + self.output + "O?" value_string = self.scpi_comm(function_string) LOGGER.warn(value_string) - time.sleep(0.1) # This might only be necessary on LAN interface + time.sleep(0.1) # This might only be necessary on LAN interface try: - value = float(value_string.replace('V', '')) + value = float(value_string.replace("V", "")) except ValueError: value = -999999 return value def read_actual_current(self): """Reads the actual output current""" - function_string = 'I' + self.output + 'O?' + function_string = "I" + self.output + "O?" value_string = self.scpi_comm(function_string) - time.sleep(0.1) # This might only be necessary on LAN interface + time.sleep(0.1) # This might only be necessary on LAN interface try: - value = float(value_string.replace('A', '')) + value = float(value_string.replace("A", "")) except ValueError: value = -9998 return value def set_voltage_stepsize(self, value): """Sets the voltage step size""" - function_string = 'DELTAV' + self.output + ' ' + str(value) + function_string = "DELTAV" + self.output + " " + str(value) return self.scpi_comm(function_string) def set_current_stepsize(self, value): """Sets the current step size""" - function_string = 'DELTAI' + self.output + ' ' + str(value) + function_string = "DELTAI" + self.output + " " + str(value) return self.scpi_comm(function_string) def read_voltage_stepsize(self): """Reads the voltage step size""" - function_string = 'DELTAV' + self.output + '?' + function_string = "DELTAV" + self.output + "?" return self.scpi_comm(function_string) def read_current_stepsize(self): - """ Read the current stepszie """ - function_string = 'DELTAI' + self.output + '?' + """Read the current stepszie""" + function_string = "DELTAI" + self.output + "?" return self.scpi_comm(function_string) def increase_voltage(self): - """ Increase voltage one step """ - function_string = 'INCV' + self.output + """Increase voltage one step""" + function_string = "INCV" + self.output return self.scpi_comm(function_string) def output_status(self, output_on=False): - """ Set the output status """ + """Set the output status""" if output_on: enabled = str(1) else: enabled = str(0) - function_string = 'OP' + self.output + ' ' + enabled + function_string = "OP" + self.output + " " + enabled return self.scpi_comm(function_string) def read_output_status(self): - """ Read the output status """ - function_string = 'OP' + self.output + '?' + """Read the output status""" + function_string = "OP" + self.output + "?" return self.scpi_comm(function_string) def get_lock(self): - """ Lock the instrument for remote operation """ - function_string = 'IFLOCK' + """Lock the instrument for remote operation""" + function_string = "IFLOCK" self.scpi_comm(function_string) - function_string = 'IFLOCK?' + function_string = "IFLOCK?" status = int(self.scpi_comm(function_string)) return_message = "" if status == 0: @@ -164,8 +167,9 @@ def get_lock(self): return_message = "Lock acquired" return return_message -if __name__ == '__main__': - CPX = CPX400DPDriver(1, interface='serial', device='/dev/ttyACM0') + +if __name__ == "__main__": + CPX = CPX400DPDriver(1, interface="serial", device="/dev/ttyACM0") print(CPX.read_software_version()) print(CPX.read_current_limit()) print(CPX.read_actual_current()) diff --git a/PyExpLabSys/drivers/crowcon.py b/PyExpLabSys/drivers/crowcon.py index 246e52ed..24a3ad1c 100644 --- a/PyExpLabSys/drivers/crowcon.py +++ b/PyExpLabSys/drivers/crowcon.py @@ -42,14 +42,26 @@ # These named tuples are used for multiple return values -Status = namedtuple('Status', ['code', 'value']) # pylint: disable=C0103 +Status = namedtuple("Status", ["code", "value"]) # pylint: disable=C0103 DetConfMap = namedtuple( # pylint: disable=C0103 - 'DetectorConfigurationMap', - ['number', 'identity', 'enabled', 'type', 'unit', 'range', 'level1', 'transition1', - 'level2', 'transition2', 'level3', 'transition3',] + "DetectorConfigurationMap", + [ + "number", + "identity", + "enabled", + "type", + "unit", + "range", + "level1", + "transition1", + "level2", + "transition2", + "level3", + "transition3", + ], ) # pylint: disable=C0103 -DetLev = namedtuple('DetectorLevels', ['number', 'level', 'status', 'inhibit']) +DetLev = namedtuple("DetectorLevels", ["number", "level", "status", "inhibit"]) ### Utility functions @@ -69,7 +81,7 @@ def register_to_bool(register): elif register == 65535: # Hex value FFFF return True else: - raise ValueError('Only 0 or 65535 can be converted to a boolean') + raise ValueError("Only 0 or 65535 can be converted to a boolean") # pylint: disable=R0904 @@ -91,8 +103,11 @@ def __init__(self, serial_device, slave_address, debug=False, cache=True, retrie change within the runtime of the program) should be cached """ - LOGGER.info('__init__ called, serial device: %s, slave address: %s', - serial_device, slave_address) + LOGGER.info( + "__init__ called, serial device: %s, slave address: %s", + serial_device, + slave_address, + ) Instrument.__init__(self, serial_device, slave_address) self.serial.baudrate = 9600 self.serial.stopbits = 2 @@ -104,13 +119,13 @@ def __init__(self, serial_device, slave_address, debug=False, cache=True, retrie # Cache self.system_conf = {} self.channel_conf = {} - LOGGER.info('__init__ complete') + LOGGER.info("__init__ complete") def close(self): """Close the serial communication connection""" - LOGGER.info('close called') + LOGGER.info("close called") self.serial.close() - LOGGER.info('close complete') + LOGGER.info("close complete") def read_register(self, *args, **kwargs): """Read register from instrument (with retries) @@ -126,8 +141,11 @@ def read_register(self, *args, **kwargs): return Instrument.read_register(self, *args, **kwargs) except ValueError as exception: if retry < self.retries: - LOGGER.warning("Communication error in read_register, retrying %s " - "out of %s times", retry + 1, self.retries) + LOGGER.warning( + "Communication error in read_register, retrying %s " "out of %s times", + retry + 1, + self.retries, + ) continue else: raise exception @@ -146,8 +164,11 @@ def read_string(self, *args, **kwargs): return Instrument.read_string(self, *args, **kwargs) except ValueError as exception: if retry < self.retries: - LOGGER.warning("Communication error in read_string, retrying %s " - "out of %s times", retry + 1, self.retries) + LOGGER.warning( + "Communication error in read_string, retrying %s " "out of %s times", + retry + 1, + self.retries, + ) continue else: raise exception @@ -164,16 +185,16 @@ def read_bool(self, register): Returns: bool: The boolean value """ - LOGGER.debug('read_bool at register %s', register) + LOGGER.debug("read_bool at register %s", register) boolean = register_to_bool(self.read_register(register)) - LOGGER.debug('read_bool %s', boolean) + LOGGER.debug("read_bool %s", boolean) return boolean def _check_detector_number(self, detector_number): """Check for a valid detector number""" num_detectors = self.get_number_installed_detectors() if detector_number not in range(1, num_detectors + 1): - raise ValueError('Only detector numbers 1-{} are valid'.format(num_detectors)) + raise ValueError("Only detector numbers 1-{} are valid".format(num_detectors)) # System commands, manual section 4.5.1 def get_type(self): @@ -182,9 +203,9 @@ def get_type(self): Returns: str: The type of the device e.g. 'Vortex' """ - LOGGER.info('get_type called') + LOGGER.info("get_type called") type_string = self.read_string(0, numberOfRegisters=8) - LOGGER.debug('get_type read %s', type_string) + LOGGER.debug("get_type read %s", type_string) return type_string def get_system_status(self): @@ -194,18 +215,18 @@ def get_system_status(self): list: ['All OK'] if no status bits (section 5.1.1 in the manual) has been set, otherwise one string for each of the status bits that was set. """ - LOGGER.info('get_system_status called') + LOGGER.info("get_system_status called") status = self.read_register(11) status_list = [] if status == 0: - status_list.append('All OK') + status_list.append("All OK") else: # Format the number into bitstring and reverse it with [::-1] - bit_string = '{:016b}'.format(status)[::-1] + bit_string = "{:016b}".format(status)[::-1] for position, status_message in SYSTEM_STATUS.items(): - if bit_string[position] == '1': + if bit_string[position] == "1": status_list.append(status_message) - LOGGER.debug('get_system_status read: %s', status_list) + LOGGER.debug("get_system_status read: %s", status_list) return status_list def get_system_power_status(self): @@ -214,11 +235,11 @@ def get_system_power_status(self): Returns: Status: A Status named tuple containing status code and string """ - LOGGER.info('get_system_power_status called') + LOGGER.info("get_system_power_status called") value = self.read_register(12) - status = SYSTEM_POWER_STATUS.get(value, 'Unknown status value') + status = SYSTEM_POWER_STATUS.get(value, "Unknown status value") status_tuple = Status(value, status) - LOGGER.debug('get_system_power_status returned: %s', status_tuple) + LOGGER.debug("get_system_power_status returned: %s", status_tuple) return status_tuple def get_serial_number(self): @@ -227,9 +248,9 @@ def get_serial_number(self): Returns: str: The serial number """ - LOGGER.info('get_serial_number called') + LOGGER.info("get_serial_number called") serial_number = self.read_string(32, numberOfRegisters=8) - LOGGER.debug('get_serial_number returned: %s', serial_number) + LOGGER.debug("get_serial_number returned: %s", serial_number) return serial_number def get_system_name(self): @@ -238,9 +259,9 @@ def get_system_name(self): Returns: str: The system name """ - LOGGER.info('get_system_name called') + LOGGER.info("get_system_name called") system_name = self.read_string(33, numberOfRegisters=8) - LOGGER.debug('get_system_name returned: %s', system_name) + LOGGER.debug("get_system_name returned: %s", system_name) return system_name def get_number_installed_detectors(self): @@ -251,17 +272,17 @@ def get_number_installed_detectors(self): Returns: int: The number of installed detectors """ - LOGGER.info('get_number_installed_detectors called') + LOGGER.info("get_number_installed_detectors called") if self.cache: - if 'installed_detectors' not in self.system_conf: - LOGGER.debug('num det. not in cache') - self.system_conf['installed_detectors'] = self.read_register(38) + if "installed_detectors" not in self.system_conf: + LOGGER.debug("num det. not in cache") + self.system_conf["installed_detectors"] = self.read_register(38) - LOGGER.debug('num det. return: %s', self.system_conf['installed_detectors']) - return self.system_conf['installed_detectors'] + LOGGER.debug("num det. return: %s", self.system_conf["installed_detectors"]) + return self.system_conf["installed_detectors"] else: number = self.read_register(38) - LOGGER.debug('num det. return un-cached: %s', number) + LOGGER.debug("num det. return un-cached: %s", number) return number # pylint: disable=C0103 @@ -271,10 +292,9 @@ def get_number_installed_digital_outputs(self): Returns: int: The number of installed digital inputs """ - LOGGER.info('get_number_installed_digital_outputs called') + LOGGER.info("get_number_installed_digital_outputs called") num_digital_outputs = self.read_register(39) - LOGGER.debug('get_number_installed_digital_outputs returned: %s', - num_digital_outputs) + LOGGER.debug("get_number_installed_digital_outputs returned: %s", num_digital_outputs) return num_digital_outputs # Detector configuration commands, manual section e.g 4.5.2 @@ -289,19 +309,22 @@ def detector_configuration(self, detector_number): Returns: DetConfMap: Named tuple (DetConfMap) containing the detector configuration """ - LOGGER.info('detector_configuration called for detector: %s', detector_number) + LOGGER.info("detector_configuration called for detector: %s", detector_number) self._check_detector_number(detector_number) # Return cached value if cache is True and it has been cached if self.cache and detector_number in self.channel_conf: - LOGGER.debug('detector_configuration returned cached value: %s', - self.channel_conf[detector_number]) + LOGGER.debug( + "detector_configuration returned cached value: %s", + self.channel_conf[detector_number], + ) return self.channel_conf[detector_number] # The registers for the detectors are shifted by 20 for each # detector and the register numbers below are for detector 1 reg_shift = 20 * (detector_number - 1) reg = {} + # fmt: off reg['enabled'] = 109 + reg_shift # pylint: disable=bad-whitespace reg['type'] = 110 + reg_shift # pylint: disable=bad-whitespace reg['level1'] = 113 + reg_shift # pylint: disable=bad-whitespace @@ -313,42 +336,46 @@ def detector_configuration(self, detector_number): reg['unit'] = 121 + reg_shift # pylint: disable=bad-whitespace reg['range'] = 122 + reg_shift # pylint: disable=bad-whitespace reg['identity'] = 125 + reg_shift # pylint: disable=bad-whitespace + # fmt: on - values = {'number': detector_number} + values = {"number": detector_number} # Read if the detector is enabled - values['enabled'] = self.read_bool(reg['enabled']) + values["enabled"] = self.read_bool(reg["enabled"]) # Read the detector type - values['type'] = DETECTOR_TYPE.get(self.read_register(reg['type']), - 'Unknown Detector Type') + values["type"] = DETECTOR_TYPE.get( + self.read_register(reg["type"]), "Unknown Detector Type" + ) # Read the units - values['unit'] = UNITS.get(self.read_register(reg['unit']), 'Unknown Unit') + values["unit"] = UNITS.get(self.read_register(reg["unit"]), "Unknown Unit") # Read the range and do not allow unknown range - range_register = self.read_register(reg['range']) + range_register = self.read_register(reg["range"]) try: - values['range'] = RANGE[range_register] + values["range"] = RANGE[range_register] except KeyError: - message = 'Unknown range code {}. Valid values are 9-25' + message = "Unknown range code {}. Valid values are 9-25" raise ValueError(message.format(range_register)) # Read the identity string - values['identity'] = self.read_string(reg['identity'], numberOfRegisters=4) - values['identity'] = values['identity'].rstrip('\x00') + values["identity"] = self.read_string(reg["identity"], numberOfRegisters=4) + values["identity"] = values["identity"].rstrip("\x00") # Read alarm levels and transition types for number in range(1, 4): - level = 'level' + str(number) - transition = 'transition' + str(number) - values[level] = self.read_register(reg[level], numberOfDecimals=3, signed=True)\ - * values['range'] + level = "level" + str(number) + transition = "transition" + str(number) + values[level] = ( + self.read_register(reg[level], numberOfDecimals=3, signed=True) + * values["range"] + ) values[transition] = TRANSITION[self.read_bool(reg[transition])] # Wrap in named tuple named_tuple_out = DetConfMap(**values) if self.cache: - LOGGER.debug('detector_configuration save cached value: %s', named_tuple_out) + LOGGER.debug("detector_configuration save cached value: %s", named_tuple_out) self.channel_conf[detector_number] = named_tuple_out - LOGGER.debug('detector_configuration return: %s', named_tuple_out) + LOGGER.debug("detector_configuration return: %s", named_tuple_out) return named_tuple_out # Detector levels commands, manual section e.g: 4.5.14 @@ -364,7 +391,7 @@ def get_detector_levels(self, detector_number): a list of status messages and a boolean that describes whether the detector is inhibited """ - LOGGER.debug('get_detector_levels called for detector: %s', detector_number) + LOGGER.debug("get_detector_levels called for detector: %s", detector_number) self._check_detector_number(detector_number) # The registers for the detectors are shfited by 10 for each @@ -373,19 +400,21 @@ def get_detector_levels(self, detector_number): # Get the range and read the level detector_range = self.detector_configuration(detector_number).range - level = self.read_register(2999 + reg_shift, numberOfDecimals=3, signed=True)\ - * detector_range + level = ( + self.read_register(2999 + reg_shift, numberOfDecimals=3, signed=True) + * detector_range + ) # Read the status register and form the list of status messages status = self.read_register(3000 + reg_shift) status_out = [] if status == 0: - status_out.append('OK') + status_out.append("OK") else: # Format the number into bitstring and reverse it with [::-1] - bit_string = '{:016b}'.format(status)[::-1] + bit_string = "{:016b}".format(status)[::-1] for bit_position, status_message in DETECTOR_STATUS.items(): - if bit_string[bit_position] == '1': + if bit_string[bit_position] == "1": status_out.append(status_message) # Read inhibited @@ -393,10 +422,9 @@ def get_detector_levels(self, detector_number): # Form detector levels named tuple detector_levels = DetLev(detector_number, level, status_out, inhibited) - LOGGER.debug('get_detector_levels return: %s', detector_levels) + LOGGER.debug("get_detector_levels return: %s", detector_levels) return detector_levels - def get_multiple_detector_levels(self, detector_numbers): """Get the levels for multiple detectors in one communication call @@ -429,19 +457,21 @@ def get_multiple_detector_levels(self, detector_numbers): # The level is the 0th register level_register = data[reg_shift] bytestring = _numToTwoByteString(level_register) - level = _twoByteStringToNum(bytestring, numberOfDecimals=3, signed=True)\ - * detector_range + level = ( + _twoByteStringToNum(bytestring, numberOfDecimals=3, signed=True) + * detector_range + ) # Status is the 1st register (0-based) status = data[1 + reg_shift] status_out = [] if status == 0: - status_out.append('OK') + status_out.append("OK") else: # Format the number into bitstring and reverse it with [::-1] - bit_string = '{:016b}'.format(status)[::-1] + bit_string = "{:016b}".format(status)[::-1] for bit_position, status_message in DETECTOR_STATUS.items(): - if bit_string[bit_position] == '1': + if bit_string[bit_position] == "1": status_out.append(status_message) # inhibit is the 4th register (0-based) @@ -457,27 +487,27 @@ def get_multiple_detector_levels(self, detector_numbers): # System power status translations, section 4.5.1 SYSTEM_POWER_STATUS = { - 0: 'OK', - 1: 'Main supply OK, battery low', - 2: 'Main supply fail, battery good', - 3: 'Main supply OK, battery disconnected', - 4: 'Main supply fail, battery low', - 5: 'No Comms to Power Card', + 0: "OK", + 1: "Main supply OK, battery low", + 2: "Main supply fail, battery good", + 3: "Main supply OK, battery disconnected", + 4: "Main supply fail, battery low", + 5: "No Comms to Power Card", } # Detector type, section e.g. 4.5.2 DETECTOR_TYPE = { - 0: 'Not Configured', - 6: 'Gas', - 8: 'Fire', + 0: "Not Configured", + 6: "Gas", + 8: "Fire", } # Detector units, manual section e.g. 4.5.2 UNITS = { - 0: '%LEL', - 1: '%VOL', - 2: 'PPM', - 3: 'Fire', + 0: "%LEL", + 1: "%VOL", + 2: "PPM", + 3: "Fire", } # Detector range, manual section e.g. 4.5.2 @@ -503,36 +533,36 @@ def get_multiple_detector_levels(self, detector_numbers): # Detector transition type, manual section e.g. 4.5.2 TRANSITION = { - True: 'Rising', - False: 'Failing', + True: "Rising", + False: "Failing", } # Detector status, section 5.1.7 DETECTOR_STATUS = { - 0: 'Alarm 1 present', - 1: 'Alarm 2 present', - 2: 'Alarm 3 present', - 3: 'Detector level interpreted as inhibit', - 4: 'Detector level interpreted as Low Info', - 5: 'Detector level interpreted as High Info', - 6: 'Low End Fault', - 7: 'High End Fault', - 8: 'Detector I2C fault', - 9: 'Detector Inhibit from controller (inhibit button pushed or inhibited from comms link)', + 0: "Alarm 1 present", + 1: "Alarm 2 present", + 2: "Alarm 3 present", + 3: "Detector level interpreted as inhibit", + 4: "Detector level interpreted as Low Info", + 5: "Detector level interpreted as High Info", + 6: "Low End Fault", + 7: "High End Fault", + 8: "Detector I2C fault", + 9: "Detector Inhibit from controller (inhibit button pushed or inhibited from comms link)", } # System status, manual section 5.1.1 SYSTEM_STATUS = { - 0: 'Vortex system is in channel test', - 1: 'Vortex system is in jump hold', - 2: 'Vortex system has a system fault', - 3: 'System fault is a battery fault', - 4: 'System fault is a FRAM data integrity fault', - 5: 'System fault is an internal I2C bus fault', - 6: 'System fault is a display access fault', - 7: 'System fault is a power monitor access fault', - 8: 'System fault is an external I2C bus fault', - 9: 'System fault is a relay board fault', + 0: "Vortex system is in channel test", + 1: "Vortex system is in jump hold", + 2: "Vortex system has a system fault", + 3: "System fault is a battery fault", + 4: "System fault is a FRAM data integrity fault", + 5: "System fault is an internal I2C bus fault", + 6: "System fault is a display access fault", + 7: "System fault is a power monitor access fault", + 8: "System fault is an external I2C bus fault", + 9: "System fault is a relay board fault", } @@ -541,45 +571,48 @@ def main(): from pprint import pprint from time import time - #logging.basicConfig(level=logging.DEBUG) - #logging.debug('Start') + # logging.basicConfig(level=logging.DEBUG) + # logging.debug('Start') - vortex = Vortex('/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FTY3G9FE-if00-port0', 2, cache=False) + vortex = Vortex( + "/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FTY3G9FE-if00-port0", 2, cache=False + ) vortex.debug = True while True: - vortex.read_bool(109)#detector_configuration(1) - #print("####################") - #print('Power status :', vortex.get_system_power_status()) - #vortex.get_detector_levels(1) - - #print('serial version:', serial.__version__) - #print('serial module:', serial) - - print('Type :', vortex.get_type()) - print('System status:', vortex.get_system_status()) - print('Power status :', vortex.get_system_power_status()) - print('Serial number:', vortex.get_serial_number()) - print('System name :', vortex.get_system_name()) + vortex.read_bool(109) # detector_configuration(1) + # print("####################") + # print('Power status :', vortex.get_system_power_status()) + # vortex.get_detector_levels(1) + + # print('serial version:', serial.__version__) + # print('serial module:', serial) + + print("Type :", vortex.get_type()) + print("System status:", vortex.get_system_status()) + print("Power status :", vortex.get_system_power_status()) + print("Serial number:", vortex.get_serial_number()) + print("System name :", vortex.get_system_name()) print() - print('Number of installed detectors :', vortex.get_number_installed_detectors()) - print('Number of installed digital outputs:', - vortex.get_number_installed_digital_outputs()) + print("Number of installed detectors :", vortex.get_number_installed_detectors()) + print( + "Number of installed digital outputs:", vortex.get_number_installed_digital_outputs() + ) print() for detector_number in range(1, 9): - print('Detector', detector_number) + print("Detector", detector_number) print(vortex.detector_configuration(detector_number)) - print(vortex.get_detector_levels(detector_number), end='\n\n') + print(vortex.get_detector_levels(detector_number), end="\n\n") - print('get_multiple_detector_levels') + print("get_multiple_detector_levels") t0 = time() while True: levels = vortex.get_multiple_detector_levels(list(range(1, 8))) pprint(levels) now = time() - print('Time to read 8 detectors', now - t0) + print("Time to read 8 detectors", now - t0) t0 = now -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/dataq_DI2008.py b/PyExpLabSys/drivers/dataq_DI2008.py index d956e447..f90d87fa 100644 --- a/PyExpLabSys/drivers/dataq_DI2008.py +++ b/PyExpLabSys/drivers/dataq_DI2008.py @@ -3,37 +3,34 @@ import signal import sys + class DI2008(object): - - def __init__(self, port='/dev/ttyACM0', echo=True): - self.ser = serial.Serial(port=port, - baudrate=115200, - timeout=0.1 - ) + def __init__(self, port="/dev/ttyACM0", echo=True): + self.ser = serial.Serial(port=port, baudrate=115200, timeout=0.1) self.acquiring = False self.stop() - self.packet_size(0, echo=echo) #128 bytes packet size - self.decimal(1) #resets filter + self.packet_size(0, echo=echo) # 128 bytes packet size + self.decimal(1) # resets filter self.srate(4) self.slist_pointer = 0 def comm(self, command, timeout=1, echo=True): prev = self.ser.read(self.ser.inWaiting()) - self.ser.write((command+'\r').encode()) + self.ser.write((command + "\r").encode()) # If not acquiring, read reply from DI2008 if not self.acquiring: - time.sleep(.1) + time.sleep(0.1) # Echo commands if not acquiring t0 = time.time() while True: if time.time() - t0 > timeout: - return 'Timeout' + return "Timeout" if self.ser.inWaiting() > 0: while True: try: s = self.ser.readline().decode() - s = s.strip('\n') - s = s.strip('\r') + s = s.strip("\n") + s = s.strip("\r") s = s.strip(chr(0)) if echo: print(repr(s)) @@ -44,114 +41,148 @@ def comm(self, command, timeout=1, echo=True): if echo: print(s) return s.lstrip(command).strip() - + def device_name(self): - self.comm('info 0') - self.comm('info 1') - + self.comm("info 0") + self.comm("info 1") + def start(self): self.acquiring = True - self.comm('start') - + self.comm("start") + def stop(self): self.acquiring = False - self.comm('stop') + self.comm("stop") def reset(self): - self.comm('reset 1') + self.comm("reset 1") - def packet_size(self, value, echo = True): + def packet_size(self, value, echo=True): if value not in range(4): - raise ValueError('Value must be 0, 1, 2, or 3.') + raise ValueError("Value must be 0, 1, 2, or 3.") psdict = {0: 16, 1: 32, 2: 64, 3: 128} self.ps = psdict[value] if echo: - print('Packet size: {} B'.format(self.ps)) - self.comm('ps ' + str(value)) + print("Packet size: {} B".format(self.ps)) + self.comm("ps " + str(value)) def srate(self, value): value = int(value) if value >= 4 and value <= 2232: - self.comm('srate ' + str(value)) + self.comm("srate " + str(value)) self.sr = value else: - raise ValueError('Value SRATE out of range') + raise ValueError("Value SRATE out of range") def decimal(self, value): value = int(value) if value >= 1 and value <= 32767: - self.comm('dec ' + str(value)) + self.comm("dec " + str(value)) self.dec = value else: - raise ValueError('Value DEC out of range') + raise ValueError("Value DEC out of range") - def set_filter(self, channel, mode='average'): - if channel not in [0,1,2,3,4]: - raise ValueError('Channel must be 1, 2, 3 or 4. Or 0 for all channels') - if mode not in ['last point','average','max','min']: - mode = 'average' - raise Warning('mode was not recognized, mode=average is used') - filter_dict = {'last point': 0, 'average': 1, 'max': 2, 'min': 3} + def set_filter(self, channel, mode="average"): + if channel not in [0, 1, 2, 3, 4]: + raise ValueError("Channel must be 1, 2, 3 or 4. Or 0 for all channels") + if mode not in ["last point", "average", "max", "min"]: + mode = "average" + raise Warning("mode was not recognized, mode=average is used") + filter_dict = {"last point": 0, "average": 1, "max": 2, "min": 3} if channel == 0: - self.comm('filter * '+str(filter_dict[mode])) + self.comm("filter * " + str(filter_dict[mode])) else: - self.comm('filter '+str(channel-1)+' '+str(filter_dict[mode])) - if not hasattr(self, 'channel_filter'): + self.comm("filter " + str(channel - 1) + " " + str(filter_dict[mode])) + if not hasattr(self, "channel_filter"): self.channel_filter = {} self.channel_filter[channel] = mode - - def add_voltage_analog_channel(self, channel, voltage=5., echo=True): - #channels 1, 2, 3, and 4, are enabled. If the more expensive 8-channel daq is purchased, this function should be expanded - #voltage is in volts + + def add_voltage_analog_channel(self, channel, voltage=5.0, echo=True): + # channels 1, 2, 3, and 4, are enabled. If the more expensive 8-channel daq is purchased, this function should be expanded + # voltage is in volts if channel not in [1, 2, 3, 4, 5, 6, 7, 8]: - raise ValueError('Channel must be between 1 and 8.') - if voltage not in [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1., 2.5, 5., 10., 25., 50.]: - raise ValueError('Cannot measure chosen voltage.') - if not hasattr(self, 'channel_voltage'): + raise ValueError("Channel must be between 1 and 8.") + if voltage not in [ + 0.01, + 0.025, + 0.05, + 0.1, + 0.25, + 0.5, + 1.0, + 2.5, + 5.0, + 10.0, + 25.0, + 50.0, + ]: + raise ValueError("Cannot measure chosen voltage.") + if not hasattr(self, "channel_voltage"): self.channel_voltage = {} self.channel_voltage[channel] = voltage - if not hasattr(self, 'slist_pointer_to_channel'): + if not hasattr(self, "slist_pointer_to_channel"): self.slist_pointer_to_channel = {} self.slist_pointer_to_channel[self.slist_pointer] = channel - voltage_dict = {0.5: '0', 0.25: '1', 0.1: '2', 0.05: '3', 0.025: '4', 0.01: '5', #6 and 7 are undefined - 50.: '8', 25.:'9', 10.: 'A', 5.: 'B', 2.5: 'C', 1.: 'D'} - self.comm('slist '+str(self.slist_pointer)+' '+str(int('0x0'+voltage_dict[voltage]+'0'+str(channel-1),16))) #writes command in hex-bit and translate to number + voltage_dict = { + 0.5: "0", + 0.25: "1", + 0.1: "2", + 0.05: "3", + 0.025: "4", + 0.01: "5", # 6 and 7 are undefined + 50.0: "8", + 25.0: "9", + 10.0: "A", + 5.0: "B", + 2.5: "C", + 1.0: "D", + } + self.comm( + "slist " + + str(self.slist_pointer) + + " " + + str(int("0x0" + voltage_dict[voltage] + "0" + str(channel - 1), 16)) + ) # writes command in hex-bit and translate to number self.slist_pointer += 1 - self.comm('filter '+str(channel-1)+' 0') - #0x[3 bits for voltage][one bit for channel] - #0x0A00 = 2560 is 10V on channel 1 - #0x0B01 = 2817 is 5V on channel 2 - #0x0C02 = 3074 is 2.5V on channel 3 - #0x0D03 = 3331 is 1V on channel 4 - if not hasattr(self, 'dec'): + self.comm("filter " + str(channel - 1) + " 0") + # 0x[3 bits for voltage][one bit for channel] + # 0x0A00 = 2560 is 10V on channel 1 + # 0x0B01 = 2817 is 5V on channel 2 + # 0x0C02 = 3074 is 2.5V on channel 3 + # 0x0D03 = 3331 is 1V on channel 4 + if not hasattr(self, "dec"): self.decimal(1) - if not hasattr(self, 'sr'): + if not hasattr(self, "sr"): self.srate(4) if echo: - print('Analog channel '+str(channel)+' measure +-'+str(voltage)+' V') + print("Analog channel " + str(channel) + " measure +-" + str(voltage) + " V") def read(self): output = {} for pointer in range(self.slist_pointer): byte = self.ser.read(2) - result = self.channel_voltage[self.slist_pointer_to_channel[pointer]] * int.from_bytes(byte,byteorder='little', signed=True) / 32768 - output[self.slist_pointer_to_channel[pointer]] = round(result,4) + result = ( + self.channel_voltage[self.slist_pointer_to_channel[pointer]] + * int.from_bytes(byte, byteorder="little", signed=True) + / 32768 + ) + output[self.slist_pointer_to_channel[pointer]] = round(result, 4) slist_pointer = 0 return output -if __name__ == '__main__': + +if __name__ == "__main__": dev = DI2008() dev.device_name() - dev.add_voltage_analog_channel(1, voltage = 0.01) - dev.add_voltage_analog_channel(2, voltage = 1.) - dev.add_voltage_analog_channel(3, voltage = 5.) - dev.add_voltage_analog_channel(4, voltage = 10.) - #dev.decimal(3) - #dev.set_filter(0,mode='average') + dev.add_voltage_analog_channel(1, voltage=0.01) + dev.add_voltage_analog_channel(2, voltage=1.0) + dev.add_voltage_analog_channel(3, voltage=5.0) + dev.add_voltage_analog_channel(4, voltage=10.0) + # dev.decimal(3) + # dev.set_filter(0,mode='average') dev.start() for n in range(10000): data = dev.read() - print(round(data[1],3),round(data[3],3),round(data[4],3)) + print(round(data[1], 3), round(data[3], 3), round(data[4], 3)) time.sleep(0.005) dev.stop() - diff --git a/PyExpLabSys/drivers/dataq_binary.py b/PyExpLabSys/drivers/dataq_binary.py index 1171746c..38c937a5 100644 --- a/PyExpLabSys/drivers/dataq_binary.py +++ b/PyExpLabSys/drivers/dataq_binary.py @@ -94,6 +94,7 @@ import numpy from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) @@ -102,6 +103,7 @@ class BufferOverflow(Exception): """Custom exception to indicate a buffer overflow""" + pass @@ -109,21 +111,27 @@ class DataQBinary(object): """Base class for DataQBinary driver""" #: Serial communication end character - end_char = '\r' + end_char = "\r" #: Wait time between serial write and read read_wait_time = 0.001 #: Information items that can be retrieved along with their code number infos = { - 'vendor': 0, - 'device name': 1, - 'firmware revision': 2, - 'serial number': 6, - 'sample rate divisor': 9, + "vendor": 0, + "device name": 1, + "firmware revision": 2, + "serial number": 6, + "sample rate divisor": 9, } #: Supported LED colors along with the code number led_colors = { - 'black': 0, 'blue': 1, 'green': 2, 'cyan': 3, - 'red': 4, 'magenta': 5, 'yellow': 6, 'white': 7, + "black": 0, + "blue": 1, + "green": 2, + "cyan": 3, + "red": 4, + "magenta": 5, + "yellow": 6, + "white": 7, } #: Buffer overflow size, may be overwritte in sub classes buffer_overflow_size = 4095 @@ -159,7 +167,7 @@ def __init__(self, device, serial_kwargs=None): self.expect_echo = True self._scan_list = [0] self._position_in_scanlist = 0 - self._sample_rate_divisor = int(self.info('sample rate divisor')) + self._sample_rate_divisor = int(self.info("sample rate divisor")) def clear_buffer(self): """Clear the buffer""" @@ -167,37 +175,36 @@ def clear_buffer(self): while self.serial.inWaiting() > 0: self.serial.read(self.serial.inWaiting()) - def _comm(self, arg, read_reply_policy='read'): + def _comm(self, arg, read_reply_policy="read"): """Execute command and return reply""" # Form command and send - command = (arg + self.end_char) + command = arg + self.end_char self.serial.write(command.encode()) LOGGER.debug("cmd: %s", command) - if read_reply_policy == 'dont_read': - return '' + if read_reply_policy == "dont_read": + return "" # Read until reply end string - return_string = '' - while return_string == '' or not return_string.endswith(self.end_char): + return_string = "" + while return_string == "" or not return_string.endswith(self.end_char): sleep(self.read_wait_time) bytes_waiting = self.serial.inWaiting() bytes_read = self.serial.read(bytes_waiting) # For the stop command, the reply might have a bit a data # prefixed, so just don't interpret the reply - if read_reply_policy == 'dont_interpret': - return '' - return_string += bytes_read.decode().rstrip('\x00') + if read_reply_policy == "dont_interpret": + return "" + return_string += bytes_read.decode().rstrip("\x00") LOGGER.debug("reply: %s", return_string) # Strip echo if present if self.expect_echo and return_string.startswith(arg): - return_string = return_string.replace(arg + ' ', '', 1) + return_string = return_string.replace(arg + " ", "", 1) # And finally end_char when we return the result return return_string.strip(self.end_char) - - def info(self, info_name='all'): + def info(self, info_name="all"): """Return information about the device Args: @@ -209,23 +216,23 @@ def info(self, info_name='all'): Returns: str or dict: Information items """ - if info_name == 'all': + if info_name == "all": out = {} for name, number in self.infos.items(): - command = 'info {}'.format(number) + command = "info {}".format(number) out[name] = self._comm(command) return out if info_name not in self.infos: msg = 'Invalid info_name. Valid value are: {} and "all"' raise ValueError(msg.format(self.infos.keys())) - command = 'info {}'.format(self.infos[info_name]) + command = "info {}".format(self.infos[info_name]) return self._comm(command) def start(self): """Start data acquisition""" self.expect_echo = False - self._comm('start 0', read_reply_policy='dont_read') + self._comm("start 0", read_reply_policy="dont_read") def stop(self): """Stop data acquisition @@ -233,7 +240,7 @@ def stop(self): This also implies clearing the buffer of any remaining data """ self.expect_echo = True - self._comm('stop', read_reply_policy='dont_interpret') + self._comm("stop", read_reply_policy="dont_interpret") # This shouldn't be necessary self.clear_buffer() @@ -257,16 +264,16 @@ def scan_list(self, scan_list): # Check for valid scan list configuratio for scan_list_configuration in scan_list: if scan_list_configuration not in range(8): - msg = 'Only scan list arguments 0-7 are supported' + msg = "Only scan list arguments 0-7 are supported" raise ValueError(msg) # Check for duplicate entries if len(set(scan_list)) != len(scan_list): - msg = 'The scan list is not allowed to have duplicate entries' + msg = "The scan list is not allowed to have duplicate entries" raise ValueError(msg) # Send the configuration for slot_number, configuration in enumerate(scan_list): - command = 'slist {} {}'.format(slot_number, configuration) + command = "slist {} {}".format(slot_number, configuration) self._comm(command) self._scan_list = scan_list @@ -293,7 +300,7 @@ def sample_rate(self, rate): rate_for_command = int(self._sample_rate_divisor / float(rate)) # Coerce in valid range rate_for_command = min(max(375, rate_for_command), 65535) - self._comm('srate {}'.format(rate_for_command)) + self._comm("srate {}".format(rate_for_command)) def packet_size(self, size): """Set the packet size @@ -306,10 +313,10 @@ class variable. size (int): The requested packet size """ if size not in self.packet_sizes: - msg = 'Invalid packet size. Available values are: {}' + msg = "Invalid packet size. Available values are: {}" raise ValueError(msg.format(self.packet_sizes.keys())) packet_size_number = self.packet_sizes[size] - command = 'ps {}'.format(packet_size_number) + command = "ps {}".format(packet_size_number) self._comm(command) def led_color(self, color): @@ -320,10 +327,10 @@ def led_color(self, color): class variable """ if color not in self.led_colors: - msg = 'Invalid color. Available colors are: {}' + msg = "Invalid color. Available colors are: {}" raise ValueError(msg.format(self.led_colors.keys())) color_number = self.led_colors[color] - command = 'led {}'.format(color_number) + command = "led {}".format(color_number) self._comm(command) def read(self): @@ -377,11 +384,11 @@ def read(self): bytes_read = self.serial.read(waiting) # ... check for buffer overflow if len(bytes_read) == self.buffer_overflow_size: - msg = ("Buffer flowed over. Adjust sampling parameters and restart " - "measurement.") + msg = "Buffer flowed over. Adjust sampling parameters and restart " "measurement." raise BufferOverflow(msg) - buffer_message = '{}/{} bytes'.format( - len(bytes_read), self.buffer_overflow_size, + buffer_message = "{}/{} bytes".format( + len(bytes_read), + self.buffer_overflow_size, ) # Parse and interpret the data @@ -389,7 +396,7 @@ def read(self): # Add buffer status for item in list_out: if item is not None: - item['buffer_status'] = buffer_message + item["buffer_status"] = buffer_message return list_out def _parse_data_packet(self, bytes_read, list_out): @@ -399,7 +406,7 @@ def _parse_data_packet(self, bytes_read, list_out): # calculate, parse the 2 byte words as int16 and convert to # float voltage values if any(n in range(0, 8) for n in self._scan_list): - ints = numpy.frombuffer(bytes_read, dtype='int16') + ints = numpy.frombuffer(bytes_read, dtype="int16") # NOTE on shift FIXME values = numpy.right_shift(ints, 4) * 10.0 / 2048 @@ -416,9 +423,9 @@ def _parse_data_packet(self, bytes_read, list_out): # Extracts every n_input_channels items at offset values_this_channel = values[offset::n_input_channels] channel_out = { - 'value': values_this_channel.mean(), - 'samples': values_this_channel.size, - 'channel': channel, + "value": values_this_channel.mean(), + "samples": values_this_channel.size, + "channel": channel, } list_out[position_in_scanlist] = channel_out else: @@ -435,6 +442,7 @@ def _parse_data_packet(self, bytes_read, list_out): class DI1110(DataQBinary): """FIXME""" + buffer_overflow_size = 4095 @@ -442,21 +450,22 @@ def module_test(): """Run primitive module tests""" logging.basicConfig(level=logging.DEBUG) global LOGGER # pylint: disable=global-statement - LOGGER = logging.getLogger('module_test') + LOGGER = logging.getLogger("module_test") # Instantiate driver and print info - dataq = DI1110('/dev/ttyUSB0') + dataq = DI1110("/dev/ttyUSB0") print(repr(dataq.info())) # Set sample rate dataq.sample_rate(1000) dataq.scan_list([1, 0, 2]) - dataq.led_color('red') + dataq.led_color("red") # Start measurement - #dataq.start() - #sleep(0.1) + # dataq.start() + # sleep(0.1) from pprint import pprint + try: while True: dataq.start() @@ -471,6 +480,5 @@ def module_test(): dataq.clear_buffer() - -if __name__ == '__main__': +if __name__ == "__main__": module_test() diff --git a/PyExpLabSys/drivers/dataq_comm.py b/PyExpLabSys/drivers/dataq_comm.py index ad6e342b..be353dc5 100644 --- a/PyExpLabSys/drivers/dataq_comm.py +++ b/PyExpLabSys/drivers/dataq_comm.py @@ -5,88 +5,91 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class DataQ(object): - """ driver for the DataQ Instrument """ + """driver for the DataQ Instrument""" + def __init__(self, port): self.serial = serial.Serial(port) - self.set_float_mode() # This is currently the only implemented mode + self.set_float_mode() # This is currently the only implemented mode self.scan_list_counter = 0 self.stop_measurement() self.scanning = False self.reset_scan_list() self.scan_list = [] time.sleep(1) - self.serial.read(self.serial.inWaiting()) # Clear the read-buffer + self.serial.read(self.serial.inWaiting()) # Clear the read-buffer def comm(self, command): - """ comm function """ - end_char = '\r' # carriage return - command = command + end_char + """comm function""" + end_char = "\r" # carriage return + command = command + end_char command = command.encode() self.serial.write(command) - return_string = b'' + return_string = b"" current_char = chr(0) while ord(current_char) != ord(end_char): current_char = self.serial.read(1) return_string += current_char - return return_string.decode('ascii') + return return_string.decode("ascii") def dataq(self): - """ Returns the string DATAQ""" - command = 'info 0' + """Returns the string DATAQ""" + command = "info 0" res = self.comm(command)[7:] return res def device_name(self): - """ Returns device name""" - command = 'info 1' + """Returns device name""" + command = "info 1" res = self.comm(command)[7:] return res def firmware(self): - """ Returns firmware version """ - command = 'info 2' + """Returns firmware version""" + command = "info 2" res = self.comm(command)[7:] return res def serial_number(self): - """ Returns device serial number """ - command = 'info 6' + """Returns device serial number""" + command = "info 6" res = self.comm(command)[7:] return res def start_measurement(self): - """ Start a measurement scan """ - command = 'start' + """Start a measurement scan""" + command = "start" res = self.comm(command) self.scanning = True return res def read_measurements(self): - """ Read the newest measurents """ + """Read the newest measurents""" if not self.scanning: return False - #data = self.serial.read(self.serial.inWaiting()) - data_start = ' ' - while data_start != b'sc ': + # data = self.serial.read(self.serial.inWaiting()) + data_start = " " + while data_start != b"sc ": data_start = self.serial.read(3) - scan_data = b' ' - try: # Python 2 + scan_data = b" " + try: # Python 2 ord(scan_data[-1]) - end_char = '\r' - except TypeError: #Python 3 + end_char = "\r" + except TypeError: # Python 3 end_char = 13 while scan_data[-1] != end_char: scan_data += self.serial.read(1) - scan_data = scan_data.decode('ascii') + scan_data = scan_data.decode("ascii") # Remove double spaces to have a unique split identifier - scan_data = scan_data.strip().replace(' ', ' ') - scan_data = scan_data.split(' ') + scan_data = scan_data.strip().replace(" ", " ") + scan_data = scan_data.split(" ") scan_values = [float(i) for i in scan_data] return_values = {} for i in range(0, len(scan_values)): @@ -94,16 +97,16 @@ def read_measurements(self): return return_values def stop_measurement(self): - """ Stop a measurement scan """ - command = 'stop' + """Stop a measurement scan""" + command = "stop" res = self.comm(command) self.scanning = False return res def add_channel(self, channel): - """ Adds a channel to scan slist. - So far only analog channels are accepted """ - command = 'slist ' + str(self.scan_list_counter) + ' x000' + str(channel - 1) + """Adds a channel to scan slist. + So far only analog channels are accepted""" + command = "slist " + str(self.scan_list_counter) + " x000" + str(channel - 1) # TODO: This is a VERY rudementary treatment of the scan-list... self.scan_list_counter = self.scan_list_counter + 1 self.scan_list.append(channel) @@ -111,20 +114,20 @@ def add_channel(self, channel): return res def set_ascii_mode(self): - """ change response mode to ACSII""" - command = 'asc' + """change response mode to ACSII""" + command = "asc" res = self.comm(command) return res def set_float_mode(self): - """ change response mode to float""" - command = 'float' + """change response mode to float""" + command = "float" res = self.comm(command) return res def reset_scan_list(self): - """ Reseting the scan list """ - command = 'slist 0 0xffff' + """Reseting the scan list""" + command = "slist 0 0xffff" self.scan_list_counter = 0 self.scan_list = [] res = self.comm(command) @@ -156,8 +159,9 @@ def set_outputs(self, ch0=False, ch1=False, ch2=False, ch3=False): return res """ -if __name__ == '__main__': - DATAQ = DataQ('/dev/ttyACM0') + +if __name__ == "__main__": + DATAQ = DataQ("/dev/ttyACM0") print(DATAQ.device_name()) print(DATAQ.firmware()) print(DATAQ.serial_number()) diff --git a/PyExpLabSys/drivers/deltaco_TB_298.py b/PyExpLabSys/drivers/deltaco_TB_298.py index 51fcf160..06b446e9 100644 --- a/PyExpLabSys/drivers/deltaco_TB_298.py +++ b/PyExpLabSys/drivers/deltaco_TB_298.py @@ -1,4 +1,3 @@ - """Driver with line reader for the Deltaco TB-298 Keypad""" import glob @@ -9,18 +8,20 @@ from queue import Queue, Empty, Full from PyExpLabSys.common.supported_versions import python3_only -python3_only(__file__) +python3_only(__file__) -KEYS_TO_CHARS = {'KEY_KP{}'.format(n): str(n) for n in range(10)} -KEYS_TO_CHARS.update({ - 'KEY_KPSLASH': '/', - 'KEY_KPASTERISK': '*', - 'KEY_KPMINUS': '-', - 'KEY_KPPLUS': '+', - 'KEY_KPDOT': '.', -}) +KEYS_TO_CHARS = {"KEY_KP{}".format(n): str(n) for n in range(10)} +KEYS_TO_CHARS.update( + { + "KEY_KPSLASH": "/", + "KEY_KPASTERISK": "*", + "KEY_KPMINUS": "-", + "KEY_KPPLUS": "+", + "KEY_KPDOT": ".", + } +) def detect_keypad_device(): @@ -33,7 +34,7 @@ def detect_keypad_device(): str: The Keypad device path """ barcode_device = None - for device_string in glob.glob('/dev/input/event*'): + for device_string in glob.glob("/dev/input/event*"): try: tmp_dev = evdev.InputDevice(device_string) except OSError: @@ -41,12 +42,14 @@ def detect_keypad_device(): else: device_information = tmp_dev.info # Check for vendor and product ids - if not (device_information.vendor == 0x04d9 and device_information.product == 0x1203): + if not ( + device_information.vendor == 0x04D9 and device_information.product == 0x1203 + ): continue - available_keys = tmp_dev.capabilities(verbose=True)[('EV_KEY', 1)] + available_keys = tmp_dev.capabilities(verbose=True)[("EV_KEY", 1)] tmp_dev.close() - if ('KEY_1', 2) in available_keys: + if ("KEY_1", 2) in available_keys: return device_string @@ -59,7 +62,7 @@ class MaxSizeQueue(Queue): queue.Full exception when adding an item. Otherwise the queue will block until an item is removed from the queue. """ - + def put(self, item): while True: try: @@ -68,6 +71,7 @@ def put(self, item): except Full: super().get() + class ThreadedKeypad(Thread): """Threaded keypad reader @@ -79,7 +83,7 @@ class ThreadedKeypad(Thread): device (evdev.InputDevice): The input device """ - def __init__(self, device_path, line_chars='0123456789', max_queue_sizes=None): + def __init__(self, device_path, line_chars="0123456789", max_queue_sizes=None): """Instantiate local variables Holds 3 non-blocking queues that record inputs. If a queue is full, the first item @@ -100,36 +104,38 @@ def __init__(self, device_path, line_chars='0123456789', max_queue_sizes=None): super().__init__() self._continue_reading = True self.device = evdev.InputDevice(device_path) - self._gathered_line = '' + self._gathered_line = "" self.line_chars = line_chars self.led_on = False - for (name, value) in self.device.leds(verbose=True): - if name == 'LED_NUML': + for name, value in self.device.leds(verbose=True): + if name == "LED_NUML": self.led_on = True - + # Create queues - _max_queue_sizes = {'line': 1024, 'event': 1024, 'key_pressed': 1024} + _max_queue_sizes = {"line": 1024, "event": 1024, "key_pressed": 1024} if max_queue_sizes: _max_queue_sizes.update(max_queue_sizes) - self.line_queue = MaxSizeQueue(maxsize=_max_queue_sizes['line']) - self.event_queue = MaxSizeQueue(maxsize=_max_queue_sizes['event']) - self.key_pressed_queue = MaxSizeQueue(maxsize=_max_queue_sizes['key_pressed']) + self.line_queue = MaxSizeQueue(maxsize=_max_queue_sizes["line"]) + self.event_queue = MaxSizeQueue(maxsize=_max_queue_sizes["event"]) + self.key_pressed_queue = MaxSizeQueue(maxsize=_max_queue_sizes["key_pressed"]) def run(self): """Main run method""" while self._continue_reading: # Check if there is anything to read on the device try: - read_list, _, _ = select.select([self.device.fd], [], [], 0.5) # previous timeout 0.2 + read_list, _, _ = select.select( + [self.device.fd], [], [], 0.5 + ) # previous timeout 0.2 if read_list: for event in self.device.read(): if event.type == evdev.ecodes.EV_KEY: self.handle_event(event) - #sleep(0.01) + # sleep(0.01) # Read LED Num Lock state - for (name, value) in self.device.leds(verbose=True): + for name, value in self.device.leds(verbose=True): # Set True if LED is on - if name == 'LED_NUML': + if name == "LED_NUML": self.led_on = True break else: @@ -156,16 +162,16 @@ def handle_event(self, event): char = KEYS_TO_CHARS[key_code] if char in self.line_chars: self._gathered_line += char - elif key_code == 'KEY_BACKSPACE': + elif key_code == "KEY_BACKSPACE": self._gathered_line = self._gathered_line[:-1] - elif key_code == 'KEY_KPENTER': + elif key_code == "KEY_KPENTER": self.line_queue.put(self._gathered_line) - self._gathered_line = '' + self._gathered_line = "" def stop(self): """Stop the thread and put deltaco_TP_298.STOP in queues""" self._continue_reading = False - #while self.is_alive(): # Commented as thread cannot be closed from + # while self.is_alive(): # Commented as thread cannot be closed from # sleep(0.1) # within otherwise self.device.close() self.line_queue.put(STOP) @@ -209,5 +215,5 @@ def module_demo(): reader.stop() -if __name__ == '__main__': +if __name__ == "__main__": module_demo() diff --git a/PyExpLabSys/drivers/edwards_agc.py b/PyExpLabSys/drivers/edwards_agc.py index 6b189438..97dbe0d9 100644 --- a/PyExpLabSys/drivers/edwards_agc.py +++ b/PyExpLabSys/drivers/edwards_agc.py @@ -2,58 +2,65 @@ from __future__ import print_function import serial + class EdwardsAGC(object): - """ Primitive driver for Edwards Active Gauge Controler + """Primitive driver for Edwards Active Gauge Controler Complete manual found at - http://www.idealvac.com/files/brochures/Edwards_AGC_D386-52-880_IssueM.pdf """ - def __init__(self, port='/dev/ttyUSB0'): + http://www.idealvac.com/files/brochures/Edwards_AGC_D386-52-880_IssueM.pdf""" + + def __init__(self, port="/dev/ttyUSB0"): self.serial = serial.Serial(port, baudrate=9600, timeout=0.5) def comm(self, command): - """ Implements basic communication """ + """Implements basic communication""" comm = command + "\r" - for _ in range(0, 10): # Seems you no to query several times to get correct reply - self.serial.write(comm.encode('ascii')) + for _ in range(0, 10): # Seems you no to query several times to get correct reply + self.serial.write(comm.encode("ascii")) complete_string = self.serial.readline().decode() - self.serial.write(comm.encode('ascii')) + self.serial.write(comm.encode("ascii")) complete_string = self.serial.readline().decode() complete_string = complete_string.strip() return complete_string def gauge_type(self, gauge_number): - """ Return the type of gauge """ - types = {0: 'Not Fitted', 1: '590 CM capacitance manometer', - 15: 'Active strain gauge', 5: 'Pirani L low pressure', - 20: 'Wide range gauge'} # Feel free to fill in.... + """Return the type of gauge""" + types = { + 0: "Not Fitted", + 1: "590 CM capacitance manometer", + 15: "Active strain gauge", + 5: "Pirani L low pressure", + 20: "Wide range gauge", + } # Feel free to fill in.... - type_number = int(self.comm('?GV ' + str(gauge_number))) + type_number = int(self.comm("?GV " + str(gauge_number))) gauge_type = types[type_number] return gauge_type def read_pressure(self, gauge_number): - """ Read the pressure of a gauge """ - pressure_string = self.comm('?GA ' + str(gauge_number)) + """Read the pressure of a gauge""" + pressure_string = self.comm("?GA " + str(gauge_number)) pressure_value = float(pressure_string) return pressure_value def pressure_unit(self, gauge_number): - """ Read the unit of a gauge """ - units = {0: 'mbar', 1: 'torr'} - unit_string = self.comm('?NU ' + str(gauge_number)) + """Read the unit of a gauge""" + units = {0: "mbar", 1: "torr"} + unit_string = self.comm("?NU " + str(gauge_number)) unit_number = int(unit_string) unit = units[unit_number] return unit def current_error(self): - """ Read the current error code """ - error_code = self.comm('?SY') + """Read the current error code""" + error_code = self.comm("?SY") return error_code - + def software_version(self): - """ Return the software version of the controller """ - return self.comm('?VE') - -if __name__ == '__main__': + """Return the software version of the controller""" + return self.comm("?VE") + + +if __name__ == "__main__": E_AGC = EdwardsAGC() print(E_AGC.gauge_type(4)) print(E_AGC.read_pressure(1)) @@ -62,4 +69,3 @@ def software_version(self): print(E_AGC.read_pressure(4)) print(E_AGC.pressure_unit(1)) print(E_AGC.current_error()) - diff --git a/PyExpLabSys/drivers/edwards_nxds.py b/PyExpLabSys/drivers/edwards_nxds.py index 2928724e..e7404bdc 100644 --- a/PyExpLabSys/drivers/edwards_nxds.py +++ b/PyExpLabSys/drivers/edwards_nxds.py @@ -2,197 +2,211 @@ import time import logging import serial + LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) class EdwardsNxds(object): - """ Driver for the Edwards nXDS series of dry pumps """ + """Driver for the Edwards nXDS series of dry pumps""" def __init__(self, port): self.ser = serial.Serial(port, 9600, timeout=2) time.sleep(0.1) def comm(self, command): - """ Ensures correct protocol for instrument """ - self.ser.write((command + '\r').encode('ascii')) + """Ensures correct protocol for instrument""" + self.ser.write((command + "\r").encode("ascii")) return_string = self.ser.readline().decode() if not return_string[2:5] == command[2:5]: raise IOError return return_string[6:-1] def read_pump_type(self): - """ Read identification information """ - return_string = self.comm('?S801') - pump_type = return_string.split(';') - return {'type': pump_type[0], 'software': pump_type[1], - 'nominal_frequency': pump_type[2]} + """Read identification information""" + return_string = self.comm("?S801") + pump_type = return_string.split(";") + return { + "type": pump_type[0], + "software": pump_type[1], + "nominal_frequency": pump_type[2], + } def read_pump_temperature(self): - """ Read Pump Temperature """ - return_string = self.comm('?V808') - temperatures = return_string.split(';') + """Read Pump Temperature""" + return_string = self.comm("?V808") + temperatures = return_string.split(";") pump = int(temperatures[0]) controller = int(temperatures[1]) - return {'pump': pump, 'controller': controller} + return {"pump": pump, "controller": controller} def read_serial_numbers(self): - """ Read Pump Serial numbers """ - return_string = self.comm('?S835') - service = return_string.split(';') - serials = service[0].split(' ') - return {'Pump SNs': serials[0], 'drive-module SN': serials[1], - 'PCA SN': serials[2], 'type': service[1].strip()} + """Read Pump Serial numbers""" + return_string = self.comm("?S835") + service = return_string.split(";") + serials = service[0].split(" ") + return { + "Pump SNs": serials[0], + "drive-module SN": serials[1], + "PCA SN": serials[2], + "type": service[1].strip(), + } def read_run_hours(self): - """ Return number of run hours """ - return_string = self.comm('?V810') + """Return number of run hours""" + return_string = self.comm("?V810") run_hours = int(return_string) return run_hours def set_run_state(self, on_state): - """ Start or stop the pump """ + """Start or stop the pump""" if on_state is True: - return_string = self.comm('!C802 1') + return_string = self.comm("!C802 1") else: - return_string = self.comm('!C802 0') + return_string = self.comm("!C802 0") return return_string def status_to_bin(self, word): - """ Convert status word to array of binaries """ - status_word = '' + """Convert status word to array of binaries""" + status_word = "" for i in range(0, 4): - val = (int(word[i], 16)) - status_word += (bin(val)[2:].zfill(4)) + val = int(word[i], 16) + status_word += bin(val)[2:].zfill(4) bin_word = [False] * 16 for i in range(0, 15): - bin_word[i] = (status_word[i] == '1') + bin_word[i] = status_word[i] == "1" return bin_word def bearing_service(self): - """ Status of bearings """ - return_string = self.comm('?V815') - status = return_string.split(';') + """Status of bearings""" + return_string = self.comm("?V815") + status = return_string.split(";") time_since = int(status[0]) time_to = int(status[1]) - return {'time_since_service': time_since, 'time_to_service': time_to} + return {"time_since_service": time_since, "time_to_service": time_to} def pump_controller_status(self): - """ Read the status of the pump controller """ - return_string = self.comm('?V813') - status = return_string.split(';') + """Read the status of the pump controller""" + return_string = self.comm("?V813") + status = return_string.split(";") controller_run_time = int(status[0]) time_to_service = int(status[1]) - return {'controller_run_time': controller_run_time, - 'time_to_service': time_to_service} + return { + "controller_run_time": controller_run_time, + "time_to_service": time_to_service, + } def read_normal_speed_threshold(self): - """ Read the value for acknowledge the pump as normally running """ - return_string = self.comm('?S804') + """Read the value for acknowledge the pump as normally running""" + return_string = self.comm("?S804") return int(return_string) def read_standby_speed(self): - """ Read the procentage of full speed on standby """ - return_string = self.comm('?S805') + """Read the procentage of full speed on standby""" + return_string = self.comm("?S805") return int(return_string) def read_pump_status(self): - """ Read the overall status of the pump """ - return_string = self.comm('?V802') - status = return_string.split(';') + """Read the overall status of the pump""" + return_string = self.comm("?V802") + status = return_string.split(";") rotational_speed = int(status[0]) system_status_1 = self.status_to_bin(status[1]) messages = [] if system_status_1[15] is True: - messages.append('Decelerating') + messages.append("Decelerating") if system_status_1[14] is True: - messages.append('Running') + messages.append("Running") if system_status_1[13] is True: - messages.append('Standby Active') + messages.append("Standby Active") if system_status_1[12] is True: - messages.append('Above normal Speed') + messages.append("Above normal Speed") # if system_status_1[11] is True: # It is not entirely clear what this # messages.append('Above ramp speed') # message means if system_status_1[5] is True: - messages.append('Serial interface enabled') + messages.append("Serial interface enabled") system_status_2 = self.status_to_bin(status[2]) if system_status_2[15] is True: - messages.append('At power limit!') + messages.append("At power limit!") if system_status_2[14] is True: - messages.append('Acceleration limited') + messages.append("Acceleration limited") if system_status_2[13] is True: - messages.append('Deceleration limited') + messages.append("Deceleration limited") if system_status_2[11] is True: - messages.append('Time for service!') + messages.append("Time for service!") if system_status_2[9] is True: - messages.append('Warning') + messages.append("Warning") if system_status_2[8] is True: - messages.append('Alarm') + messages.append("Alarm") warnings = [] warning_status = self.status_to_bin(status[3]) if warning_status[14] is True: - warnings.append('Temperature too low') + warnings.append("Temperature too low") if warning_status[9] is True: - warnings.append('Pump too hot') + warnings.append("Pump too hot") if warning_status[5] is True: - warnings.append('Temperature above maxumum measureable value') + warnings.append("Temperature above maxumum measureable value") if warning_status[0] is True: - warnings.append('EEPROM problem - service needed!') + warnings.append("EEPROM problem - service needed!") faults = [] fault_status = self.status_to_bin(status[4]) if fault_status[14] is True: - faults.append('Voltage too high') + faults.append("Voltage too high") if fault_status[13] is True: - faults.append('Current too high') + faults.append("Current too high") if fault_status[12] is True: - faults.append('Temperature too high') + faults.append("Temperature too high") if fault_status[11] is True: - faults.append('Temperature sensor fault') + faults.append("Temperature sensor fault") if fault_status[10] is True: - faults.append('Power stage failure') + faults.append("Power stage failure") if fault_status[7] is True: - faults.append('Hardware latch fault') + faults.append("Hardware latch fault") if fault_status[6] is True: - faults.append('EEPROM problem') + faults.append("EEPROM problem") if fault_status[4] is True: - faults.append('No parameter set') + faults.append("No parameter set") if fault_status[3] is True: - faults.append('Self test fault') + faults.append("Self test fault") if fault_status[2] is True: - faults.append('Serial control interlock') + faults.append("Serial control interlock") if fault_status[1] is True: - faults.append('Overload time out') + faults.append("Overload time out") if fault_status[0] is True: - faults.append('Acceleration time out') - return {'rotational_speed': rotational_speed, 'messages': messages, - 'warnings': warnings, 'faults': faults} + faults.append("Acceleration time out") + return { + "rotational_speed": rotational_speed, + "messages": messages, + "warnings": warnings, + "faults": faults, + } def read_service_status(self): - """ Read the overall status of the pump """ - service_status = self.status_to_bin(self.comm('?V826')) + """Read the overall status of the pump""" + service_status = self.status_to_bin(self.comm("?V826")) messages = [] if service_status[15] is True: - messages.append('Tip seal service is due') + messages.append("Tip seal service is due") if service_status[14] is True: - messages.append('Bearing service is due') + messages.append("Bearing service is due") if service_status[12] is True: - messages.append('Controller service is due') + messages.append("Controller service is due") if service_status[8] is True: - messages.append('Service is due') + messages.append("Service is due") return messages def set_standby_mode(self, standbymode): - """ Set the pump on or off standby mode """ + """Set the pump on or off standby mode""" if standbymode is True: - return_string = self.comm('!C803 1') + return_string = self.comm("!C803 1") else: - return_string = self.comm('!C803 0') + return_string = self.comm("!C803 0") return return_string -if __name__ == '__main__': - PUMP = EdwardsNxds('/dev/ttyUSB3') +if __name__ == "__main__": + PUMP = EdwardsNxds("/dev/ttyUSB3") # print(PUMP.read_pump_type()) print(PUMP.read_pump_temperature()) # print(PUMP.read_serial_numbers()) diff --git a/PyExpLabSys/drivers/epimax.py b/PyExpLabSys/drivers/epimax.py index f9b556cc..3f45fbed 100644 --- a/PyExpLabSys/drivers/epimax.py +++ b/PyExpLabSys/drivers/epimax.py @@ -1,4 +1,3 @@ - """Driver for the Epimax PVCi process vacuum controller There are three controllers share the same kind of communication: @@ -51,6 +50,7 @@ # in the program running using it, since minimal modbus is missing a few corners in the # conversion to Python 2 and 3 support from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) @@ -58,7 +58,9 @@ ############### minimalmodbus.TIMEOUT = 1 -minimalmodbus.FLOAT_ENDIANNESS = '<' +minimalmodbus.FLOAT_ENDIANNESS = "<" + + class PVCCommon(minimalmodbus.Instrument): """Common base for the PVCX, PVCi and PVCiDuo devices @@ -100,60 +102,72 @@ def __init__(self, port, slave_address=1, check_hardware_version=True): # function that converts those 4 bytes to the desired value. self.fields = { # Group 1 - 'global_id': (0x00, 'string', None), - 'firmware_version': (0x02, bytes_to_firmware_version, None), + "global_id": (0x00, "string", None), + "firmware_version": (0x02, bytes_to_firmware_version, None), # Group 2 - 'unit_name': (0x10, 'string', None), - 'user_id': (0x12, 'string', None), + "unit_name": (0x10, "string", None), + "user_id": (0x12, "string", None), # Group 5 - 'slot_a_id': (0x42, bytes_to_slot_id, None), - 'slot_b_id': (0x44, bytes_to_slot_id, None), - 'bakeout_flags': (0x48, bytes_to_bakeout_flags, None), + "slot_a_id": (0x42, bytes_to_slot_id, None), + "slot_b_id": (0x44, bytes_to_slot_id, None), + "bakeout_flags": (0x48, bytes_to_bakeout_flags, None), # Group 9 - 'trip_1_7_status': (0x80, partial(bytes_to_status, status_type='trip'), None), - 'digital_input_1_2_status': (0x82, - partial(bytes_to_status, status_type='digital_input'), - None), + "trip_1_7_status": ( + 0x80, + partial(bytes_to_status, status_type="trip"), + None, + ), + "digital_input_1_2_status": ( + 0x82, + partial(bytes_to_status, status_type="digital_input"), + None, + ), # Group 10 - 'ion_gauge_1_pressure': (0x9A, 'float', 'selected_unit'), + "ion_gauge_1_pressure": (0x9A, "float", "selected_unit"), # Group 14 - 'bake_out_temp_1': (0xD0, 'float', 'C'), - 'bake_out_temp_2': (0xD2, 'float', 'C'), - 'bake_out_temp_3': (0xD4, 'float', 'C'), - 'bake_out_temp_4': (0xD6, 'float', 'C'), - 'bake_out_temp_5': (0xD8, 'float', 'C'), - 'bake_out_temp_6': (0xDA, 'float', 'C'), - 'bake_out_temp_hysteresis': (0xDC, 'float', 'C'), - 'ion_gauge_1_pressure_trip': (0xDE, 'float', 'selected_unit'), + "bake_out_temp_1": (0xD0, "float", "C"), + "bake_out_temp_2": (0xD2, "float", "C"), + "bake_out_temp_3": (0xD4, "float", "C"), + "bake_out_temp_4": (0xD6, "float", "C"), + "bake_out_temp_5": (0xD8, "float", "C"), + "bake_out_temp_6": (0xDA, "float", "C"), + "bake_out_temp_hysteresis": (0xDC, "float", "C"), + "ion_gauge_1_pressure_trip": (0xDE, "float", "selected_unit"), # Group 15 - 'bake_out_time_1': (0xE0, 'float', 'h'), - 'bake_out_time_2': (0xE2, 'float', 'h'), - 'bake_out_time_3': (0xE4, 'float', 'h'), - 'bake_out_time_4': (0xE6, 'float', 'h'), - 'bake_out_time_5': (0xE8, 'float', 'h'), - 'bake_out_time_6': (0xEA, 'float', 'h'), - 'bake_out_setpoint': (0xEC, 'float', 'C'), - 'remaining_bake_out_time': (0xEE, 'float', 'h'), + "bake_out_time_1": (0xE0, "float", "h"), + "bake_out_time_2": (0xE2, "float", "h"), + "bake_out_time_3": (0xE4, "float", "h"), + "bake_out_time_4": (0xE6, "float", "h"), + "bake_out_time_5": (0xE8, "float", "h"), + "bake_out_time_6": (0xEA, "float", "h"), + "bake_out_setpoint": (0xEC, "float", "C"), + "remaining_bake_out_time": (0xEE, "float", "h"), } if check_hardware_version: # Check that this is the correct hardware - ids = self.get_fields(['global_id', 'firmware_version']) - if ids['firmware_version'][0] != self.firmware_name or\ - ids['global_id'] != self.global_id: - message = ('This driver class \'{}\' indicates that this hardware should ' - 'have global_id: \'{}\' and firmware name: \'{}\'. However, ' - 'the values are: \'{}\' and \'{}\'. This driver is not meant ' - 'for this hardware. To run anyway, set ' - 'check_hardware_version=False in __init__') + ids = self.get_fields(["global_id", "firmware_version"]) + if ( + ids["firmware_version"][0] != self.firmware_name + or ids["global_id"] != self.global_id + ): + message = ( + "This driver class '{}' indicates that this hardware should " + "have global_id: '{}' and firmware name: '{}'. However, " + "the values are: '{}' and '{}'. This driver is not meant " + "for this hardware. To run anyway, set " + "check_hardware_version=False in __init__" + ) raise ValueError( message.format( - self.__class__.__name__, self.global_id, self.firmware_name, - ids['global_id'], ids['firmware_version'][0] + self.__class__.__name__, + self.global_id, + self.firmware_name, + ids["global_id"], + ids["firmware_version"][0], ) ) - def close(self): """Close the serial connection""" self.serial.close() @@ -170,11 +184,11 @@ def _read_bytes(self, register_start, count=4): """ raw_value = self.read_string( registeraddress=register_start, - numberOfRegisters=count//2, + numberOfRegisters=count // 2, functioncode=23, ) if sys.version_info.major >= 3: - value = raw_value.encode('latin1') + value = raw_value.encode("latin1") else: value = raw_value return value @@ -194,9 +208,9 @@ def get_field(self, field_name): """ address, type_or_convertion_function, _ = self.fields[field_name] - if type_or_convertion_function == 'string': + if type_or_convertion_function == "string": value = self.read_string(address, 2, 23) - elif type_or_convertion_function == 'float': + elif type_or_convertion_function == "float": value = self.read_float( registeraddress=address, functioncode=23, @@ -206,7 +220,7 @@ def get_field(self, field_name): value = type_or_convertion_function(raw) return value - def get_fields(self, fields='common'): + def get_fields(self, fields="common"): """Return a dict with fields and values for a list of fields This method is specifically for getting multiple values in the shortest @@ -223,15 +237,15 @@ def get_fields(self, fields='common'): dict: Field name to value mapping """ # Update and check fields - if fields == 'common': + if fields == "common": # Form a list of the keys whose address is between 0x80 and 0x9E fields = [key for key, value in self.fields.items() if 0x80 <= value[0] <= 0x9E] - elif fields == 'all': + elif fields == "all": fields = self.fields.keys() else: for field in fields: if field not in self.fields: - message = 'Field name {} is not valid'.format(field) + message = "Field name {} is not valid".format(field) raise KeyError(message) data = {field: self.get_field(field) for field in fields} @@ -242,8 +256,9 @@ def __getattr__(self, attrname): if attrname in self.fields: return self.get_field(attrname) else: - message = '\'{}\' object has no attribute {}'.format(self.__class__.__name__, - attrname) + message = "'{}' object has no attribute {}".format( + self.__class__.__name__, attrname + ) raise AttributeError(message) @@ -256,28 +271,33 @@ class :class:`PVCCommon` """ # Used in the __init__ of PVCCommon to check for the correct hardware version - global_id = 'PVCi' - firmware_name = 'PVCi' + global_id = "PVCi" + firmware_name = "PVCi" def __init__(self, *args, **kwargs): """For specification for __init__ arguments, see :meth:`PVCCommon.__init__`""" super(PVCi, self).__init__(*args, **kwargs) # Update the common field definitions with those specific to the PVCi - self.fields.update({ - 'ion_gauge_1_status': (0x88, - partial(ion_gauge_status, controller_type='pvci'), - None), - 'slot_a_value_1': (0x90, 'float', None), - 'slot_a_value_2': (0x92, 'float', None), - 'slot_b_value_1': (0x94, 'float', None), - 'slot_b_value_2': (0x96, 'float', None), - }) + self.fields.update( + { + "ion_gauge_1_status": ( + 0x88, + partial(ion_gauge_status, controller_type="pvci"), + None, + ), + "slot_a_value_1": (0x90, "float", None), + "slot_a_value_2": (0x92, "float", None), + "slot_b_value_1": (0x94, "float", None), + "slot_b_value_2": (0x96, "float", None), + } + ) ### Convertion Functions ### ############################ + def bytes_to_firmware_version(bytes_): """Convert 4 bytes to firmware type and version""" # Reverse order @@ -292,7 +312,7 @@ def bytes_to_firmware_version(bytes_): unit_type = UNIT_TYPE[unit_code] # The last two are integer major and minor parts of the version - version = '{}.{}'.format(*bytes_as_ints[2:]) + version = "{}.{}".format(*bytes_as_ints[2:]) return unit_type, version @@ -305,20 +325,20 @@ def bytes_to_string(bytes_, valid_chars=None): will be filtered out. """ if valid_chars: - bytes_ = b''.join(c for c in bytes_ if valid_chars[0] <= ord(c) <= valid_chars[1]) - return bytes_.decode('ascii') + bytes_ = b"".join(c for c in bytes_ if valid_chars[0] <= ord(c) <= valid_chars[1]) + return bytes_.decode("ascii") def bytes_to_float(bytes_): """Convert 2 16 bit registers to a float""" - return unpack(' 1: all_state_strings = [] - for bit_num, bit_meaning in enumerate(['on', 'inhibit', 'override']): + for bit_num, bit_meaning in enumerate(["on", "inhibit", "override"]): if state_bits[bit_num]: all_state_strings.append(bit_meaning) - states[status_type + str(state_num)] = ', '.join(all_state_strings) + states[status_type + str(state_num)] = ", ".join(all_state_strings) else: - states[status_type + str(state_num)] = 'off' + states[status_type + str(state_num)] = "off" return states -def byte_to_bits(byte, ): +def byte_to_bits( + byte, +): """Convert a byte to a list of bits""" try: byte_in = ord(byte) except TypeError: byte_in = byte - bits = [b == '1' for b in bin(byte_in)[2:].zfill(8)] + bits = [b == "1" for b in bin(byte_in)[2:].zfill(8)] return bits def raise_if_not_set(bits, index, parameter): """Raise a ValueError if bit is not set""" if not bits[index]: - message = 'Bad \'{}\'. Expected bit {} to be set, got bits {}' + message = "Bad '{}'. Expected bit {} to be set, got bits {}" raise ValueError(message.format(parameter, index, bits)) @@ -387,36 +407,36 @@ def ion_gauge_status(bytes_, controller_type=None): bits = byte_to_bits(next(bytes_)) for bit_, state in zip(bits, ALL_PVC_IONGAUGE_MODES): if bit_: - status['status'] = state + status["status"] = state # Filemant type and number bits = byte_to_bits(next(bytes_)) - if controller_type == 'pvci': - raise_if_not_set(bits, 0, 'filament type') - status['filemant_type'] = 'tungsten' if bits[3] else 'iridium' - raise_if_not_set(bits, 4, 'filemant number') - status['filament_number'] = int(bits[7]) + 1 + if controller_type == "pvci": + raise_if_not_set(bits, 0, "filament type") + status["filemant_type"] = "tungsten" if bits[3] else "iridium" + raise_if_not_set(bits, 4, "filemant number") + status["filament_number"] = int(bits[7]) + 1 # Measurement error and pressure trend bits = byte_to_bits(next(bytes_)) - raise_if_not_set(bits, 0, 'measurement error') - status['measurement_error'] = 'electrometer input below min. limit' if bits[1] else 'none' + raise_if_not_set(bits, 0, "measurement error") + status["measurement_error"] = "electrometer input below min. limit" if bits[1] else "none" - raise_if_not_set(bits, 4, 'ion gauge trend') - status['ion_gauge_trend'] = 'none' - for bit_number, value in zip([7, 6], ['rising', 'falling']): + raise_if_not_set(bits, 4, "ion gauge trend") + status["ion_gauge_trend"] = "none" + for bit_number, value in zip([7, 6], ["rising", "falling"]): if bits[bit_number]: - status['ion_gauge_trend'] = value + status["ion_gauge_trend"] = value # Current ion gauge emission/degas setting - if controller_type == 'pvci': + if controller_type == "pvci": byte = next(bytes_) bits = byte_to_bits(byte) - raise_if_not_set(bits, 0, 'ion gauge emission/degas setting') - status_dict = {'mode': 'manual'} - for bit_number, value in zip([1, 3], ['autoemission', 'quick degas']): + raise_if_not_set(bits, 0, "ion gauge emission/degas setting") + status_dict = {"mode": "manual"} + for bit_number, value in zip([1, 3], ["autoemission", "quick degas"]): if bits[bit_number]: - status_dict['mode'] = value + status_dict["mode"] = value # The current/power is given by an integer formed by the last 4 bits try: @@ -424,17 +444,17 @@ def ion_gauge_status(bytes_, controller_type=None): except TypeError: byte_as_int = byte current_int = byte_as_int % 16 - status_dict['emission'] = PVCI_ION_GAUGE_STATUSSES[current_int] - status['ion_gauge_emission_setting'] = status_dict + status_dict["emission"] = PVCI_ION_GAUGE_STATUSSES[current_int] + status["ion_gauge_emission_setting"] = status_dict else: - raise NotImplementedError('Only controller type pvci is implement for gauge status') + raise NotImplementedError("Only controller type pvci is implement for gauge status") # Only return if there are no bytes left, else raise try: next(bytes_) except StopIteration: return status - raise ValueError('Too many bytes for gauge status') + raise ValueError("Too many bytes for gauge status") def bytes_to_bakeout_flags(bytes_): @@ -444,7 +464,7 @@ def bytes_to_bakeout_flags(bytes_): # Degas at end of bake bits = byte_to_bits(next(bytes_)) - status['degas_at_end_of_bake'] = bits[7] + status["degas_at_end_of_bake"] = bits[7] # Middle two bytes not implemented next(bytes_) @@ -456,9 +476,9 @@ def bytes_to_bakeout_flags(bytes_): for bit_number, flag in BAKEOUT_FLAGS.items(): if bits[bit_number]: status_flags.append(flag) - #if len(status_flags) == 0: + # if len(status_flags) == 0: # status_flags.append('off') - status['status_flags'] = status_flags + status["status_flags"] = status_flags return status @@ -467,52 +487,58 @@ def bytes_to_bakeout_flags(bytes_): ################# ALL_PVC_IONGAUGE_MODES = [ - 'normal', 'fan_fail', 'digital_input_fail', 'over_pressure_fail', # bits 0-3 - 'emmision_failed', 'interlock_trip', 'emmission_trip', # bits 4-6 - 'filament_overcurrent_trip'] # bit 7 + "normal", + "fan_fail", + "digital_input_fail", + "over_pressure_fail", # bits 0-3 + "emmision_failed", + "interlock_trip", + "emmission_trip", # bits 4-6 + "filament_overcurrent_trip", +] # bit 7 PVCI_ION_GAUGE_STATUSSES = { - 0x0: 'OFF', - 0x1: 'IGS_EM_100uA', - 0x2: 'IGS_EM_200uA', - 0x3: 'IGS_EM_500uA', - 0x4: 'IGS_EM_1mA', - 0x5: 'IGS_EM_2mA', - 0x6: 'IGS_EM_5mA', - 0x7: 'IGS_EM_10mA', - 0x8: 'IGS_EM_1W', - 0x9: 'IGS_EM_2W', - 0xA: 'IGS_EM_3W', - 0xB: 'IGS_EM_6W', - 0xC: 'IGS_EM_12W', - 0xD: 'IGS_EM_20W', - 0xE: 'IGS_EM_30W', + 0x0: "OFF", + 0x1: "IGS_EM_100uA", + 0x2: "IGS_EM_200uA", + 0x3: "IGS_EM_500uA", + 0x4: "IGS_EM_1mA", + 0x5: "IGS_EM_2mA", + 0x6: "IGS_EM_5mA", + 0x7: "IGS_EM_10mA", + 0x8: "IGS_EM_1W", + 0x9: "IGS_EM_2W", + 0xA: "IGS_EM_3W", + 0xB: "IGS_EM_6W", + 0xC: "IGS_EM_12W", + 0xD: "IGS_EM_20W", + 0xE: "IGS_EM_30W", } BAKEOUT_FLAGS = { - 7: 'bake-out started', - 6: 'bake-out is inhibited by assigned digital inputs', - 5: 'bake-out is inhibited by ion gauge pressure', - 4: 'bake-out is suspended', - 3: 'bake-out output is on', + 7: "bake-out started", + 6: "bake-out is inhibited by assigned digital inputs", + 5: "bake-out is inhibited by ion gauge pressure", + 4: "bake-out is suspended", + 3: "bake-out output is on", } UNIT_TYPE = { - (0x45, 0x58): 'PVCX', - (0x45, 0x44): 'PVCi', - (0x45, 0x32): 'PVCiDuo', + (0x45, 0x58): "PVCX", + (0x45, 0x44): "PVCi", + (0x45, 0x32): "PVCiDuo", } SLOT_IDS = { - 0: 'empty', - 1: 'ion gauge (internally set)', - 2: 'V module, VG pirani gauge head', - 3: 'K module, type K thermocouple', - 4: 'E module, M and Thyracont Pirani gauge head', - 5: 'U module, universal input range', + 0: "empty", + 1: "ion gauge (internally set)", + 2: "V module, VG pirani gauge head", + 3: "K module, type K thermocouple", + 4: "E module, M and Thyracont Pirani gauge head", + 5: "U module, universal input range", } @@ -526,15 +552,16 @@ def run_module(): """ import logging + logging.basicConfig() log = logging.getLogger() log.setLevel(logging.DEBUG) # '/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FTY3M2GN-if00-port0' - #pvci = PVCi('/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FTY3M2GN-if00-port0') - pvci = PVCi('/dev/ttyUSB0') + # pvci = PVCi('/dev/serial/by-id/usb-FTDI_USB-RS485_Cable_FTY3M2GN-if00-port0') + pvci = PVCi("/dev/ttyUSB0") from pprint import pprint - pprint(pvci.get_fields('all')) + pprint(pvci.get_fields("all")) # Continuous try: @@ -542,12 +569,14 @@ def run_module(): print(pvci.ion_gauge_1_status) for _ in range(20): print( - 'Pressure {:.2e} Setpoint: {:.2f} Actual temp: {:.2f}'.format( - pvci.ion_gauge_1_pressure, pvci.bake_out_setpoint, pvci.slot_b_value_1 + "Pressure {:.2e} Setpoint: {:.2f} Actual temp: {:.2f}".format( + pvci.ion_gauge_1_pressure, + pvci.bake_out_setpoint, + pvci.slot_b_value_1, ) ) except KeyboardInterrupt: - print('closing') + print("closing") if __name__ == "__main__": diff --git a/PyExpLabSys/drivers/four_d_systems.py b/PyExpLabSys/drivers/four_d_systems.py index b17f96ff..5800b169 100644 --- a/PyExpLabSys/drivers/four_d_systems.py +++ b/PyExpLabSys/drivers/four_d_systems.py @@ -36,11 +36,30 @@ """ from __future__ import division, print_function, unicode_literals + # Forces imports of backports of python 3 functionality into python 2.7 with the # python-future module from builtins import ( # pylint: disable=redefined-builtin, unused-import - bytes, dict, int, list, object, range, str, ascii, chr, hex, input, next, oct, open, - pow, round, super, filter, map, zip + bytes, + dict, + int, + list, + object, + range, + str, + ascii, + chr, + hex, + input, + next, + oct, + open, + pow, + round, + super, + filter, + map, + zip, ) from time import sleep @@ -54,47 +73,130 @@ Image = None from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) # TODO: Implement proper logging # Constant(s) -ACKNOWLEDGE = b'\x06' +ACKNOWLEDGE = b"\x06" TEXT_PROPERTY_TO_COMMAND = { - 'bold': b'\xFF\xDE', - 'inverse': b'\xFF\xDC', - 'italic': b'\xFF\xDD', - 'opacity': b'\xFF\xDF', - 'underline': b'\xFF\xDB' + "bold": b"\xFF\xDE", + "inverse": b"\xFF\xDC", + "italic": b"\xFF\xDD", + "opacity": b"\xFF\xDF", + "underline": b"\xFF\xDB", } -SCREEN_MODES = ['landscape', 'landscape reverse', 'portrait', 'portrait reverse'] +SCREEN_MODES = ["landscape", "landscape reverse", "portrait", "portrait reverse"] GRAPHICS_PARAMETERS = [ - 'x_max', 'y_max', 'last_object_left', 'last_object_top', 'last_object_right', - 'last_object_bottom', + "x_max", + "y_max", + "last_object_left", + "last_object_top", + "last_object_right", + "last_object_bottom", ] -TOUCH_STATES = ['enable', 'disable', 'default'] -TOUCH_STATUSSES = ['invalid/notouch', 'press', 'release', 'moving'] +TOUCH_STATES = ["enable", "disable", "default"] +TOUCH_STATUSSES = ["invalid/notouch", "press", "release", "moving"] LATIN_DICT = { - u"¡": u"!", u"¢": u"c", u"£": u"L", u"¤": u"o", u"¥": u"Y", - u"¦": u"|", u"§": u"S", u"¨": u"`", u"©": u"c", u"ª": u"a", - u"«": u"<<", u"¬": u"-", u"­": u"-", u"®": u"R", u"¯": u"-", - u"°": u"o", u"±": u"+-", u"²": u"2", u"³": u"3", u"´": u"'", - u"µ": u"u", u"¶": u"P", u"·": u".", u"¸": u",", u"¹": u"1", - u"º": u"o", u"»": u">>", u"¼": u"1/4", u"½": u"1/2", u"¾": u"3/4", - u"¿": u"?", u"À": u"A", u"Á": u"A", u"Â": u"A", u"Ã": u"A", - u"Ä": u"A", u"Å": u"Aa", u"Æ": u"Ae", u"Ç": u"C", u"È": u"E", - u"É": u"E", u"Ê": u"E", u"Ë": u"E", u"Ì": u"I", u"Í": u"I", - u"Î": u"I", u"Ï": u"I", u"Ð": u"D", u"Ñ": u"N", u"Ò": u"O", - u"Ó": u"O", u"Ô": u"O", u"Õ": u"O", u"Ö": u"O", u"×": u"*", - u"Ø": u"Oe", u"Ù": u"U", u"Ú": u"U", u"Û": u"U", u"Ü": u"U", - u"Ý": u"Y", u"Þ": u"p", u"ß": u"b", u"à": u"a", u"á": u"a", - u"â": u"a", u"ã": u"a", u"ä": u"a", u"å": u"aa", u"æ": u"ae", - u"ç": u"c", u"è": u"e", u"é": u"e", u"ê": u"e", u"ë": u"e", - u"ì": u"i", u"í": u"i", u"î": u"i", u"ï": u"i", u"ð": u"d", - u"ñ": u"n", u"ò": u"o", u"ó": u"o", u"ô": u"o", u"õ": u"o", - u"ö": u"o", u"÷": u"/", u"ø": u"oe", u"ù": u"u", u"ú": u"u", - u"û": u"u", u"ü": u"u", u"ý": u"y", u"þ": u"p", u"ÿ": u"y", - u"’": u"'", u"č": u"c", u"ž": u"z" + "¡": "!", + "¢": "c", + "£": "L", + "¤": "o", + "¥": "Y", + "¦": "|", + "§": "S", + "¨": "`", + "©": "c", + "ª": "a", + "«": "<<", + "¬": "-", + "­": "-", + "®": "R", + "¯": "-", + "°": "o", + "±": "+-", + "²": "2", + "³": "3", + "´": "'", + "µ": "u", + "¶": "P", + "·": ".", + "¸": ",", + "¹": "1", + "º": "o", + "»": ">>", + "¼": "1/4", + "½": "1/2", + "¾": "3/4", + "¿": "?", + "À": "A", + "Á": "A", + "Â": "A", + "Ã": "A", + "Ä": "A", + "Å": "Aa", + "Æ": "Ae", + "Ç": "C", + "È": "E", + "É": "E", + "Ê": "E", + "Ë": "E", + "Ì": "I", + "Í": "I", + "Î": "I", + "Ï": "I", + "Ð": "D", + "Ñ": "N", + "Ò": "O", + "Ó": "O", + "Ô": "O", + "Õ": "O", + "Ö": "O", + "×": "*", + "Ø": "Oe", + "Ù": "U", + "Ú": "U", + "Û": "U", + "Ü": "U", + "Ý": "Y", + "Þ": "p", + "ß": "b", + "à": "a", + "á": "a", + "â": "a", + "ã": "a", + "ä": "a", + "å": "aa", + "æ": "ae", + "ç": "c", + "è": "e", + "é": "e", + "ê": "e", + "ë": "e", + "ì": "i", + "í": "i", + "î": "i", + "ï": "i", + "ð": "d", + "ñ": "n", + "ò": "o", + "ó": "o", + "ô": "o", + "õ": "o", + "ö": "o", + "÷": "/", + "ø": "oe", + "ù": "u", + "ú": "u", + "û": "u", + "ü": "u", + "ý": "y", + "þ": "p", + "ÿ": "y", + "’": "'", + "č": "c", + "ž": "z", } BAUD_RATES = { 110: 0, @@ -130,15 +232,15 @@ def to_ascii(string): def to_ascii_utf8(string): """Convert non-ascii characters in a utf-8 encoded string to ascii""" - string = string.decode('utf-8') + string = string.decode("utf-8") for char in string: if char in LATIN_DICT: string = string.replace(char, LATIN_DICT[char]) - return string.encode('utf-8') + return string.encode("utf-8") # Create to_word function as a partial -to_word = partial(pack, '>H') # pylint: disable=invalid-name +to_word = partial(pack, ">H") # pylint: disable=invalid-name def to_words(*args): @@ -163,7 +265,7 @@ def to_words(*args): convert += list(arg) else: convert.append(arg) - return pack('>' + 'H' * len(convert), *convert) + return pack(">" + "H" * len(convert), *convert) def to_gci(image, resize=None): @@ -174,7 +276,7 @@ def to_gci(image, resize=None): resize (tuple): A 2 element tuple (x, y) for resizing """ if Image is None: - message = 'The to_gci function requires PIL to be installed' + message = "The to_gci function requires PIL to be installed" raise RuntimeError(message) if not isinstance(image, Image.Image): @@ -189,16 +291,16 @@ def to_gci(image, resize=None): x, y = image.size # Get pixels as 1 byte color components (0-255), convert to 0.0-1.0 float color # components and finally convert to Picaso's 16 bit color format - colors = [PicasoCommon._to_16_bit_rgb([x/255 for x in p]) for p in pixels] + colors = [PicasoCommon._to_16_bit_rgb([x / 255 for x in p]) for p in pixels] # NOTE: There is an error in the spec I found online: # https://forum.4dsystems.com.au/forum/forum-aa/faq-frequently-asked-questions/faq/ # 2290-graphics-composer-and-the-display-modules # The color code is just one byte, followed by a zero byte - data = to_words(x, y) + b'\x10\x00' + to_words(colors) + data = to_words(x, y) + b"\x10\x00" + to_words(colors) # Pad data with zero bytes up to 512 bytes remainder = 512 - len(data) % 512 - data = data + b'\x00' * remainder + data = data + b"\x00" * remainder return data @@ -213,8 +315,7 @@ class PicasoCommon(object): the serial communication """ - def __init__(self, serial_device='/dev/ttyUSB0', baudrate=9600, - debug=False): + def __init__(self, serial_device="/dev/ttyUSB0", baudrate=9600, debug=False): """Initialize the driver The serial device and the baudrate are configurable, as described @@ -246,8 +347,9 @@ def close(self): """Close the serial communication""" self.serial.close() - def _send_command(self, command, reply_length=0, output_as_bytes=False, - reply_is_string=False): + def _send_command( + self, command, reply_length=0, output_as_bytes=False, reply_is_string=False + ): """Send a command and return status and reply Args: @@ -273,8 +375,8 @@ def _send_command(self, command, reply_length=0, output_as_bytes=False, # Special case for baud rate change command_as_bytes = bytes(command) - if command_as_bytes[0: 2] == b'\x00\x26': - baudrate_index = int.from_bytes(command_as_bytes[2:], byteorder='big') + if command_as_bytes[0:2] == b"\x00\x26": + baudrate_index = int.from_bytes(command_as_bytes[2:], byteorder="big") baudrate = {v: k for k, v in BAUD_RATES.items()}[baudrate_index] sleep(1) self.serial.baudrate = baudrate @@ -289,44 +391,48 @@ def _send_command(self, command, reply_length=0, output_as_bytes=False, # First check if it succeded acknowledge_as_byte = self.serial.read(1) - if acknowledge_as_byte != b'\x06': - message = 'The command \'{}\' failed with code: {}'.format(command, - acknowledge_as_byte) - raise PicasoException(message, exception_type='failed') + if acknowledge_as_byte != b"\x06": + message = "The command '{}' failed with code: {}".format( + command, acknowledge_as_byte + ) + raise PicasoException(message, exception_type="failed") # The read reply is any if reply_is_string: reply_length = 0 string_length_as_bytes = self.serial.read(2) - string_length = int.from_bytes(string_length_as_bytes, byteorder='big') + string_length = int.from_bytes(string_length_as_bytes, byteorder="big") reply_raw = self.serial.read(string_length) else: if reply_length > 0: reply_raw = self.serial.read(reply_length) else: - reply_raw = b'' + reply_raw = b"" # Make sure there is nothing waiting if self.debug: in_waiting = self.serial.inWaiting() if in_waiting != 0: - message = 'Wrong reply length. There are still {0} bytes '\ - 'left waiting on the serial port'.format(in_waiting) - raise PicasoException(message, exception_type='bytes_still_waiting') + message = ( + "Wrong reply length. There are still {0} bytes " + "left waiting on the serial port".format(in_waiting) + ) + raise PicasoException(message, exception_type="bytes_still_waiting") # Return appropriate value if reply_length > 0: if len(reply_raw) != reply_length: - message = 'The reply length {0} bytes, did not match the '\ - 'requested reply length {1} bytes'.format( - len(reply_raw) - 1, reply_length) - raise PicasoException(message, exception_type='unexpected_reply') + message = ( + "The reply length {0} bytes, did not match the " + "requested reply length {1} bytes".format(len(reply_raw) - 1, reply_length) + ) + raise PicasoException(message, exception_type="unexpected_reply") reply = None if output_as_bytes or reply_is_string: reply = reply_raw else: - reply = int.from_bytes(reply_raw, byteorder='big') + reply = int.from_bytes(reply_raw, byteorder="big") return reply @staticmethod @@ -345,13 +451,13 @@ def _to_16_bit_rgb(color): # 5 bits for red and blue and 6 for green if isinstance(color, str): # Convert e.g. '#ffffff' to [1.0, 1.0, 1.0] - color = [int(color[n: n+2], base=16) / 255 for n in range(1, 6, 2)] + color = [int(color[n : n + 2], base=16) / 255 for n in range(1, 6, 2)] # '0001100011001001' # |-r-||--g-||-b-| - bitstring = '{:05b}{:06b}{:05b}'.format(int(color[0] * 31), - int(color[1] * 63), - int(color[2] * 31)) + bitstring = "{:05b}{:06b}{:05b}".format( + int(color[0] * 31), int(color[1] * 63), int(color[2] * 31) + ) # Turn the bit string into an integer and return return int(bitstring, 2) @@ -369,12 +475,12 @@ def _from_16_bit_rgb(color): """ # '0001100011001001' # |-r-||--g-||-b-| - bitstring = '{:0>16b}'.format(color) + bitstring = "{:0>16b}".format(color) out = [] # Extract the r, g and b parts from the bitstring for start, end in ((0, 5), (5, 11), (11, 16)): # Convert to absolute int value - as_int = int(bitstring[start: end], 2) + as_int = int(bitstring[start:end], 2) # Convert to relative float (0.0 - 1.0) as_float = as_int / (2 ** (end - start) - 1) out.append(as_float) @@ -394,7 +500,7 @@ def move_cursor(self, line, column): # Section .1 Raises: PicasoException: If the command fails """ - command = b'\xFF\xE9' + to_words(line, column) + command = b"\xFF\xE9" + to_words(line, column) self._send_command(command) def put_string(self, string): # Section .3 @@ -414,7 +520,7 @@ def put_string(self, string): # Section .3 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\x00\x18' + string.encode('ascii') + b'\x00' + command = b"\x00\x18" + string.encode("ascii") + b"\x00" return self._send_command(command, 2) def character_width(self, character): # Sub-section .4 @@ -433,8 +539,8 @@ def character_width(self, character): # Sub-section .4 """ if len(character) != 1: - raise ValueError('Character must be a string of length 1') - command = b'\x00\x1E' + character.encode('ascii') + raise ValueError("Character must be a string of length 1") + command = b"\x00\x1E" + character.encode("ascii") return self._send_command(command, 2) def character_height(self, character): # Sub-section .5 @@ -452,8 +558,8 @@ def character_height(self, character): # Sub-section .5 ValueError: If ``character`` does not have length 1 """ if len(character) != 1: - raise ValueError('character must be a string of length 1') - command = b'\x00\x1D' + character.encode('ascii') + raise ValueError("character must be a string of length 1") + command = b"\x00\x1D" + character.encode("ascii") return self._send_command(command, 2) def text_foreground_color(self, color): # Sub-section .6 @@ -470,7 +576,7 @@ def text_foreground_color(self, color): # Sub-section .6 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE7' + to_word(self._to_16_bit_rgb(color)) + command = b"\xFF\xE7" + to_word(self._to_16_bit_rgb(color)) return self._from_16_bit_rgb(self._send_command(command, 2)) def text_background_color(self, color): # Sub-section .7 @@ -487,7 +593,7 @@ def text_background_color(self, color): # Sub-section .7 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE6' + to_word(self._to_16_bit_rgb(color)) + command = b"\xFF\xE6" + to_word(self._to_16_bit_rgb(color)) return self._from_16_bit_rgb(self._send_command(command, 2)) def text_width(self, factor): # Sub-section .9 @@ -503,7 +609,7 @@ def text_width(self, factor): # Sub-section .9 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE4' + to_word(factor) + command = b"\xFF\xE4" + to_word(factor) return self._send_command(command, 2) def text_height(self, factor): # Sub-section .10 @@ -519,7 +625,7 @@ def text_height(self, factor): # Sub-section .10 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE3' + to_word(factor) + command = b"\xFF\xE3" + to_word(factor) return self._send_command(command, 2) def text_factor(self, factor): # Sub-section .9 and .10 @@ -553,7 +659,7 @@ def text_x_gap(self, pixels): # Sub-section .11 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE2' + to_word(pixels) + command = b"\xFF\xE2" + to_word(pixels) return self._send_command(command, 2) def text_y_gap(self, pixels): # Sub-section .11 @@ -569,7 +675,7 @@ def text_y_gap(self, pixels): # Sub-section .11 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xE1' + to_word(pixels) + command = b"\xFF\xE1" + to_word(pixels) return self._send_command(command, 2) def text_attribute(self, attribute, status=True): # Sub-section .13 - .17 @@ -591,10 +697,11 @@ def text_attribute(self, attribute, status=True): # Sub-section .13 - .17 ValueError: If ``attribute`` is unknown """ if attribute not in TEXT_PROPERTY_TO_COMMAND: - message = 'Attribute \'{0}\' unknown. Valid attributes are {1}'\ - .format(attribute, TEXT_PROPERTY_TO_COMMAND.keys()) + message = "Attribute '{0}' unknown. Valid attributes are {1}".format( + attribute, TEXT_PROPERTY_TO_COMMAND.keys() + ) raise ValueError(message) - status = b'\x00\x01' if status else b'\x00\x00' + status = b"\x00\x01" if status else b"\x00\x00" command = TEXT_PROPERTY_TO_COMMAND[attribute] + status reply = self._send_command(command, 2) return True if reply == 1 else False @@ -606,7 +713,7 @@ def clear_screen(self): # Sub-section .1 Raises: PicasoException: If the command fails """ - self._send_command(b'\xFF\xCD') + self._send_command(b"\xFF\xCD") def draw_line(self, start, end, color): # Sub-section .5 """Draw a line from x1, y1 to x2, y2 and return boolean for success @@ -620,7 +727,7 @@ def draw_line(self, start, end, color): # Sub-section .5 Raises: PicasoException: If the command fails """ - command = b'\xFF\xC8' + to_words(start, end, self._to_16_bit_rgb(color)) + command = b"\xFF\xC8" + to_words(start, end, self._to_16_bit_rgb(color)) self._send_command(command) def draw_rectangle(self, top_left, bottom_right, color): # Sub-section .6 @@ -635,7 +742,7 @@ def draw_rectangle(self, top_left, bottom_right, color): # Sub-section .6 Raises: PicasoException: If the command fails """ - command = b'\xFF\xC5' + to_words(top_left, bottom_right, self._to_16_bit_rgb(color)) + command = b"\xFF\xC5" + to_words(top_left, bottom_right, self._to_16_bit_rgb(color)) self._send_command(command) # Sub-section .6 @@ -651,13 +758,13 @@ def draw_filled_rectangle(self, top_left, bottom_right, color): Raises: PicasoException: If the command fails """ - command = b'\xFF\xC4' + to_words(top_left, bottom_right, self._to_16_bit_rgb(color)) + command = b"\xFF\xC4" + to_words(top_left, bottom_right, self._to_16_bit_rgb(color)) self._send_command(command) # Sub-section .14 def put_pixel(self, x, y, color): """Set a pixel""" - command = b'\xFF\xC1' + to_words(x, y, self._to_16_bit_rgb(color)) + command = b"\xFF\xC1" + to_words(x, y, self._to_16_bit_rgb(color)) self._send_command(command) def move_origin(self, x, y): @@ -671,7 +778,7 @@ def move_origin(self, x, y): Raises: PicasoException: If the command fails """ - command = b'\xFF\xCC' + to_words(x, y) + command = b"\xFF\xCC" + to_words(x, y) self._send_command(command) def screen_mode(self, mode): # Sub-section 34 @@ -689,7 +796,7 @@ def screen_mode(self, mode): # Sub-section 34 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\x9E' + to_word(SCREEN_MODES.index(mode)) + command = b"\xFF\x9E" + to_word(SCREEN_MODES.index(mode)) layout_number = self._send_command(command, 2) return SCREEN_MODES[layout_number] @@ -716,7 +823,7 @@ def get_graphics_parameters(self, parameter): # Sub-section 38 PicasoException: If the command fails or if the reply does not have the expected length """ - command = b'\xFF\xA6' + to_word(GRAPHICS_PARAMETERS.index(parameter)) + command = b"\xFF\xA6" + to_word(GRAPHICS_PARAMETERS.index(parameter)) return self._send_command(command, 2) # MEDIE COMMANDS, section 5.3 in the manual @@ -727,7 +834,7 @@ def media_init(self): # Sub-section .1 bool: True is the memory card was present and successfully initialized and False otherwise """ - return_value = self._send_command(b'\xFF\x89', 2) + return_value = self._send_command(b"\xFF\x89", 2) return return_value == 1 def set_sector_address(self, high_word, low_word): @@ -741,7 +848,7 @@ def set_sector_address(self, high_word, low_word): high_word (int): An integer between 0 and 65535. See above. low_word (int): An integer between 0 and 65535. See above. """ - self._send_command(b'\xFF\x92' + to_words(high_word, low_word)) + self._send_command(b"\xFF\x92" + to_words(high_word, low_word)) def read_sector(self): """Read the block (512 bytes) at the sector address pointer @@ -753,11 +860,11 @@ def read_sector(self): sector address pointer will be incremented by 1. """ - val = self._send_command(b'\x00\x16', reply_length=514, output_as_bytes=True) + val = self._send_command(b"\x00\x16", reply_length=514, output_as_bytes=True) success = to_word(val[:2]) if success != 1: - message = 'Sector read failed' - raise PicasoException(message, exception_type='sector_read_failed') + message = "Sector read failed" + raise PicasoException(message, exception_type="sector_read_failed") return val[2:] def write_sector(self, block): @@ -774,7 +881,7 @@ def write_sector(self, block): The address pointer is set with :meth:`set_sector_address`. After the write, the sector address pointer will be incremented by 1. """ - return_value = self._send_command(b'\x00\x17' + block, 2) + return_value = self._send_command(b"\x00\x17" + block, 2) return return_value == 1 def write_sectors(self, blocks): @@ -796,7 +903,7 @@ def write_sectors(self, blocks): """ successes = [] for position in range(0, len(blocks), 512): - block = blocks[position: position + 512] + block = blocks[position : position + 512] successes.append(self.write_sector(block)) return all(successes) @@ -806,7 +913,7 @@ def flush_media(self): Returns: bool: Whether flush operation was successful """ - val = self._send_command(b'\xFF\x8A', 2) + val = self._send_command(b"\xFF\x8A", 2) return val != 0 def display_image(self, x, y): @@ -816,7 +923,7 @@ def display_image(self, x, y): x (int): X-coordinate of the upper left corner y (int): Y-coordinate of the upper left corner """ - self._send_command(b'\xFF\x8B' + to_words(x, y)) + self._send_command(b"\xFF\x8B" + to_words(x, y)) # SERIAL UART COMMUNICATIONS COMMANDS, section 5.4 in the manual def set_baud_rate(self, baud_rate): @@ -832,7 +939,7 @@ def set_baud_rate(self, baud_rate): except KeyError: message = "Invalid baud rate {} requested. Valid values are: {}" raise ValueError(message.format(baud_rate, list(BAUD_RATES.keys()))) - self._send_command(b'\x00\x26' + to_word(index)) + self._send_command(b"\x00\x26" + to_word(index)) # TOUCH SCREEN COMMANDS, section 5.8 in the manual def touch_detect_region(self, upper_left, bottom_right): # Sub-section .1 @@ -847,7 +954,7 @@ def touch_detect_region(self, upper_left, bottom_right): # Sub-section .1 Raises: PicasoException: If the command fails """ - command = b'\xFF\x39' + to_words(upper_left, bottom_right) + command = b"\xFF\x39" + to_words(upper_left, bottom_right) self._send_command(command) def touch_set(self, mode): # Sub-section .2 @@ -864,7 +971,7 @@ def touch_set(self, mode): # Sub-section .2 PicasoException: If the command fails """ mode = TOUCH_STATES.index(mode) - command = b'\xFF\x38' + to_word(mode) + command = b"\xFF\x38" + to_word(mode) self._send_command(command) def touch_get_status(self): # Sub-section .3 @@ -879,7 +986,7 @@ def touch_get_status(self): # Sub-section .3 PicasoException: If the command fails or if the reply does not have the expected length """ - touch_status_number = self._send_command(b'\xFF\x37\x00\x00', 2) + touch_status_number = self._send_command(b"\xFF\x37\x00\x00", 2) return TOUCH_STATUSSES[touch_status_number] def touch_get_coordinates(self): # Sub-section .3 @@ -892,8 +999,8 @@ def touch_get_coordinates(self): # Sub-section .3 PicasoException: If the command fails or if the reply does not have the expected length """ - x_coord = self._send_command(b'\xFF\x37\x00\x01', 2) - y_coord = self._send_command(b'\xFF\x37\x00\x02', 2) + x_coord = self._send_command(b"\xFF\x37\x00\x01", 2) + y_coord = self._send_command(b"\xFF\x37\x00\x02", 2) return x_coord, y_coord # SYSTEM COMMANDS, section 5.10 in the manual @@ -907,8 +1014,8 @@ def get_display_model(self): # Sub-section .3 PicasoException: If the command fails or if the reply does not have the expected length """ - reply = self._send_command(b'\x00\x1A', reply_is_string=True) - return reply.decode('ascii') + reply = self._send_command(b"\x00\x1A", reply_is_string=True) + return reply.decode("ascii") def get_spe_version(self): # Sub-section .4 """Get the version of the Serial Platform Environment @@ -920,8 +1027,8 @@ def get_spe_version(self): # Sub-section .4 PicasoException: If the command fails or if the reply does not have the expected length """ - reply = self._send_command(b'\x00\x1B', 2, output_as_bytes=True) - return '{}.{}'.format(*bytearray(reply)) + reply = self._send_command(b"\x00\x1B", 2, output_as_bytes=True) + return "{}.{}".format(*bytearray(reply)) class PicasouLCD28PTU(PicasoCommon): @@ -931,8 +1038,7 @@ class PicasouLCD28PTU(PicasoCommon): documentation for :py:class:`PicasoCommon` """ - def __init__(self, serial_device='/dev/ttyUSB0', baudrate=9600, - debug=False): + def __init__(self, serial_device="/dev/ttyUSB0", baudrate=9600, debug=False): super(PicasouLCD28PTU, self).__init__(serial_device, baudrate) @@ -951,10 +1057,18 @@ def __init__(self, message, exception_type): class Button(object): """Class that represents a button to use in the interface""" - def __init__(self, picaso, top_left, bottom_right, text, - text_justify='center', left_justify_indent=None, - text_color='#000000', inactive_color='#B2B2B2', - active_color='#979797'): + def __init__( + self, + picaso, + top_left, + bottom_right, + text, + text_justify="center", + left_justify_indent=None, + text_color="#000000", + inactive_color="#B2B2B2", + active_color="#979797", + ): self.picaso = picaso self.text = text self.text_justify = text_justify @@ -969,8 +1083,8 @@ def __init__(self, picaso, top_left, bottom_right, text, self.button_width = None self.set_position(top_left, bottom_right) # Text properties - self.char_height = picaso.character_height('C') - self.char_width = picaso.character_width('C') + self.char_height = picaso.character_height("C") + self.char_width = picaso.character_width("C") def set_position(self, top_left, bottom_right): """Set position of the button""" @@ -983,17 +1097,15 @@ def draw_button(self, active=False): """Draw button with either its active or inactive background color""" # Draw rectangle color = self.active_color if active else self.inactive_color - self.picaso.draw_filled_rectangle(self.top_left, self.bottom_right, - color) + self.picaso.draw_filled_rectangle(self.top_left, self.bottom_right, color) # Calculate text origin y-coordinate by creating splitting remaining # vertical space or default to top of button - origin_y = (self.button_height - self.char_height) // 2 +\ - self.top_left[1] + origin_y = (self.button_height - self.char_height) // 2 + self.top_left[1] origin_y = max(origin_y, self.top_left[1]) # Calculate text origin x-coordinate dependent on justification - if self.text_justify == 'left': + if self.text_justify == "left": # If left if self.left_justify_indent: origin_x = self.top_left[0] + self.left_justify_indent @@ -1006,8 +1118,7 @@ def draw_button(self, active=False): # Set text background and foreground color and write text self.picaso.move_origin(origin_x, origin_y) - old_foreground_color = self.picaso.text_foreground_color( - self.text_color) + old_foreground_color = self.picaso.text_foreground_color(self.text_color) old_background_color = self.picaso.text_background_color(color) self.picaso.put_string(self.text) # Restore colors diff --git a/PyExpLabSys/drivers/freescale_mma7660fc.py b/PyExpLabSys/drivers/freescale_mma7660fc.py index 4a6df833..8b4ffe4d 100644 --- a/PyExpLabSys/drivers/freescale_mma7660fc.py +++ b/PyExpLabSys/drivers/freescale_mma7660fc.py @@ -2,19 +2,21 @@ import time import os from PyExpLabSys.common.supported_versions import python2_and_3 -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +on_rtd = os.environ.get("READTHEDOCS", None) == "True" python2_and_3(__file__) if on_rtd: pass else: import smbus + class MMA7660FC(object): - """ Class for reading accelerometer output """ + """Class for reading accelerometer output""" def __init__(self): self.bus = smbus.SMBus(1) - self.device_address = 0x4c + self.device_address = 0x4C # Turn on the device through MODE register (7) self.bus.write_byte_data(self.device_address, 0x07, 0x01) # Number of samples pr seconds, registor 8 @@ -23,7 +25,7 @@ def __init__(self): time.sleep(0.5) def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" data = self.bus.read_i2c_block_data(0x4C, 0x00, 5) x_value = data[0] & 0x3F @@ -41,9 +43,10 @@ def read_values(self): z_value = z_value - 64 z_value = z_value * 1.5 / 32 - return(x_value, y_value, z_value) + return (x_value, y_value, z_value) + -if __name__ == '__main__': +if __name__ == "__main__": MMA = MMA7660FC() for i in range(0, 20): time.sleep(0.05) diff --git a/PyExpLabSys/drivers/fug.py b/PyExpLabSys/drivers/fug.py index f0bd091a..b45ada4e 100644 --- a/PyExpLabSys/drivers/fug.py +++ b/PyExpLabSys/drivers/fug.py @@ -23,74 +23,73 @@ # Error codes and their interpretations as copied from manuals ERRORCODES = { - 'E0': 'no error', - 'E1': 'no data available', - 'E2': 'unknown register type', - 'E4': 'invalid argument', - 'E5': 'argument out of range', - 'E6': 'register is read only', - 'E7': 'Receive Overflow', - 'E8': 'EEPROM is write protected', - 'E9': 'adress error', - 'E10': 'unknown SCPI command', - 'E11': 'not allowed Trigger-on-Talk', - 'E12': 'invalid argument in ~Tn command', - 'E13': 'invalid N-value', - 'E14': 'register is write only', - 'E15': 'string too long', - 'E16': 'wrong checksum' - } + "E0": "no error", + "E1": "no data available", + "E2": "unknown register type", + "E4": "invalid argument", + "E5": "argument out of range", + "E6": "register is read only", + "E7": "Receive Overflow", + "E8": "EEPROM is write protected", + "E9": "adress error", + "E10": "unknown SCPI command", + "E11": "not allowed Trigger-on-Talk", + "E12": "invalid argument in ~Tn command", + "E13": "invalid N-value", + "E14": "register is write only", + "E15": "string too long", + "E16": "wrong checksum", +} class FUGNTN140Driver(object): """Driver for fug NTN 140 power supply - **Methods** - - * **Private** - - * __init__ - * _check_answer - * _flush_answer - * _get_answer - * _write_register - * _read_register - - * **Public** - - * reset() - * stop() - * is_on() - * output(state=True/False) - * get_state() - * identification_string() - * --- - * set_voltage(value) - * get_voltage() - * monitor_voltage() - * ramp_voltage(value, program=0) - * ramp_voltage_running() - * --- - * set_current(value) - * get_current() - * monitor_current() - * ramp_current(value, program=0) - * ramp_current_running() + **Methods** + + * **Private** + + * __init__ + * _check_answer + * _flush_answer + * _get_answer + * _write_register + * _read_register + + * **Public** + + * reset() + * stop() + * is_on() + * output(state=True/False) + * get_state() + * identification_string() + * --- + * set_voltage(value) + * get_voltage() + * monitor_voltage() + * ramp_voltage(value, program=0) + * ramp_voltage_running() + * --- + * set_current(value) + * get_current() + * monitor_current() + * ramp_current(value, program=0) + * ramp_current_running() """ - def __init__( # pylint: disable=too-many-arguments - self, - port='/dev/ttyUSB0', - baudrate=9600, - parity=serial.PARITY_NONE, - stopbits=serial.STOPBITS_ONE, - bytesize=serial.EIGHTBITS, - device_reset=True, - V_max=6.5, - I_max=10, - ): + self, + port="/dev/ttyUSB0", + baudrate=9600, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS, + device_reset=True, + V_max=6.5, + I_max=10, + ): """Initialize object variables For settings port, baudrate, parity, stopbits, bytesize, see @@ -119,16 +118,16 @@ def __init__( # pylint: disable=too-many-arguments if self.ser.isOpen(): break except AttributeError: - print('Attempt #{}\n'.format(timeout_counter)) + print("Attempt #{}\n".format(timeout_counter)) else: - print('Connection timeout') + print("Connection timeout") sys.exit() # End character - self.end = '\x00' + self.end = "\x00" if not self.ser.isOpen(): - raise IOError('Connection to device is not open') + raise IOError("Connection to device is not open") else: - print('Connected to device: {}'.format(self.identification_string())) + print("Connected to device: {}".format(self.identification_string())) if device_reset: self.reset() self.V_max = V_max @@ -136,17 +135,17 @@ def __init__( # pylint: disable=too-many-arguments # Answer string handling def _check_answer(self): - """Verify correct answer string (neglect previous answers) """ + """Verify correct answer string (neglect previous answers)""" string = self.ser.readline() - if string.decode('ascii').strip() == 'E0': + if string.decode("ascii").strip() == "E0": return True else: self.stop() - raise IOError(string.strip() + ' : ' + ERRORCODES[string.strip()]) + raise IOError(string.strip() + " : " + ERRORCODES[string.strip()]) def _flush_answer(self, print_answer=False): - """Flush answer bytes in waiting (should probably not be used) """ + """Flush answer bytes in waiting (should probably not be used)""" if print_answer: while self.ser.inWaiting() > 0: @@ -155,51 +154,51 @@ def _flush_answer(self, print_answer=False): self.ser.read(self.ser.inWaiting()) def _get_answer(self): - """Get waiting answer string """ + """Get waiting answer string""" string = self.ser.readline() - string = string.decode('ascii') + string = string.decode("ascii") return string # Register handlers def _write_register(self, register, value): - """Alters the value of a register """ + """Alters the value of a register""" - command = '>' + register + ' ' + str(value) + self.end + command = ">" + register + " " + str(value) + self.end self.ser.write(command.encode()) self._check_answer() def _read_register(self, register, value_type=float): - """Queries a register and returns its value """ + """Queries a register and returns its value""" - command = '>' + register + '?' + self.end + command = ">" + register + "?" + self.end self.ser.write(command.encode()) string = self._get_answer() if value_type == float: # Interpret answer as 'float' - return float(string.split(register + ':')[-1]) + return float(string.split(register + ":")[-1]) elif value_type == int: # Interpret answer as 'int' - return int(string.split(register + ':')[-1]) + return int(string.split(register + ":")[-1]) elif value_type == str: # Return entire answer string return string elif value_type == bool: # Interpret answer as 'boolean' (through 'int') - return bool(int(string.split(register + ':')[-1])) + return bool(int(string.split(register + ":")[-1])) else: - raise TypeError('Wrong input value_type') + raise TypeError("Wrong input value_type") # Termination functions def reset(self): - """Resets device """ + """Resets device""" - command = '=' + self.end + command = "=" + self.end self.ser.write(command.encode()) self._check_answer() def stop(self, reset=True): - """Closes device properly before exit """ + """Closes device properly before exit""" if reset: self.reset() @@ -208,40 +207,40 @@ def stop(self, reset=True): # Output interpreters def is_on(self): """Checks if output is ON (>DON) - Returns True if ON + Returns True if ON """ - return self._read_register('DON', bool) + return self._read_register("DON", bool) def output(self, state=False): - """Set output ON (>BON) """ + """Set output ON (>BON)""" if state is True: - register = 'F1' + register = "F1" elif state is False: - register = 'F0' + register = "F0" command = register + self.end self.ser.write(command.encode()) self._check_answer() def get_state(self): - """Checks whether unit is in CV or CC mode (>DVR/>DIR) """ + """Checks whether unit is in CV or CC mode (>DVR/>DIR)""" - if self._read_register('DVR', bool): - return 'CV' - elif self._read_register('DIR', bool): - return 'CC' + if self._read_register("DVR", bool): + return "CV" + elif self._read_register("DIR", bool): + return "CC" else: - return 'No regulation mode detected' + return "No regulation mode detected" def identification_string(self): """Output serial number of device""" - return self._read_register('CFN', str) + return self._read_register("CFN", str) # Voltage interpreters def set_voltage(self, value): - """Sets the voltage (>S0) """ + """Sets the voltage (>S0)""" # Minimum voltage if value < 0.0: @@ -249,17 +248,17 @@ def set_voltage(self, value): # Maximum voltage if value > self.V_max: value = self.V_max - self._write_register('S0', value) + self._write_register("S0", value) def get_voltage(self): - """Reads the set point voltage (>S0A) """ + """Reads the set point voltage (>S0A)""" - return self._read_register('S0A', float) + return self._read_register("S0A", float) def monitor_voltage(self): - """Reads the actual (monitor) voltage (>M0) """ + """Reads the actual (monitor) voltage (>M0)""" - V = self._read_register('M0', float) + V = self._read_register("M0", float) # Correct analog zero if V < 1e-3: V = 0.0 @@ -290,20 +289,20 @@ def ramp_voltage(self, value, program=0): """ if program != -1: - self._write_register('S0B', program) - self._write_register('S0R', value) + self._write_register("S0B", program) + self._write_register("S0R", value) def ramp_voltage_running(self): """Return status of voltage ramp. - True: still ramping - False: ramp complete + True: still ramping + False: ramp complete """ - return self._read_register('S0S', bool) + return self._read_register("S0S", bool) # Current interpreters def set_current(self, value): - """Sets the current (>S1) """ + """Sets the current (>S1)""" # Minimum current if value < 0: @@ -312,17 +311,17 @@ def set_current(self, value): if value > self.I_max: value = self.I_max # Set current - self._write_register('S1', value) + self._write_register("S1", value) def get_current(self): - """Reads the set point current (>S1A) """ + """Reads the set point current (>S1A)""" - return self._read_register('S1A', float) + return self._read_register("S1A", float) def monitor_current(self): - """Reads the actual (monitor) current (>M1) """ + """Reads the actual (monitor) current (>M1)""" - I = self._read_register('M1', float) + I = self._read_register("M1", float) # Correct analog zero if I < 1e-3: I = 0.0 @@ -333,74 +332,80 @@ def ramp_current(self, value, program=0): See ramp_voltage() for description.""" if program != -1: - self._write_register('S1B', program) - self._write_register('S1R', value) + self._write_register("S1B", program) + self._write_register("S1R", value) def ramp_current_running(self): """Return status of current ramp. - True: still ramping - False: ramp complete + True: still ramping + False: ramp complete """ - return self._read_register('S1S', bool) + return self._read_register("S1S", bool) def read_H1(self, ret=False): """Read H1 FIXME not yet done""" t0 = time.time() - command = '>H1?' + self.end + command = ">H1?" + self.end self.ser.write(command.encode()) bytes_ = self.ser.read(36) bytes_ = bytes_[3:-1].decode() # Byte 01 - voltage = int.from_bytes(bytes.fromhex(bytes_[0:4]), byteorder='little')/65535*12.5 + voltage = int.from_bytes(bytes.fromhex(bytes_[0:4]), byteorder="little") / 65535 * 12.5 # Byte 23 - current = int.from_bytes(bytes.fromhex(bytes_[4:8]), byteorder='little')/65535*8 + current = int.from_bytes(bytes.fromhex(bytes_[4:8]), byteorder="little") / 65535 * 8 if ret is True: return voltage, current # Byte 4 - print('Byte 4: ', end='') + print("Byte 4: ", end="") byte = bytes.fromhex(bytes_[8:10]) - bits = bin(int.from_bytes(byte, byteorder='big'))[2:].zfill(2) + bits = bin(int.from_bytes(byte, byteorder="big"))[2:].zfill(2) print(bits) - print('Power supply is {}digitally controlled'.format('not ' if bits[-1] == '0' else '')) - print('Power supply is {}analogue controlled'.format('not ' if bits[-2] == '0' else '')) - print('Power supply is {}in calibration mode'.format('not ' if bits[-3] == '0' else '')) - print('X-STAT: {}'.format(bits[-4])) - print('3-REG: {}'.format(bits[-5])) - print('Output is {}'.format('ON' if bits[-6] == '1' else 'OFF')) - if bits[:2] == '01': - mode = 'is in CV mode' - elif bits[:2] == '10': - mode = 'is in CC mode' - elif bits[:2] == '00': - mode = 'is not regulated' - elif bits[:2] == '11': - mode = 'appears to be in both CV and CC mode' - print('Power supply {}'.format(mode)) + print( + "Power supply is {}digitally controlled".format("not " if bits[-1] == "0" else "") + ) + print( + "Power supply is {}analogue controlled".format("not " if bits[-2] == "0" else "") + ) + print( + "Power supply is {}in calibration mode".format("not " if bits[-3] == "0" else "") + ) + print("X-STAT: {}".format(bits[-4])) + print("3-REG: {}".format(bits[-5])) + print("Output is {}".format("ON" if bits[-6] == "1" else "OFF")) + if bits[:2] == "01": + mode = "is in CV mode" + elif bits[:2] == "10": + mode = "is in CC mode" + elif bits[:2] == "00": + mode = "is not regulated" + elif bits[:2] == "11": + mode = "appears to be in both CV and CC mode" + print("Power supply {}".format(mode)) # Byte 5 - print('Byte 5: ', end='') + print("Byte 5: ", end="") byte = bytes.fromhex(bytes_[10:12]) - bits = bin(int.from_bytes(byte, byteorder='big'))[2:].zfill(8) + bits = bin(int.from_bytes(byte, byteorder="big"))[2:].zfill(8) print(bits) - print('Polarity of voltage: {}'.format('positive' if bits[-1] == '0' else 'negative')) - print('Polarity of current: {}'.format('positive' if bits[-2] == '0' else 'negative')) - + print("Polarity of voltage: {}".format("positive" if bits[-1] == "0" else "negative")) + print("Polarity of current: {}".format("positive" if bits[-2] == "0" else "negative")) + print() # UNUSED 6789 # Byte 10 11 12 13 - print('Serial number: ', end='') + print("Serial number: ", end="") byte = bytes.fromhex(bytes_[20:28]) - print(int.from_bytes(byte, byteorder='big')) + print(int.from_bytes(byte, byteorder="big")) # Byte 14 byte = bytes.fromhex(bytes_[28:30]) - code = int.from_bytes(byte, byteorder='big') - print('Last error code: {}\n'.format(code)) - print('{:6.4} V - {:6.4} A '.format(voltage, current)) - #while self.ser.inWaiting() > 0: + code = int.from_bytes(byte, byteorder="big") + print("Last error code: {}\n".format(code)) + print("{:6.4} V - {:6.4} A ".format(voltage, current)) + # while self.ser.inWaiting() > 0: # bytes_.append(self.ser.read(1)) - #bytes_.append(self.ser.read(32) - print('Command time: {} s'.format(time.time() - t0)) + # bytes_.append(self.ser.read(32) + print("Command time: {} s".format(time.time() - t0)) return bytes_ def print_states(self, t0=0): @@ -409,15 +414,18 @@ def print_states(self, t0=0): V = self.monitor_voltage() I = self.monitor_current() state = self.get_state() - deltat = time.time()-t - print('{:8.2f} s ; {:>6.2f} W ; {:>8.4} V ; {:>8.4} A ; {:4.3f} s ; {}'.format( - t-t0, V*I, V, I, deltat, state)) + deltat = time.time() - t + print( + "{:8.2f} s ; {:>6.2f} W ; {:>8.4} V ; {:>8.4} A ; {:4.3f} s ; {}".format( + t - t0, V * I, V, I, deltat, state + ) + ) def test(): """Module test function""" try: - power = FUGNTN140Driver(port='/dev/ttyUSB2', device_reset=True) + power = FUGNTN140Driver(port="/dev/ttyUSB2", device_reset=True) return power power.output(True) power.ramp_current(value=0.2, program=1) @@ -429,7 +437,7 @@ def test(): power.set_current(2.5) while power.ramp_voltage_running(): power.print_states(t0) - print('Ramp criteria fulfilled') + print("Ramp criteria fulfilled") power.ramp_voltage(0) power.ramp_current(0) power.set_voltage(6.5) @@ -446,5 +454,5 @@ def test(): power.stop() -if __name__ == '__main__': +if __name__ == "__main__": ps = test() diff --git a/PyExpLabSys/drivers/galaxy_3500.py b/PyExpLabSys/drivers/galaxy_3500.py index 10102c00..83763689 100644 --- a/PyExpLabSys/drivers/galaxy_3500.py +++ b/PyExpLabSys/drivers/galaxy_3500.py @@ -4,44 +4,48 @@ """ from __future__ import print_function import telnetlib + try: from credentials import upsuser, upspasswd except ImportError: print("Did not find user/pswd settings in credentials.py. Using default!") - upsuser = 'apc' - upspasswd = 'apc' + upsuser = "apc" + upspasswd = "apc" from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class Galaxy3500(object): - """ Interface driver for a Galaxy3500 UPS. """ + """Interface driver for a Galaxy3500 UPS.""" + def __init__(self, hostname): self.status = {} self.ups_handle = telnetlib.Telnet(hostname) - self.ups_handle.expect([b': ']) - self.ups_handle.write('{}\r'.format(upsuser).encode()) - self.ups_handle.expect([b': ']) - self.ups_handle.write('{}\r'.format(upspasswd).encode()) - self.ups_handle.expect([b'apc>']) + self.ups_handle.expect([b": "]) + self.ups_handle.write("{}\r".format(upsuser).encode()) + self.ups_handle.expect([b": "]) + self.ups_handle.write("{}\r".format(upspasswd).encode()) + self.ups_handle.expect([b"apc>"]) def comm(self, command, keywords=None): - """ Send a command to the ups """ - self.ups_handle.write(command.encode('ascii') + b'\r') - echo = self.ups_handle.expect([b'\r'])[2] - assert(echo == command.encode('ascii') + b'\r') - code = self.ups_handle.expect([b'\r'])[2] - assert('E000' in code.decode()) - output = self.ups_handle.expect([b'apc>'])[2] + """Send a command to the ups""" + self.ups_handle.write(command.encode("ascii") + b"\r") + echo = self.ups_handle.expect([b"\r"])[2] + assert echo == command.encode("ascii") + b"\r" + code = self.ups_handle.expect([b"\r"])[2] + assert "E000" in code.decode() + output = self.ups_handle.expect([b"apc>"])[2] output = output.decode() if keywords is not None: return_val = {} for param in list(keywords): pos = output.find(param) - line = output[pos + len(param) + 1:pos + len(param) + 8].strip() + line = output[pos + len(param) + 1 : pos + len(param) + 8].strip() for i in range(0, 3): try: - value = float(line[:-1 * i]) + value = float(line[: -1 * i]) break except ValueError: pass @@ -51,69 +55,89 @@ def comm(self, command, keywords=None): return return_val def alarms(self): - """ Return list of active alarms """ - warnings = self.comm('alarmcount -p warning', ['WarningAlarmCount']) - criticals = self.comm('alarmcount -p critical', ['CriticalAlarmCount']) - warnings_value = int(warnings['WarningAlarmCount']) - criticals_value = int(criticals['CriticalAlarmCount']) - self.status['WarningAlarmCount'] = warnings_value - self.status['CriticalAlarmCount'] = criticals_value + """Return list of active alarms""" + warnings = self.comm("alarmcount -p warning", ["WarningAlarmCount"]) + criticals = self.comm("alarmcount -p critical", ["CriticalAlarmCount"]) + warnings_value = int(warnings["WarningAlarmCount"]) + criticals_value = int(criticals["CriticalAlarmCount"]) + self.status["WarningAlarmCount"] = warnings_value + self.status["CriticalAlarmCount"] = criticals_value return (warnings_value, criticals_value) def battery_charge(self): - """ Return the battery charge state """ - keyword = 'Battery State Of Charge' - charge = self.comm('detstatus -soc', [keyword]) + """Return the battery charge state""" + keyword = "Battery State Of Charge" + charge = self.comm("detstatus -soc", [keyword]) self.status[keyword] = charge[keyword] return charge[keyword] def temperature(self): - """ Return the temperature of the UPS """ - keyword = 'Internal Temperature' - temp = self.comm('detstatus -tmp', [keyword]) + """Return the temperature of the UPS""" + keyword = "Internal Temperature" + temp = self.comm("detstatus -tmp", [keyword]) self.status[keyword] = temp[keyword] return temp[keyword] def battery_status(self): - """ Return the battery voltage """ - params = ['Battery Voltage', 'Battery Current'] - output = self.comm('detstatus -bat', params) + """Return the battery voltage""" + params = ["Battery Voltage", "Battery Current"] + output = self.comm("detstatus -bat", params) for param in params: self.status[param] = output[param] return output def output_measurements(self): - """ Return status of the device's output """ - params = ['Output Voltage L1', 'Output Voltage L2', 'Output Voltage L3', - 'Output Frequency', 'Output Watts Percent L1', - 'Output Watts Percent L2', 'Output Watts Percent L3', - 'Output VA Percent L1', 'Output VA Percent L2', - 'Output VA Percent L3', 'Output kVA L1', - 'Output kVA L2', 'Output kVA L3', 'Output Current L1', - 'Output Current L2', 'Output Current L3'] - output = self.comm('detstatus -om', params) + """Return status of the device's output""" + params = [ + "Output Voltage L1", + "Output Voltage L2", + "Output Voltage L3", + "Output Frequency", + "Output Watts Percent L1", + "Output Watts Percent L2", + "Output Watts Percent L3", + "Output VA Percent L1", + "Output VA Percent L2", + "Output VA Percent L3", + "Output kVA L1", + "Output kVA L2", + "Output kVA L3", + "Output Current L1", + "Output Current L2", + "Output Current L3", + ] + output = self.comm("detstatus -om", params) for param in params: self.status[param] = output[param] return output def input_measurements(self): - """ Return status of the device's output """ - params = ['Input Voltage L1', 'Input Voltage L2', 'Input Voltage L3', - 'Input Frequency', 'Bypass Input Voltage L1', - 'Bypass Input Voltage L2', 'Bypass Input Voltage L3', - 'Input Current L1', 'Input Current L2', 'Input Current L3'] - output = self.comm('detstatus -im', params) + """Return status of the device's output""" + params = [ + "Input Voltage L1", + "Input Voltage L2", + "Input Voltage L3", + "Input Frequency", + "Bypass Input Voltage L1", + "Bypass Input Voltage L2", + "Bypass Input Voltage L3", + "Input Current L1", + "Input Current L2", + "Input Current L3", + ] + output = self.comm("detstatus -im", params) for param in params: self.status[param] = output[param] return output -if __name__ == '__main__': - UPS = Galaxy3500('ups-b312') + +if __name__ == "__main__": + UPS = Galaxy3500("ups-b312") print(UPS.alarms()) print(UPS.battery_charge()) print(UPS.output_measurements()) print(UPS.input_measurements()) print(UPS.battery_status()) print(UPS.temperature()) - print('---') + print("---") print(UPS.status) diff --git a/PyExpLabSys/drivers/hamamatsu.py b/PyExpLabSys/drivers/hamamatsu.py index b74d2df2..3ed14feb 100644 --- a/PyExpLabSys/drivers/hamamatsu.py +++ b/PyExpLabSys/drivers/hamamatsu.py @@ -5,32 +5,34 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) -class LCL1V5(): - """ Driver for the Hamamatsu LC-L1V5 LED Driver """ + +class LCL1V5: + """Driver for the Hamamatsu LC-L1V5 LED Driver""" def __init__(self, port="/dev/ttyUSB0"): - """ Open the serial port """ + """Open the serial port""" self.ser = serial.Serial(port) - time.sleep(0.1) + time.sleep(0.1) def comm(self, command): - """ Handle sending and receiving commands """ - self.ser.write((command + '\r').encode('ascii')) + """Handle sending and receiving commands""" + self.ser.write((command + "\r").encode("ascii")) time.sleep(0.1) return_string = self.ser.read(self.ser.inWaiting()).decode() print(return_string) return return_string def select_command_communication(self): - """ Selecting command communication opens for sending commands to the controller via the serial interface. - in panel control mode the commands are limited to checking the control mode """ - - command = 'CNT1' + """Selecting command communication opens for sending commands to the controller via the serial interface. + in panel control mode the commands are limited to checking the control mode""" + + command = "CNT1" response = self.comm(command) return response @@ -39,14 +41,14 @@ def check_control_mode(self): CNT0: Panel control CNT1: Command communication """ - - command = 'CNTQ' + + command = "CNTQ" response = self.comm(command) return response def switch_led_on(self, channel): - """ select a led to switch on. - + """select a led to switch on. + Parameters ---------- channel : int @@ -57,13 +59,13 @@ def switch_led_on(self, channel): 4 : 'switch on led 4'} """ - command = 'ON' + str(channel) + command = "ON" + str(channel) response = self.comm(command) return response def switch_led_off(self, channel): - """ select a led to switch off. - + """select a led to switch off. + Parameters ---------- channel : int @@ -73,38 +75,38 @@ def switch_led_off(self, channel): 3 : 'switch off led 3', 4 : 'switch off led 4'} """ - command = 'OFF' + str(channel) + command = "OFF" + str(channel) response = self.comm(command) return response def check_led_status(self, channel): - """ Check the status of a led. For information about steps, refer to manual page 28 - + """Check the status of a led. For information about steps, refer to manual page 28 + Parameters ---------- channel : int - + Return --------- channel, step - + step : {0 : 'off', 1 : 'step 1', 2 : 'step 2', 3 : 'step 3'} """ - command = 'STPQ' + str(channel) + command = "STPQ" + str(channel) response = self.comm(command) - + return response def set_step_settings(self, channel, intensities, times): - """ Sets the intensity and time for all 3 steps on selected channel + """Sets the intensity and time for all 3 steps on selected channel Parameters ---------- channel : int - + intensities (%) : str range : 000 - 100 @@ -113,39 +115,39 @@ def set_step_settings(self, channel, intensities, times): * if times < 10 s range : 0.1 - 9.9 --------- - + Example -------- set_step_settings(1,('010','050','100'),('5.0','10','99')) - + """ - command = 'CURE' + str(channel) - stop_sign = 'STOP' + command = "CURE" + str(channel) + stop_sign = "STOP" # The tuples must contain 3 steps if len(intensities) != 3: print("intensities has to contain intensities of all 3 steps") elif len(times) != 3: print("intensities has to contain intensities of all 3 steps") else: - print('') + print("") # check if the items are strings for i in range(3): if not isinstance(intensities[i], str) or len(intensities[i]) != 3: - print('Intensities must be given as strings with 3 characters') - command += stop_sign + print("Intensities must be given as strings with 3 characters") + command += stop_sign else: - command += (',' + intensities[i]) + command += "," + intensities[i] if not isinstance(times[i], str): - print('Times must be a tuple of strings') + print("Times must be a tuple of strings") command += stop_sign else: - command += (',' + times[i]) + command += "," + times[i] if stop_sign in command: - print('something went wrong with the command') + print("something went wrong with the command") response = stop_sign else: print(command) @@ -155,17 +157,16 @@ def set_step_settings(self, channel, intensities, times): def start_stepped_program(self, channel): """start the programmed irradiation set with set_step_settings""" - command = 'START' + str(channel) + command = "START" + str(channel) response = self.comm(command) return response - - -if __name__ == '__main__': + +if __name__ == "__main__": # Initialize the driver LED = LCL1V5() - + time.sleep(0.5) # Turn on external communication LED.select_command_communication() diff --git a/PyExpLabSys/drivers/honeywell_6000.py b/PyExpLabSys/drivers/honeywell_6000.py index e151c913..84691d54 100644 --- a/PyExpLabSys/drivers/honeywell_6000.py +++ b/PyExpLabSys/drivers/honeywell_6000.py @@ -1,31 +1,34 @@ """ Driver for HIH6000 class temperature and humidity sensors """ import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: pass else: import smbus import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class HIH6130(object): - """ Class for reading pressure and temperature from - Honeywell HumidIcon HIH-6130/6131 """ + """Class for reading pressure and temperature from + Honeywell HumidIcon HIH-6130/6131""" def __init__(self, i2cbus=1): self.bus = smbus.SMBus(i2cbus) self.device_address = 0x27 def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" self.bus.write_quick(0x27) time.sleep(0.05) result = self.bus.read_i2c_block_data(self.device_address, 0, 4) # Two upper bits of byte 0 are stauts bits status = (result[0] & 0b11000000) >> 6 - if status > 1: # Error + if status > 1: # Error return None # The rest of byte 0 is the most significant byte of the total 14 bit value @@ -33,16 +36,16 @@ def read_values(self): # Add this to lower byte to fill in the lower 8 bit hum_total = hum_high + result[1] hum_calibrated = hum_total * 100.0 / (2**14 - 1) - - #3rd byte contans the upper 8 bits of temperature, make room to the six lower bits + + # 3rd byte contans the upper 8 bits of temperature, make room to the six lower bits temp_high = result[2] << 6 - #4th byte contains the lower six bits, shifted by two empty bits - temp_low = + (result[3] & 0b11111100) >> 2 + # 4th byte contains the lower six bits, shifted by two empty bits + temp_low = +(result[3] & 0b11111100) >> 2 temp = temp_high + temp_low - temp_calibrated = (temp * 165.0 / (2**14-1)) - 40 - return(hum_calibrated, temp_calibrated) + temp_calibrated = (temp * 165.0 / (2**14 - 1)) - 40 + return (hum_calibrated, temp_calibrated) -if __name__ == '__main__': +if __name__ == "__main__": HIH = HIH6130() print(HIH.read_values()) diff --git a/PyExpLabSys/drivers/htc5500.py b/PyExpLabSys/drivers/htc5500.py index b440c260..2cbcddcc 100644 --- a/PyExpLabSys/drivers/htc5500.py +++ b/PyExpLabSys/drivers/htc5500.py @@ -4,65 +4,68 @@ import minimalmodbus from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) + def hex_to_dec(hex_string): return int(hex_string, 16) + class temperature_controller(object): - def __init__(self, port, slave_adress=1): - self.instrument = minimalmodbus.Instrument(port, slave_adress, mode = 'rtu') + self.instrument = minimalmodbus.Instrument(port, slave_adress, mode="rtu") self.instrument.serial.baudrate = 9600 self.instrument.serial.bytesize = 8 self.instrument.serial.parity = minimalmodbus.serial.PARITY_EVEN self.instrument.serial.stopbits = 1 - self.instrument.serial.timeout = 1 #seconds - + self.instrument.serial.timeout = 1 # seconds + def read_register(self, register): response = self.instrument.read_register(register) return response def write_register(self, register, setting): self.write_register(register, setting) - + def read_temperature(self): - response = self.instrument.read_register(hex_to_dec('1000'), 1, signed=True) + response = self.instrument.read_register(hex_to_dec("1000"), 1, signed=True) return response def read_set_temperature(self): - response = self.instrument.read_register(hex_to_dec('1001'), 1, signed=True) + response = self.instrument.read_register(hex_to_dec("1001"), 1, signed=True) return response - + def write_set_temperature(self, setting): # Resolution is 0.1 C - self.instrument.write_register(hex_to_dec('1001'), setting, 1, signed=True) - + self.instrument.write_register(hex_to_dec("1001"), setting, 1, signed=True) + def read_control_method(self): - response = self.instrument.read_register(hex_to_dec('1005')) + response = self.instrument.read_register(hex_to_dec("1005")) return response - + def write_control_method(self, setting): # 0: PID, 1: ON/OFF, 2: manual tuning, 3: PID program control if setting == 0 or setting == 1 or setting == 2 or setting == 3: - self.instrument.write_register(hex_to_dec('1005'), int(setting)) + self.instrument.write_register(hex_to_dec("1005"), int(setting)) else: - print('Wrong input') - print('0: PID, 1: ON/OFF, 2: manual tuning, 3: PID grogram control') - + print("Wrong input") + print("0: PID, 1: ON/OFF, 2: manual tuning, 3: PID grogram control") + def read_run_stop(self): - response = self.instrument.read_bit(hex_to_dec('0814')) + response = self.instrument.read_bit(hex_to_dec("0814")) return response - + def write_run_stop(self, setting): # 0: STOP, 1: RUN (default) if setting == 0 or setting == 1: - self.instrument.write_bit(hex_to_dec('0814'), int(setting)) + self.instrument.write_bit(hex_to_dec("0814"), int(setting)) else: - print('Wrong input') - print('0: STOP, 1: RUN (default)') - -if __name__ == '__main__': - port = '/dev/ttyUSB1' - - controller = temperature_controller(port, 1) \ No newline at end of file + print("Wrong input") + print("0: STOP, 1: RUN (default)") + + +if __name__ == "__main__": + port = "/dev/ttyUSB1" + + controller = temperature_controller(port, 1) diff --git a/PyExpLabSys/drivers/inficon_sqm160.py b/PyExpLabSys/drivers/inficon_sqm160.py index fba3a92f..6bab7865 100644 --- a/PyExpLabSys/drivers/inficon_sqm160.py +++ b/PyExpLabSys/drivers/inficon_sqm160.py @@ -3,20 +3,20 @@ import serial import time + class InficonSQM160(object): - """ Driver for Inficon SQM160 QCM controller """ - def __init__(self, port='/dev/ttyUSB0'): - self.serial = serial.Serial(port=port, - baudrate=9600, - timeout=2, - bytesize=serial.EIGHTBITS, - xonxoff=True) + """Driver for Inficon SQM160 QCM controller""" + + def __init__(self, port="/dev/ttyUSB0"): + self.serial = serial.Serial( + port=port, baudrate=9600, timeout=2, bytesize=serial.EIGHTBITS, xonxoff=True + ) def comm(self, command): - """ Implements actual communication with device """ + """Implements actual communication with device""" length = chr(len(command) + 34) crc = self.crc_calc(length + command) - command = '!' + length + command + crc[0] + crc[1] + command = "!" + length + command + crc[0] + crc[1] command_bytes = bytearray() for i in range(0, len(command)): command_bytes.append(ord(command[i])) @@ -27,7 +27,7 @@ def comm(self, command): reply = self.serial.read(self.serial.inWaiting()) crc = self.crc_calc(reply[1:-2]) try: - crc_ok = (reply[-2] == crc[0] and reply[-1] == crc[1]) + crc_ok = reply[-2] == crc[0] and reply[-1] == crc[1] except IndexError: crc_ok = False if crc_ok: @@ -39,12 +39,12 @@ def comm(self, command): @staticmethod def crc_calc(input_string): - """ Calculate crc value of command """ + """Calculate crc value of command""" command_string = [] for i in range(0, len(input_string)): command_string.append(ord(input_string[i])) - crc = int('3fff', 16) - mask = int('2001', 16) + crc = int("3fff", 16) + mask = int("2001", 16) for command in command_string: crc = command ^ crc for i in range(0, 8): @@ -52,49 +52,50 @@ def crc_calc(input_string): crc = crc >> 1 if old_crc % 2 == 1: crc = crc ^ mask - crc1_mask = int('1111111', 2) + crc1_mask = int("1111111", 2) crc1 = chr((crc & crc1_mask) + 34) crc2 = chr((crc >> 7) + 34) - return(crc1, crc2) + return (crc1, crc2) def show_version(self): - """ Read the firmware version """ - command = '@' + """Read the firmware version""" + command = "@" return self.comm(command) def show_film_parameters(self): - """ Read the film paramters """ - command = 'A1?' + """Read the film paramters""" + command = "A1?" print(self.comm(command)) def rate(self, channel=1): - """ Return the deposition rate """ - command = 'L' + str(channel) + """Return the deposition rate""" + command = "L" + str(channel) value_string = self.comm(command) rate = float(value_string) return rate def thickness(self, channel=1): - """ Return the film thickness """ - command = 'N' + str(channel) + """Return the film thickness""" + command = "N" + str(channel) value_string = self.comm(command) thickness = float(value_string) return thickness def frequency(self, channel=1): - """ Return the frequency of the crystal """ - command = 'P' + str(channel) + """Return the frequency of the crystal""" + command = "P" + str(channel) value_string = self.comm(command) frequency = float(value_string) return frequency def crystal_life(self, channel=1): - """ Read crystal life """ - command = 'R' + str(channel) + """Read crystal life""" + command = "R" + str(channel) value_string = self.comm(command) life = float(value_string) return life + if __name__ == "__main__": INFICON = InficonSQM160() print(INFICON.show_version()) @@ -103,4 +104,3 @@ def crystal_life(self, channel=1): print(INFICON.thickness(1)) print(INFICON.frequency(1)) print(INFICON.crystal_life(1)) - diff --git a/PyExpLabSys/drivers/innova.py b/PyExpLabSys/drivers/innova.py index 00fc7bf6..1f053ead 100644 --- a/PyExpLabSys/drivers/innova.py +++ b/PyExpLabSys/drivers/innova.py @@ -13,33 +13,33 @@ #: The first 7 places of the response to the status inquiry are numbers, who #: are paired with the names in the list below STATUS_INQUIRY_NAMES = [ - 'input_voltage', - 'input_fault_voltage', - 'output_voltage', - 'output_current_load_percent', - 'input_frequency', - 'battery_voltage', - 'temperature_C', + "input_voltage", + "input_fault_voltage", + "output_voltage", + "output_current_load_percent", + "input_frequency", + "battery_voltage", + "temperature_C", ] #: The last section of the response to the status inquiry are 0's and 1's, #: which indicate the boolean status of the fields listed below. STATUS_INQUIRY_BOOLEANS = [ - 'utility_fail_immediate', - 'battery_low', - 'bypass_boost_or_buck_active', - 'UPS_failed', - 'UPS_type_is_standby', - 'test_in_progress', - 'shutdown_active', - 'beeper_on', + "utility_fail_immediate", + "battery_low", + "bypass_boost_or_buck_active", + "UPS_failed", + "UPS_type_is_standby", + "test_in_progress", + "shutdown_active", + "beeper_on", ] #: The names for the floats returned as section from the rating information #: command RATING_INFORMATION_FIELDS = [ - 'rating_voltage', - 'rating_current', - 'battery_voltage', - 'frequency', + "rating_voltage", + "rating_current", + "battery_voltage", + "frequency", ] @@ -49,10 +49,9 @@ class Megatec(object): def __init__(self, device, baudrate=2400, timeout=2.0): self.serial = serial.Serial(device, baudrate=baudrate, timeout=timeout) self.serialio = io.TextIOWrapper( - io.BufferedRWPair(self.serial, self.serial), - newline='\r' + io.BufferedRWPair(self.serial, self.serial), newline="\r" ) - print('init') + print("init") def com(self, command): """Perform communication""" @@ -86,35 +85,35 @@ def get_status(self): * beeper_on """ - response = self.com('Q1\r') - if response[0] != '(' or response[-1] != '\r': - msg = ('Unexpect reply on status inquiry. Either did not start ' - 'with "(" or end with "\\r"') + response = self.com("Q1\r") + if response[0] != "(" or response[-1] != "\r": + msg = ( + "Unexpect reply on status inquiry. Either did not start " + 'with "(" or end with "\\r"' + ) raise IOError(msg) # Split into section and - sections = response.strip('(').split(' ') - status = {name: float(value) for name, value in - zip(STATUS_INQUIRY_NAMES, sections)} + sections = response.strip("(").split(" ") + status = {name: float(value) for name, value in zip(STATUS_INQUIRY_NAMES, sections)} # Section 7 are boolean indicators bool_strs = sections[7].strip() for name, bool_str in zip(STATUS_INQUIRY_BOOLEANS, bool_strs): - status[name] = bool_str == '1' + status[name] = bool_str == "1" return status def test_for_10_sec(self): """Run a test of the batteries for 10 sec and return to utility""" - response = self.com('T\r') - if response.strip() != 'ACK': - message = ('UPS response to command "T" was "{}", not "ACK" as ' - 'expected.') + response = self.com("T\r") + if response.strip() != "ACK": + message = 'UPS response to command "T" was "{}", not "ACK" as ' "expected." raise IOError(message.format(response)) def ups_information(self): """Return the UPS information""" - response = self.com('I\r') + response = self.com("I\r") return response.strip() def ups_rating_information(self): @@ -127,12 +126,14 @@ def ups_rating_information(self): * rating_current * rating_voltage """ - response = self.com('F\r') - if response[0] != '#' or response[-1] != '\r': - msg = ('Unexpect reply on status inquiry. Either did not start ' - 'with "#" or end with "\\r"') - raise IOError(msg) - sections = response.strip('#\r').split(' ') + response = self.com("F\r") + if response[0] != "#" or response[-1] != "\r": + msg = ( + "Unexpect reply on status inquiry. Either did not start " + 'with "#" or end with "\\r"' + ) + raise IOError(msg) + sections = response.strip("#\r").split(" ") rating_information = {} for name, value_str in zip(RATING_INFORMATION_FIELDS, sections): @@ -147,10 +148,11 @@ class InnovaRT6K(Megatec): def main(): from pprint import pprint - innova = InnovaRT6K('COM1') - #pprint(innova.get_status()) + + innova = InnovaRT6K("COM1") + # pprint(innova.get_status()) pprint(innova.ups_rating_information()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/intellemetrics_il800.py b/PyExpLabSys/drivers/intellemetrics_il800.py index f59c5e45..1eb5d90c 100644 --- a/PyExpLabSys/drivers/intellemetrics_il800.py +++ b/PyExpLabSys/drivers/intellemetrics_il800.py @@ -1,38 +1,41 @@ """ Driver for IL800 deposition controller """ import serial + class IL800(object): - """ Driver for IL800 depostition controller """ + """Driver for IL800 depostition controller""" + def __init__(self, port): self.serial = serial.Serial(port, 9600, timeout=3, xonxoff=False, rtscts=True) def comm(self, command): - """ Communicate with instrument """ - self.serial.write(command + '\r') + """Communicate with instrument""" + self.serial.write(command + "\r") status = self.serial.read(2) - if status[0] == '0': #Everything ok - ret_value = ' ' - while not ret_value[-1] == '\r': + if status[0] == "0": # Everything ok + ret_value = " " + while not ret_value[-1] == "\r": ret_value += self.serial.read() return ret_value[1:-1] def rate(self): """Return the deposition rate in nm/s""" - rate = self.comm('CHKRATE') + rate = self.comm("CHKRATE") return float(rate) def thickness(self): - """ Return the currently measured thickness in nm """ - thickness = self.comm('CHKTHICKNESS') + """Return the currently measured thickness in nm""" + thickness = self.comm("CHKTHICKNESS") return float(thickness) def frequency(self): - """ Return the qrystal frequency in Hz """ - thickness = self.comm('CHKXTAL') + """Return the qrystal frequency in Hz""" + thickness = self.comm("CHKXTAL") return float(thickness) -if __name__ == '__main__': - IL800_UNIT = IL800('/dev/ttyUSB1') + +if __name__ == "__main__": + IL800_UNIT = IL800("/dev/ttyUSB1") print(IL800_UNIT.rate()) print(IL800_UNIT.thickness()) diff --git a/PyExpLabSys/drivers/isotech_ips.py b/PyExpLabSys/drivers/isotech_ips.py index 90c71a0e..aee76fb6 100644 --- a/PyExpLabSys/drivers/isotech_ips.py +++ b/PyExpLabSys/drivers/isotech_ips.py @@ -7,48 +7,50 @@ import serial import time + class IPS(object): - """ Driver for IPS power supply """ + """Driver for IPS power supply""" + def __init__(self, port): - self.serial = serial.Serial(port, 2400, timeout=10, xonxoff=False, - rtscts=False) + self.serial = serial.Serial(port, 2400, timeout=10, xonxoff=False, rtscts=False) def comm(self, command): - """ Communicate with instrument """ - encoded_command = (command + '\r').encode('ascii') + """Communicate with instrument""" + encoded_command = (command + "\r").encode("ascii") self.serial.write(encoded_command) # The unit will fail to run at more than 2Hz time.sleep(0.5) return True def set_vlimit_to_max(self): - """ Set the voltage limit to the maximum the device deliver """ - self.comm('SUM') + """Set the voltage limit to the maximum the device deliver""" + self.comm("SUM") def set_ilimit_to_max(self): - """ Set the current limit to the maximum the device deliver """ - self.comm('SIM') + """Set the current limit to the maximum the device deliver""" + self.comm("SIM") def set_relay_status(self, status=False): - """ Turn the output on or off """ + """Turn the output on or off""" if status is True: - self.comm('KOE') + self.comm("KOE") else: - self.comm('KOD') + self.comm("KOD") def set_output_voltage(self, voltage): - """ Set the output voltage """ - self.comm('SV ' + '{:2.2f}'.format(voltage).zfill(5)) + """Set the output voltage""" + self.comm("SV " + "{:2.2f}".format(voltage).zfill(5)) def set_voltage_limit(self, voltage): - """ Set the voltage limit """ - self.comm('SU ' + str(voltage)) + """Set the voltage limit""" + self.comm("SU " + str(voltage)) def set_current_limit(self, current): - """ Set the current limit """ - self.comm('SI ' + '{:1.2f}'.format(current).zfill(3)) + """Set the current limit""" + self.comm("SI " + "{:1.2f}".format(current).zfill(3)) + -if __name__ == '__main__': - ips = IPS('/dev/ttyUSB2') +if __name__ == "__main__": + ips = IPS("/dev/ttyUSB2") ips.set_relay_status(True) ips.set_output_voltage(5) diff --git a/PyExpLabSys/drivers/keithley_2700.py b/PyExpLabSys/drivers/keithley_2700.py index 2e1a649a..0646943a 100644 --- a/PyExpLabSys/drivers/keithley_2700.py +++ b/PyExpLabSys/drivers/keithley_2700.py @@ -3,83 +3,95 @@ import time from PyExpLabSys.drivers.scpi import SCPI from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class KeithleySMU(SCPI): - """ Simple driver for Keithley Model 2700 """ + """Simple driver for Keithley Model 2700""" - def __init__(self, interface, device='/dev/ttyUSB0'): - if interface == 'serial': - SCPI.__init__(self, interface='serial', device=device, baudrate=9600) - self.scpi_comm('FORMAT:ELEMENTS READ') # Set short read-format + def __init__(self, interface, device="/dev/ttyUSB0"): + if interface == "serial": + SCPI.__init__(self, interface="serial", device=device, baudrate=9600) + self.scpi_comm("FORMAT:ELEMENTS READ") # Set short read-format def select_measurement_function(self, function): - """ Select a measurement function. + """Select a measurement function. Keyword arguments: Function -- A string stating the wanted measurement function. """ - values = ['CAPACITANCE', 'CONTINUITY', 'CURRENT', 'DIODE', 'FREQUENCY', - 'RESISTANCE', 'FRESISTANCE', 'TEMPERATURE', 'VOLTAGE'] + values = [ + "CAPACITANCE", + "CONTINUITY", + "CURRENT", + "DIODE", + "FREQUENCY", + "RESISTANCE", + "FRESISTANCE", + "TEMPERATURE", + "VOLTAGE", + ] return_value = False if function in values: return_value = True - function_string = "FUNCTION " + "\"" + function + "\"" + function_string = "FUNCTION " + '"' + function + '"' self.scpi_comm(function_string) return return_value def read(self): - """ Read a value from the device """ + """Read a value from the device""" value = float(self.scpi_comm("READ?")) return value - -if __name__ == '__main__': - PORT = '/dev/ttyUSB0' - K2700 = KeithleySMU(interface='serial', device=PORT) - #K2700.reset_device() + +if __name__ == "__main__": + PORT = "/dev/ttyUSB0" + + K2700 = KeithleySMU(interface="serial", device=PORT) + # K2700.reset_device() time.sleep(1) print(K2700.read_software_version()) - #print('--') - #print(K2700.read()) - #print('--') - #K2700.select_measurement_function('RESISTANCE') - #print(K2700.read()) - #print('--') - #K2700.select_measurement_function('TEMPERATURE') - #print(K2700.read()) - #print('--') - #print(K2700.scpi_comm('ROUTE:SCAN (@101:108)')) - #print(K2700.scpi_comm('ROUTE:SCAN?')) - #print(K2700.scpi_comm('TRIGGER:COUNT 1')) - #print(K2700.scpi_comm('SAMP:COUNT 8')) - #print(K2700.scpi_comm('READ?')) - #print(K2700.scpi_comm('ROUNT:SCAN:NVOL ON')) - #K2700.scpi_comm('INIT') - #print(K2700.scpi_comm('READ?')) - #print(K2700.scpi_comm('ROUTE:SCAN:LSEL NONE')) - #print(K2700.scpi_comm('CALC1:DATA?')) - #print(K2700.read()) + # print('--') + # print(K2700.read()) + # print('--') + # K2700.select_measurement_function('RESISTANCE') + # print(K2700.read()) + # print('--') + # K2700.select_measurement_function('TEMPERATURE') + # print(K2700.read()) + # print('--') + # print(K2700.scpi_comm('ROUTE:SCAN (@101:108)')) + # print(K2700.scpi_comm('ROUTE:SCAN?')) + # print(K2700.scpi_comm('TRIGGER:COUNT 1')) + # print(K2700.scpi_comm('SAMP:COUNT 8')) + # print(K2700.scpi_comm('READ?')) + # print(K2700.scpi_comm('ROUNT:SCAN:NVOL ON')) + # K2700.scpi_comm('INIT') + # print(K2700.scpi_comm('READ?')) + # print(K2700.scpi_comm('ROUTE:SCAN:LSEL NONE')) + # print(K2700.scpi_comm('CALC1:DATA?')) + # print(K2700.read()) - K2700.scpi_comm('TRAC:CLE') - K2700.scpi_comm('INIT:CONT OFF') - K2700.scpi_comm('TRAC:CLE') - K2700.scpi_comm('TRIG:SOUR IMM') - K2700.scpi_comm('TRIG COUN 1') - K2700.scpi_comm('SAMP:COUNT 1') + K2700.scpi_comm("TRAC:CLE") + K2700.scpi_comm("INIT:CONT OFF") + K2700.scpi_comm("TRAC:CLE") + K2700.scpi_comm("TRIG:SOUR IMM") + K2700.scpi_comm("TRIG COUN 1") + K2700.scpi_comm("SAMP:COUNT 1") for i in range(1, 11): - scan_list = '(@1' + str(i).zfill(2) + ')' + scan_list = "(@1" + str(i).zfill(2) + ")" time.sleep(0.25) command = "SENS:FUNC 'RESISTANCE', " + scan_list print(command) K2700.scpi_comm(command) time.sleep(0.25) - K2700.scpi_comm('ROUNT:SCAN ' + scan_list) - K2700.scpi_comm('ROUT:SCAN:TSO IMM') - K2700.scpi_comm('ROUT:SCAN:LSEL INT') - print(K2700.scpi_comm('READ?')) - K2700.scpi_comm('ROUT:SCAN:LSEL NONE') + K2700.scpi_comm("ROUNT:SCAN " + scan_list) + K2700.scpi_comm("ROUT:SCAN:TSO IMM") + K2700.scpi_comm("ROUT:SCAN:LSEL INT") + print(K2700.scpi_comm("READ?")) + K2700.scpi_comm("ROUT:SCAN:LSEL NONE") diff --git a/PyExpLabSys/drivers/keithley_smu.py b/PyExpLabSys/drivers/keithley_smu.py index 4a60912e..9873daae 100644 --- a/PyExpLabSys/drivers/keithley_smu.py +++ b/PyExpLabSys/drivers/keithley_smu.py @@ -4,126 +4,155 @@ import logging from PyExpLabSys.drivers.scpi import SCPI from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) -class KeithleySMU(SCPI): - """ Simple driver for Keithley SMU """ - def __init__(self, interface, hostname='', device='', baudrate=9600): - if interface == 'serial': - SCPI.__init__(self, interface=interface, device=device, - baudrate=baudrate, line_ending='\n') +class KeithleySMU(SCPI): + """Simple driver for Keithley SMU""" + + def __init__(self, interface, hostname="", device="", baudrate=9600): + if interface == "serial": + SCPI.__init__( + self, + interface=interface, + device=device, + baudrate=baudrate, + line_ending="\n", + ) self.comm_dev.timeout = 2 self.comm_dev.rtscts = False self.comm_dev.xonxoff = False print(self.comm_dev) - if interface == 'lan': + if interface == "lan": SCPI.__init__(self, interface=interface, hostname=hostname) - self.channel_names = {1: 'a', 2: 'b'} + self.channel_names = {1: "a", 2: "b"} def output_state(self, output_on=False, channel=1): - """ Turn the output on or off """ + """Turn the output on or off""" if output_on is True: - self.scpi_comm('smu' + self.channel_names[channel] + '.source.output = 1') + self.scpi_comm("smu" + self.channel_names[channel] + ".source.output = 1") else: - self.scpi_comm('smu' + self.channel_names[channel] + '.source.output = 0') + self.scpi_comm("smu" + self.channel_names[channel] + ".source.output = 0") return output_on def set_current_measure_range(self, current_range=None, channel=1): - """ Set the current measurement range """ - ch_name = 'smu' + self.channel_names[channel] + '.' + """Set the current measurement range""" + ch_name = "smu" + self.channel_names[channel] + "." if current_range is None: - self.scpi_comm(ch_name + 'measure.autorangei = ' + ch_name + 'AUTORANGE_ON') + self.scpi_comm(ch_name + "measure.autorangei = " + ch_name + "AUTORANGE_ON") else: - self.scpi_comm(ch_name + 'measure.rangei = ' + str(current_range)) + self.scpi_comm(ch_name + "measure.rangei = " + str(current_range)) return True def set_integration_time(self, nplc=None, channel=1): - """ Set the measurement integration time """ - ch_name = 'smu' + self.channel_names[channel] + '.' + """Set the measurement integration time""" + ch_name = "smu" + self.channel_names[channel] + "." if nplc is None: - self.scpi_comm(ch_name + 'measure.nplc = 1') + self.scpi_comm(ch_name + "measure.nplc = 1") else: - self.scpi_comm(ch_name + 'measure.nplc = ' + str(nplc)) + self.scpi_comm(ch_name + "measure.nplc = " + str(nplc)) return True def read_current(self, channel=1): - """ Read the measured current """ - self.scpi_comm('reading = smu' + self.channel_names[channel] + '.measure.i()') - self.scpi_comm('*TRG') - current_string = self.scpi_comm('print(reading)', True) + """Read the measured current""" + self.scpi_comm("reading = smu" + self.channel_names[channel] + ".measure.i()") + self.scpi_comm("*TRG") + current_string = self.scpi_comm("print(reading)", True) try: current = float(current_string) except (ValueError, TypeError): current = None - logging.error('Current string: ' + str(current_string)) + logging.error("Current string: " + str(current_string)) return current def read_voltage(self, channel=1): - """ Read the measured voltage """ - self.scpi_comm('reading = smu' + self.channel_names[channel] + '.measure.v()') - self.scpi_comm('*TRG') - voltage_string = self.scpi_comm('print(reading)', True) + """Read the measured voltage""" + self.scpi_comm("reading = smu" + self.channel_names[channel] + ".measure.v()") + self.scpi_comm("*TRG") + voltage_string = self.scpi_comm("print(reading)", True) try: voltage = float(voltage_string) except (ValueError, TypeError): voltage = None - logging.error('Voltage string: ' + str(voltage_string)) + logging.error("Voltage string: " + str(voltage_string)) return voltage def set_source_function(self, function, channel=1): - scpi_string = ('smu' + self.channel_names[channel] + '.source.func = ' + - self.channel_names[channel] + '.OUTPUT_') - if function in ('i', 'I'): - self.scpi_comm(scpi_string + 'DC_AMPS') - print('Source function: Current') - if function in ('v', 'V'): - print('Source function: Voltage') - self.scpi_comm(scpi_string + 'DC_VOLTS') + scpi_string = ( + "smu" + + self.channel_names[channel] + + ".source.func = " + + self.channel_names[channel] + + ".OUTPUT_" + ) + if function in ("i", "I"): + self.scpi_comm(scpi_string + "DC_AMPS") + print("Source function: Current") + if function in ("v", "V"): + print("Source function: Voltage") + self.scpi_comm(scpi_string + "DC_VOLTS") def set_current_limit(self, current, channel=1): - """ Set the desired current limit """ - self.scpi_comm('smu' + self.channel_names[channel] + - '.source.limiti = ' + str(current)) + """Set the desired current limit""" + self.scpi_comm( + "smu" + self.channel_names[channel] + ".source.limiti = " + str(current) + ) def set_voltage(self, voltage, channel=1): - """ Set the desired voltage """ - self.scpi_comm('smu' + self.channel_names[channel] + - '.source.levelv = ' + str(voltage)) + """Set the desired voltage""" + self.scpi_comm( + "smu" + self.channel_names[channel] + ".source.levelv = " + str(voltage) + ) def set_voltage_limit(self, voltage, channel=1): - """ Set the desired voltate limit """ - self.scpi_comm('smu' + self.channel_names[channel] + - '.source.limitv = ' + str(voltage)) + """Set the desired voltate limit""" + self.scpi_comm( + "smu" + self.channel_names[channel] + ".source.limitv = " + str(voltage) + ) def set_current(self, current, channel=1): - """ Set the desired current """ - self.scpi_comm('smu' + self.channel_names[channel] + - '.source.leveli = ' + str(current)) + """Set the desired current""" + self.scpi_comm( + "smu" + self.channel_names[channel] + ".source.leveli = " + str(current) + ) def iv_scan(self, v_from, v_to, steps, settle_time, channel=1): - """ Perform iv_scan """ - ch_name = 'smu' + self.channel_names[channel] - self.scpi_comm('SweepVLinMeasureI('+ ch_name + ', ' + - str(v_from) + ', ' + - str(v_to) + ', ' + - str(settle_time) + ', ' + - str(steps) + ')') - readings = (self.scpi_comm('printbuffer(1, ' + str(steps) + ', ' + ch_name + - '.nvbuffer1.readings)', True)) - sourcevalues = (self.scpi_comm('printbuffer(1, ' + str(steps) + ', ' + ch_name + - '.nvbuffer1.sourcevalues)', True)) - readings = readings.split(',') - sourcevalues = sourcevalues.split(',') + """Perform iv_scan""" + ch_name = "smu" + self.channel_names[channel] + self.scpi_comm( + "SweepVLinMeasureI(" + + ch_name + + ", " + + str(v_from) + + ", " + + str(v_to) + + ", " + + str(settle_time) + + ", " + + str(steps) + + ")" + ) + readings = self.scpi_comm( + "printbuffer(1, " + str(steps) + ", " + ch_name + ".nvbuffer1.readings)", + True, + ) + sourcevalues = self.scpi_comm( + "printbuffer(1, " + str(steps) + ", " + ch_name + ".nvbuffer1.sourcevalues)", + True, + ) + readings = readings.split(",") + sourcevalues = sourcevalues.split(",") for i in range(0, steps): readings[i] = float(readings[i]) sourcevalues[i] = float(sourcevalues[i]) return (sourcevalues, readings) -if __name__ == '__main__': - PORT = '/dev/ttyUSB0' - SMU = KeithleySMU(interface='serial', device=PORT, baudrate=4800) + +if __name__ == "__main__": + PORT = "/dev/ttyUSB0" + SMU = KeithleySMU(interface="serial", device=PORT, baudrate=4800) print(SMU.comm_dev.inWaiting()) SMU.comm_dev.read(SMU.comm_dev.inWaiting()) @@ -131,27 +160,26 @@ def iv_scan(self, v_from, v_to, steps, settle_time, channel=1): print(SMU.read_voltage(1)) print(SMU.read_software_version()) - #print(SMU) - #SMU.set_source_function('i') - #SMU.output_state(True) - #time.sleep(1) - #SMU.set_voltage(0.00) - #time.sleep(1) - #print(SMU.set_voltage_limit(1)) - #time.sleep(1) - #SMU.set_current(0.0) - #time.sleep(3) - #print('Voltage: ' + str(SMU.read_voltage())) - #print('Current: ' + str(SMU.read_current())) - #print('-') - #time.sleep(1) - #SMU.output_state(False) - - #print(SMU.read_software_version()) - #print('-') - #print(SMU.read_current()) - #print('-') - #print(SMU.read_voltage()) - #print('-') - #print(SMU.iv_scan(v_from=-1.1, v_to=0, steps=10, settle_time=0)) - + # print(SMU) + # SMU.set_source_function('i') + # SMU.output_state(True) + # time.sleep(1) + # SMU.set_voltage(0.00) + # time.sleep(1) + # print(SMU.set_voltage_limit(1)) + # time.sleep(1) + # SMU.set_current(0.0) + # time.sleep(3) + # print('Voltage: ' + str(SMU.read_voltage())) + # print('Current: ' + str(SMU.read_current())) + # print('-') + # time.sleep(1) + # SMU.output_state(False) + + # print(SMU.read_software_version()) + # print('-') + # print(SMU.read_current()) + # print('-') + # print(SMU.read_voltage()) + # print('-') + # print(SMU.iv_scan(v_from=-1.1, v_to=0, steps=10, settle_time=0)) diff --git a/PyExpLabSys/drivers/kjlc_pressure_gauge.py b/PyExpLabSys/drivers/kjlc_pressure_gauge.py index c74edeaa..fcb52f83 100644 --- a/PyExpLabSys/drivers/kjlc_pressure_gauge.py +++ b/PyExpLabSys/drivers/kjlc_pressure_gauge.py @@ -2,32 +2,40 @@ import serial import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) KJLC = None + class KJLC300(object): - """ Class implements a KJLC interface """ + """Class implements a KJLC interface""" def __init__(self, port): - self.connection = serial.Serial(port, baudrate=1200, bytesize=8, - parity='N', stopbits=1, timeout=1, - writeTimeout=1) + self.connection = serial.Serial( + port, + baudrate=1200, + bytesize=8, + parity="N", + stopbits=1, + timeout=1, + writeTimeout=1, + ) def close(self): - """ Closes connection """ + """Closes connection""" self.connection.close() def read_software_version(self): - """ Reads software version """ - self.connection.write('#01VER\r'.encode('ascii')) + """Reads software version""" + self.connection.write("#01VER\r".encode("ascii")) raw = self.connection.read(13) out = self._format_output(raw) return out def read_pressure(self): - """ Reads pressure in Torr """ - self.connection.write('#01RD\r'.encode('ascii')) + """Reads pressure in Torr""" + self.connection.write("#01RD\r".encode("ascii")) raw = self.connection.read(13) out = self._format_output(raw) try: @@ -35,12 +43,11 @@ def read_pressure(self): out = 1.33322 * out time.sleep(0.1) except ValueError: - out = 'error' + out = "error" return out def _format_output(self, string): - """ Strip *, the adress and a space from the beginning and a CR from the + """Strip *, the adress and a space from the beginning and a CR from the end """ return string[4:-1] - diff --git a/PyExpLabSys/drivers/lascar.py b/PyExpLabSys/drivers/lascar.py index ae12bd79..7d8dcff3 100644 --- a/PyExpLabSys/drivers/lascar.py +++ b/PyExpLabSys/drivers/lascar.py @@ -18,6 +18,7 @@ from __future__ import division, print_function import struct + try: import hid except (ImportError, AttributeError): @@ -32,11 +33,11 @@ class ElUsbRt(object): def __init__(self, device_path=None): if device_path is None: for dev in hid.enumerate(): - if dev['product_string'] == 'EL USB RT': - path = dev['path'] + if dev["product_string"] == "EL USB RT": + path = dev["path"] if path is None: - message = 'No path give and unable to find it' + message = "No path give and unable to find it" raise ValueError(message) self.dev = hid.Device(path=path) @@ -49,24 +50,24 @@ def get_temperature_and_humidity(self): out = {} while len(out) < 2: string = self.dev.read(8) - if string.startswith('\x03'): - frac, = struct.unpack('H', string[1:]) - out['temperature'] = -200 + frac * 0.1 - elif string.startswith('\x02'): - frac, = struct.unpack('B', string[1:]) - out['humidity'] = frac * 0.5 + if string.startswith("\x03"): + (frac,) = struct.unpack("H", string[1:]) + out["temperature"] = -200 + frac * 0.1 + elif string.startswith("\x02"): + (frac,) = struct.unpack("B", string[1:]) + out["humidity"] = frac * 0.5 return out def get_temperature(self): """Returns the temperature (in celcius, float)""" while True: string = self.dev.read(8) - if string.startswith('\x03'): - frac, = struct.unpack('H', string[1:]) + if string.startswith("\x03"): + (frac,) = struct.unpack("H", string[1:]) return -200 + frac * 0.1 -if __name__ == '__main__': +if __name__ == "__main__": DEV = ElUsbRt() while True: print(DEV.get_temperature()) diff --git a/PyExpLabSys/drivers/luxaflex.py b/PyExpLabSys/drivers/luxaflex.py index 0b507f5b..cbc23ec8 100644 --- a/PyExpLabSys/drivers/luxaflex.py +++ b/PyExpLabSys/drivers/luxaflex.py @@ -4,7 +4,7 @@ class PowerView(object): def __init__(self, address): - self.address = 'http://' + address + '/api/' + self.address = "http://" + address + "/api/" self.rooms = self._find_rooms() # Fast, does not talk to shades self.shades = {} @@ -20,21 +20,21 @@ def _find_rooms(self): Find configured rooms, this is an internal operatioin that does not need contact to the shades. """ - r = requests.get(self.address + 'rooms') + r = requests.get(self.address + "rooms") rooms = {} rooms_raw = r.json() - for room in rooms_raw['roomData']: - rooms[room['id']] = self._decode_name(room['name']) + for room in rooms_raw["roomData"]: + rooms[room["id"]] = self._decode_name(room["name"]) return rooms def _jog_shade(self, shade_id): """ Jog the shade to visually identify it. """ - msg = '{}shades/{}'.format(self.address, shade_id) + msg = "{}shades/{}".format(self.address, shade_id) payload = {"shade": {"motion": "jog"}} r = requests.put(msg, json=payload) - success = (r.status_code == 200) + success = r.status_code == 200 return success def hub_info(self): @@ -42,7 +42,7 @@ def hub_info(self): Find configured rooms, this is an internal operatioin that does not need contact to the shades. """ - r = requests.get(self.address + 'userdata') + r = requests.get(self.address + "userdata") hub_raw = r.json() print(hub_raw) @@ -51,19 +51,19 @@ def find_all_shades(self): Find all configured shades. If contact can be established, each shade will be populated with current info. """ - r = requests.get(self.address + 'shades?') + r = requests.get(self.address + "shades?") shades_raw = r.json() - for shade in shades_raw['shadeData']: - name = self._decode_name(shade['name']) - shade['name'] = name - self.shades[shade['id']] = shade + for shade in shades_raw["shadeData"]: + name = self._decode_name(shade["name"]) + shade["name"] = name + self.shades[shade["id"]] = shade return True def update_battery_level(self, shade_id): """ Update battery level. Possibly a redundant function. """ - msg = '{}shades/{}?updateBatteryLevel=true' + msg = "{}shades/{}?updateBatteryLevel=true" r = requests.get(msg.format(self.address, shade_id)) print(r.json()) @@ -74,17 +74,17 @@ def update_shade(self, shade_id): update individual shades. """ if shade_id not in self.shades: - print('Shade unknown - run find_all_shades()') + print("Shade unknown - run find_all_shades()") return False - msg = '{}shades/{}?refresh=true' + msg = "{}shades/{}?refresh=true" r = requests.get(msg.format(self.address, shade_id)) shade = r.json() - assert('shade' in shade) - shade = shade['shade'] - name = self._decode_name(shade['name']) - shade['name'] = name - self.shades[shade['id']] = shade + assert "shade" in shade + shade = shade["shade"] + name = self._decode_name(shade["name"]) + shade["name"] = name + self.shades[shade["id"]] = shade def move_shade(self, shade_id, raw_pos=None, percent_pos=None): """ @@ -100,38 +100,34 @@ def move_shade(self, shade_id, raw_pos=None, percent_pos=None): if percent_pos is not None: raw_pos = int(percent_pos / 100.0 * 2**16) - 1 - assert(0 < raw_pos < 2**16) - payload = { - 'shade': { - 'positions': {'posKind1': 1, 'position1': raw_pos} - } - } - msg = '{}shades/{}'.format(self.address, shade_id) + assert 0 < raw_pos < 2**16 + payload = {"shade": {"positions": {"posKind1": 1, "position1": raw_pos}}} + msg = "{}shades/{}".format(self.address, shade_id) requests.put(msg, json=payload) return True def print_current_shade_status(self, shade_id): shade = self.shades.get(shade_id) - room_name = self.rooms[shade['roomId']] + room_name = self.rooms[shade["roomId"]] if shade is None: - print('Shade unknown') - elif 'positions' not in shade: - msg = 'Shade {} in room {} is not currently witin range' - print(msg.format(shade['name'], room_name)) + print("Shade unknown") + elif "positions" not in shade: + msg = "Shade {} in room {} is not currently witin range" + print(msg.format(shade["name"], room_name)) else: - msg = 'Shade {} in room {} is {:.0f}% open. Battery is {}' + msg = "Shade {} in room {} is {:.0f}% open. Battery is {}" print( msg.format( - shade['name'], + shade["name"], room_name, - 100.0 * shade['positions']['position1'] / 2**16, - shade['batteryStrength'] + 100.0 * shade["positions"]["position1"] / 2**16, + shade["batteryStrength"], ) ) -if __name__ == '__main__': - pv = PowerView('192.168.1.76') +if __name__ == "__main__": + pv = PowerView("192.168.1.76") pv.find_all_shades() pv.move_shade(32852, percent_pos=85) diff --git a/PyExpLabSys/drivers/microchip_tech_mcp3428.py b/PyExpLabSys/drivers/microchip_tech_mcp3428.py index 0357a29b..39431ad0 100644 --- a/PyExpLabSys/drivers/microchip_tech_mcp3428.py +++ b/PyExpLabSys/drivers/microchip_tech_mcp3428.py @@ -8,12 +8,12 @@ class I2C: - """ File based i2c. + """File based i2c. Code adapted from: https://www.raspberrypi.org/forums/viewtopic.php?t=134997""" def __init__(self, device, bus): - self.file_read = io.open("/dev/i2c-"+str(bus), "rb", buffering=0) - self.file_write = io.open("/dev/i2c-"+str(bus), "wb", buffering=0) + self.file_read = io.open("/dev/i2c-" + str(bus), "rb", buffering=0) + self.file_write = io.open("/dev/i2c-" + str(bus), "wb", buffering=0) i2c_slave = 0x0703 # set device address @@ -21,22 +21,22 @@ def __init__(self, device, bus): fcntl.ioctl(self.file_write, i2c_slave, device) def write(self, values): - """ Write a value to i2c port """ + """Write a value to i2c port""" self.file_write.write(bytearray(values)) def read(self, number_of_bytes): - """ Read value from i2c port""" + """Read value from i2c port""" value_bytes = self.file_read.read(number_of_bytes) return list(value_bytes) def close(self): - """ Close the device """ + """Close the device""" self.file_write.close() self.file_read.close() class MCP3428(object): - """ Class for reading voltage from MCP3428 + """Class for reading voltage from MCP3428 For some reason this chip works only partly with smbus, hence the use of file based i2c. """ @@ -49,14 +49,13 @@ def __init__(self, address_index=0, voltage_ref=2.048): def __del__(self): self.bus.close() - def read_sample(self, channel: int = 1, gain: int = 1, - resolution: int = 12) -> float: - """ Read a single sample """ + def read_sample(self, channel: int = 1, gain: int = 1, resolution: int = 12) -> float: + """Read a single sample""" command_byte = ( - self.resolution(resolution) | - 0x00 | # One shot measuremet, use 0x10 for continous mode - self.gain(gain) | - self.channel(channel) + self.resolution(resolution) + | 0x00 + | self.gain(gain) # One shot measuremet, use 0x10 for continous mode + | self.channel(channel) ) command_byte = command_byte | 0x80 # start conversion self.bus.write([command_byte]) @@ -73,15 +72,15 @@ def read_sample(self, channel: int = 1, gain: int = 1, # print('Execution time: {:.2f}ms'.format(meas_time * 1000)) raw_value = data[0] * 256 + data[1] - if raw_value > (2**(resolution - 1) - 1): + if raw_value > (2 ** (resolution - 1) - 1): raw_value = raw_value - 2**resolution # print('Raw sensor value: {}'.format(raw_value)) - bit_size = self.voltage_ref / (2**(resolution - 1) * gain) + bit_size = self.voltage_ref / (2 ** (resolution - 1) * gain) voltage = raw_value * bit_size return voltage def gain(self, gain: int = 1) -> int: - """ Return the command code to set gain """ + """Return the command code to set gain""" gain_val = 0x00 if gain == 1: gain_val = 0x00 @@ -94,7 +93,7 @@ def gain(self, gain: int = 1) -> int: return gain_val def resolution(self, resolution: int = 12) -> int: - """ Return the command code to set resolution """ + """Return the command code to set resolution""" resolution_val = 0x00 if resolution == 12: resolution_val = 0x00 @@ -105,7 +104,7 @@ def resolution(self, resolution: int = 12) -> int: return resolution_val def channel(self, channel: int = 1) -> int: - """ Return the command code to set channel """ + """Return the command code to set channel""" channel_val = 0x00 if channel == 1: channel_val = 0x00 @@ -119,7 +118,7 @@ def channel(self, channel: int = 1) -> int: return channel_val -if __name__ == '__main__': +if __name__ == "__main__": MCP = MCP3428(address_index=4) print(MCP.read_sample(channel=1, gain=1, resolution=16) * (3.3 + 2.2) / 2.2) print(MCP.read_sample(channel=2, gain=1, resolution=16) * (3.3 + 2.2) / 2.2) diff --git a/PyExpLabSys/drivers/microchip_tech_mcp9808.py b/PyExpLabSys/drivers/microchip_tech_mcp9808.py index 2e4a9f17..d63af37a 100644 --- a/PyExpLabSys/drivers/microchip_tech_mcp9808.py +++ b/PyExpLabSys/drivers/microchip_tech_mcp9808.py @@ -3,14 +3,14 @@ class MCP9808(object): - """ Class for reading temperature from MCP9808 """ + """Class for reading temperature from MCP9808""" def __init__(self, i2cbus=1): self.bus = smbus.SMBus(i2cbus) self.device_address = 0x18 def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" self.bus.write_i2c_block_data(self.device_address, 0x01, [0x00, 0x00]) # Set range @@ -18,7 +18,7 @@ def read_values(self): time.sleep(1) data = self.bus.read_i2c_block_data(self.device_address, 0x05, 2) - temp_temp = ((data[0] & 0x1f) * 256) + data[1] + temp_temp = ((data[0] & 0x1F) * 256) + data[1] temp = temp_temp * 0.0625 # Scale return temp @@ -29,11 +29,11 @@ def read_resolution(self): def read_manufacturer_id(self): data = self.bus.read_i2c_block_data(self.device_address, 0x06, 2) man_id = data[1] - print('ID is: {}'.format(hex(man_id))) + print("ID is: {}".format(hex(man_id))) return man_id -if __name__ == '__main__': +if __name__ == "__main__": mcp9808 = MCP9808() mcp9808.read_manufacturer_id() mcp9808.read_resolution() diff --git a/PyExpLabSys/drivers/mks_925_pirani.py b/PyExpLabSys/drivers/mks_925_pirani.py index 63fb45c3..79ab0fdc 100644 --- a/PyExpLabSys/drivers/mks_925_pirani.py +++ b/PyExpLabSys/drivers/mks_925_pirani.py @@ -4,29 +4,32 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class Mks925(object): - """ Driver for MKS 925 micro pirani """ + """Driver for MKS 925 micro pirani""" + def __init__(self, port): self.ser = serial.Serial(port, 9600, timeout=2) time.sleep(0.1) def comm(self, command): - """ Implement communication protocol """ - prestring = b'@254' - endstring = b';FF' - self.ser.write(prestring + command.encode('ascii') + endstring) + """Implement communication protocol""" + prestring = b"@254" + endstring = b";FF" + self.ser.write(prestring + command.encode("ascii") + endstring) time.sleep(0.3) return_string = self.ser.read(self.ser.inWaiting()).decode() return return_string def read_pressure(self): - """ Read the pressure from the device """ - command = 'PR1?' + """Read the pressure from the device""" + command = "PR1?" error = 1 while (error > 0) and (error < 10): signal = self.comm(command) @@ -40,27 +43,28 @@ def read_pressure(self): return value def set_comm_speed(self, speed): - """ Change the baud rate """ - command = 'BR!' + str(speed) + """Change the baud rate""" + command = "BR!" + str(speed) signal = self.comm(command) return signal - def change_unit(self, unit): #STRING: TORR, PASCAL, MBAR - """ Change the unit of the return value """ - command = 'U!' + unit + def change_unit(self, unit): # STRING: TORR, PASCAL, MBAR + """Change the unit of the return value""" + command = "U!" + unit signal = self.comm(command) return signal def read_serial(self): - """ Read the serial number of the device """ - command = 'SN?' + """Read the serial number of the device""" + command = "SN?" signal = self.comm(command) signal = signal[7:-3] return signal -if __name__ == '__main__': - MKS = Mks925('/dev/ttyUSB1') - #print MKS.set_comm_speed(9600) - print(MKS.change_unit('MBAR')) + +if __name__ == "__main__": + MKS = Mks925("/dev/ttyUSB1") + # print MKS.set_comm_speed(9600) + print(MKS.change_unit("MBAR")) print("Pressure: " + str(MKS.read_pressure())) - print('Serial: ' + str(MKS.read_serial())) + print("Serial: " + str(MKS.read_serial())) diff --git a/PyExpLabSys/drivers/mks_937b.py b/PyExpLabSys/drivers/mks_937b.py index 99ebe0f6..29b9d7a2 100644 --- a/PyExpLabSys/drivers/mks_937b.py +++ b/PyExpLabSys/drivers/mks_937b.py @@ -4,71 +4,75 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class Mks937b(object): - """ Driver for MKS 937B Gauge Controller """ + """Driver for MKS 937B Gauge Controller""" + def __init__(self, port): self.ser = serial.Serial(port, 9600, timeout=2) time.sleep(0.1) def comm(self, command): - """ Implement communication protocol """ - prestring = b'@254' - endstring = b';FF' - self.ser.write(prestring + command.encode('ascii') + endstring) + """Implement communication protocol""" + prestring = b"@254" + endstring = b";FF" + self.ser.write(prestring + command.encode("ascii") + endstring) time.sleep(0.3) return_string = self.ser.read(self.ser.inWaiting()).decode() - success = return_string.find('ACK') + success = return_string.find("ACK") if success > 0: - return_string = return_string[success+3:-3] + return_string = return_string[success + 3 : -3] return return_string def read_pressure_gauge(self, gauge_number): - """ Read a specific pressure gauge """ - pressure_string = self.comm('PR' + str(gauge_number) + '?') - if pressure_string.find('LO') > -1: - pressure_string = '0' - if pressure_string.find('>') > -1: - pressure_string = '-1' - if pressure_string.find('OFF') > -1: - pressure_string = '-1' - if pressure_string.find('WAIT') > -1: - pressure_string = '-1' + """Read a specific pressure gauge""" + pressure_string = self.comm("PR" + str(gauge_number) + "?") + if pressure_string.find("LO") > -1: + pressure_string = "0" + if pressure_string.find(">") > -1: + pressure_string = "-1" + if pressure_string.find("OFF") > -1: + pressure_string = "-1" + if pressure_string.find("WAIT") > -1: + pressure_string = "-1" pressure_value = float(pressure_string) return pressure_value def read_sensor_types(self): - """ Return a list of connected sensors """ - sensors = self.comm('MT?') + """Return a list of connected sensors""" + sensors = self.comm("MT?") return sensors def read_all_pressures(self): - """ Returns an overview of all sensors """ - return self.comm('PRZ?') + """Returns an overview of all sensors""" + return self.comm("PRZ?") def pressure_unit(self, unit=None): - """ Read or configure pressure unit + """Read or configure pressure unit Legal values: torr, mbar, pascal, micron""" if unit is not None: - self.comm('U!' + str(unit)) - unit = self.comm('U?') + self.comm("U!" + str(unit)) + unit = self.comm("U?") return unit -if __name__ == '__main__': - MKS = Mks937b('/dev/ttyUSB0') + +if __name__ == "__main__": + MKS = Mks937b("/dev/ttyUSB0") print(MKS.read_pressure_gauge(1)) print(MKS.read_pressure_gauge(3)) print(MKS.read_pressure_gauge(5)) print(MKS.read_all_pressures()) print(MKS.read_sensor_types()) - print(MKS.pressure_unit('mbar')) - #print(MKS.comm('PR1?')) - #print(MKS.comm('PR2?')) - #print MKS.set_comm_speed(9600) - #print(MKS.change_unit('MBAR')) - #print("Pressure: " + str(MKS.read_pressure())) - #print('Serial: ' + str(MKS.read_serial())) + print(MKS.pressure_unit("mbar")) + # print(MKS.comm('PR1?')) + # print(MKS.comm('PR2?')) + # print MKS.set_comm_speed(9600) + # print(MKS.change_unit('MBAR')) + # print("Pressure: " + str(MKS.read_pressure())) + # print('Serial: ' + str(MKS.read_serial())) diff --git a/PyExpLabSys/drivers/mks_g_series.py b/PyExpLabSys/drivers/mks_g_series.py index 4df2a187..b5ad5a9b 100644 --- a/PyExpLabSys/drivers/mks_g_series.py +++ b/PyExpLabSys/drivers/mks_g_series.py @@ -1,17 +1,19 @@ - """ Driver for MKS g-series flow controller """ from __future__ import print_function import time import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) -class MksGSeries(): - """ Driver for G-series flow controllers from MKS """ - def __init__(self, port='/dev/ttyUSB0'): + +class MksGSeries: + """Driver for G-series flow controllers from MKS""" + + def __init__(self, port="/dev/ttyUSB0"): # TODO: Auto-check all possible baud-rates self.ser = serial.Serial(port, 9600) self.ser.parity = serial.PARITY_NONE @@ -19,9 +21,9 @@ def __init__(self, port='/dev/ttyUSB0'): self.ser.stopbits = serial.STOPBITS_ONE def checksum(self, command, reply=False): - """ Calculate checksum of command """ + """Calculate checksum of command""" if not reply: - com_string = '@' + command + com_string = "@" + command else: com_string = command total = 0 @@ -30,91 +32,91 @@ def checksum(self, command, reply=False): return (hex(total)[-2:]).upper() def comm(self, command, addr): - """ Implements communication protocol """ - com_string = str(addr).zfill(3) + command + ';' + """Implements communication protocol""" + com_string = str(addr).zfill(3) + command + ";" checksum = self.checksum(com_string) - com_string = '@@@@' + com_string + checksum - com_string = com_string.encode('ascii') + com_string = "@@@@" + com_string + checksum + com_string = com_string.encode("ascii") self.ser.write(com_string) time.sleep(0.1) reply = self.ser.read(self.ser.inWaiting()) try: - reply = reply.decode('ascii') + reply = reply.decode("ascii") except UnicodeDecodeError: - reply = reply.decode('ascii', 'ignore') - reply = reply.strip('\x00') - reply = '@' + reply + reply = reply.decode("ascii", "ignore") + reply = reply.strip("\x00") + reply = "@" + reply if len(reply) == 0: - LOGGER.warning('No such device') + LOGGER.warning("No such device") else: if reply[-3:] == self.checksum(reply[1:-3], reply=True): - reply = reply[6:-3] # Cut away master address and checksum + reply = reply[6:-3] # Cut away master address and checksum else: - LOGGER.error('Checksum error in reply') - reply = '' - if reply[1:4] == 'ACK': + LOGGER.error("Checksum error in reply") + reply = "" + if reply[1:4] == "ACK": reply = reply[4:-3] else: - LOGGER.warning('Error in command') + LOGGER.warning("Error in command") return reply def read_full_scale_range(self, addr): - """ Read back the current full scale range from the instrument """ - command = 'U?' + """Read back the current full scale range from the instrument""" + command = "U?" unit = self.comm(command, addr) - command = 'FS?' + command = "FS?" value = self.comm(command, addr) return value + unit def read_device_address(self, address=254): - """ Read the device address """ - command = 'CA?' + """Read the device address""" + command = "CA?" return self.comm(command, address) def set_device_address(self, old_addr, new_addr): - """ Set the device address """ + """Set the device address""" if (new_addr > 0) and (new_addr < 254): addr_string = str(new_addr).zfill(3) - command = 'CA!' + addr_string + command = "CA!" + addr_string self.comm(command, old_addr) def read_current_gas_type(self, addr): - """ Read the current default gas type """ - command = 'PG?' + """Read the current default gas type""" + command = "PG?" reply = self.comm(command, addr) return reply def read_run_hours(self, addr): - """ Return number of running hours of mfc """ - command = 'RH?' + """Return number of running hours of mfc""" + command = "RH?" return self.comm(command, addr) def read_setpoint(self, addr): - """ Read current setpoint """ - command = 'SX?' + """Read current setpoint""" + command = "SX?" value = float(self.comm(command, addr)) return value def set_flow(self, value, addr=254): - """ Set the flow setpoint """ - command = 'SX!' + str(round(value, 1)) + """Set the flow setpoint""" + command = "SX!" + str(round(value, 1)) self.comm(command, addr) return True def purge(self, t=1, addr=254): - """ purge for t seconds, default is 1 second """ - command1 = 'VO!PURGE' - command2 = 'VO!NORMAL' + """purge for t seconds, default is 1 second""" + command1 = "VO!PURGE" + command2 = "VO!NORMAL" self.comm(command1, addr) - print('PURGING') + print("PURGING") time.sleep(abs(t)) self.comm(command2, addr) - print('DONE PURGING') + print("DONE PURGING") def read_flow(self, addr=254): - """ Read the flow """ - command = 'FX?' + """Read the flow""" + command = "FX?" error = 1 while (error > 0) and (error < 50): try: @@ -126,12 +128,13 @@ def read_flow(self, addr=254): return flow def read_serial_number(self, addr=254): - """ Read the serial number of the device """ - command = 'SN?' + """Read the serial number of the device""" + command = "SN?" return self.comm(command, addr) -if __name__ == '__main__': + +if __name__ == "__main__": MKS = MksGSeries() print(MKS.read_serial_number(1)) print(MKS.read_full_scale_range(1)) -#print(MKS.set_device_address(254,005)) +# print(MKS.set_device_address(254,005)) diff --git a/PyExpLabSys/drivers/mks_pi_pc.py b/PyExpLabSys/drivers/mks_pi_pc.py index 9591dae5..d339535a 100644 --- a/PyExpLabSys/drivers/mks_pi_pc.py +++ b/PyExpLabSys/drivers/mks_pi_pc.py @@ -1,36 +1,45 @@ import serial import time -class Mks_Pi_Pc(): - def __init__(self, port='/dev/ttyUSB2'): +class Mks_Pi_Pc: + def __init__(self, port="/dev/ttyUSB2"): self.f = serial.Serial(port, 38400, timeout=1) self.macid = 55 - self.fullrange = 2666 # mbar. In torr the value is 2000 + self.fullrange = 2666 # mbar. In torr the value is 2000 def checksum(self, command): s = 0 - for i in range(0,len(command)): - s = s+ord(command[i]) - #print ord(command[i]) - return(s%256) + for i in range(0, len(command)): + s = s + ord(command[i]) + # print ord(command[i]) + return s % 256 - def comm(self, classid, instanceid, attributeid, write=False, macid=None, length=3, data=''): + def comm( + self, + classid, + instanceid, + attributeid, + write=False, + macid=None, + length=3, + data="", + ): if macid is None: macid = self.macid if write is True: - command = int('81',16) + command = int("81", 16) else: - command = int('80',16) + command = int("80", 16) - comm = chr(2) #STX + comm = chr(2) # STX comm += chr(command) - comm += chr(length) # This could properly be found algorithmically + comm += chr(length) # This could properly be found algorithmically comm += chr(classid) comm += chr(instanceid) comm += chr(attributeid) comm += data - comm += chr(0) #Required by protocol + comm += chr(0) # Required by protocol checksum = self.checksum(comm) self.f.write(chr(macid) + comm + chr(checksum)) time.sleep(0.1) @@ -39,34 +48,36 @@ def comm(self, classid, instanceid, attributeid, write=False, macid=None, length if not write: if ord(reply_checksum) == self.checksum(reply[1:-1]): length = ord(reply[4]) - reply = reply[8:8+length-3] #According to protocol + reply = reply[8 : 8 + length - 3] # According to protocol else: - print('Error') + print("Error") else: if not ord(reply[0]) == 6: - print('Error') - return(reply) + print("Error") + return reply def convert_value_from_mks(self, value): msb = value[1] lsb = value[0] hex_value = hex(ord(msb))[2:].zfill(2) + hex(ord(lsb))[2:].zfill(2) - full_scale = int('C000',16) - int('4000',16) - calibrated_value = (1.0 * self.fullrange * (int(hex_value,16) - int('4000',16))) / full_scale - return(calibrated_value) + full_scale = int("C000", 16) - int("4000", 16) + calibrated_value = ( + 1.0 * self.fullrange * (int(hex_value, 16) - int("4000", 16)) + ) / full_scale + return calibrated_value def set_setpoint(self, setpoint): - full_scale = int('C000',16) - int('4000',16) - mks_setpoint = (full_scale * setpoint / self.fullrange) + int('4000',16) + full_scale = int("C000", 16) - int("4000", 16) + mks_setpoint = (full_scale * setpoint / self.fullrange) + int("4000", 16) hex_setpoint = hex(mks_setpoint)[2:] - assert(len(hex_setpoint)==4) + assert len(hex_setpoint) == 4 msb = chr(int(hex_setpoint[0:2], 16)) lsb = chr(int(hex_setpoint[2:4], 16)) - self.comm(int('69',16),1,int('a4',16), length=5, write=True, data=lsb+msb) + self.comm(int("69", 16), 1, int("a4", 16), length=5, write=True, data=lsb + msb) def read_mac_id(self): macid = self.comm(3, 1, 1) - return(ord(macid)) + return ord(macid) """ def query_full_range(self): @@ -84,17 +95,17 @@ def query_full_range(self): """ def read_setpoint(self): - setpoint = self.comm(int('6a',16), 1, int('a6',16)) - return(self.convert_value_from_mks(setpoint)) + setpoint = self.comm(int("6a", 16), 1, int("a6", 16)) + return self.convert_value_from_mks(setpoint) def read_pressure(self): - pressure = self.comm(int('6a',16), 1, int('a9',16)) - return(self.convert_value_from_mks(pressure)) + pressure = self.comm(int("6a", 16), 1, int("a9", 16)) + return self.convert_value_from_mks(pressure) -if __name__ == '__main__': +if __name__ == "__main__": mks = Mks_Pi_Pc() - #print mks.read_mac_id() + # print mks.read_mac_id() print("Setpoint: " + str(mks.read_setpoint())) print("Pressure: " + str(mks.read_pressure())) mks.set_setpoint(500) diff --git a/PyExpLabSys/drivers/nxp_pcf8593.py b/PyExpLabSys/drivers/nxp_pcf8593.py index f8d4e3b2..51d857be 100644 --- a/PyExpLabSys/drivers/nxp_pcf8593.py +++ b/PyExpLabSys/drivers/nxp_pcf8593.py @@ -20,7 +20,7 @@ def set_status(self): # print(alarm_status, bin(alarm_status)[2:].zfill(8)) wanted_alarm = 0b00000010 self.bus.write_byte_data(self.device_address, 0x08, enable_counter) - + def read_counter(self): data = self.bus.read_i2c_block_data(self.device_address, 0x01, 3) # print(data) @@ -41,11 +41,11 @@ def read_counter(self): high_digit = int(hex(data[2])[2:]) except ValueError: high_digit = 0 - count = high_digit * 10**4 + middle_digit * 10** 2 + low_digit + count = high_digit * 10**4 + middle_digit * 10**2 + low_digit return count -if __name__ == '__main__': +if __name__ == "__main__": pcf = NXP_PCF8593() time.sleep(1) @@ -55,10 +55,10 @@ def read_counter(self): for i in range(0, 10): print() first_counter = pcf.read_counter() - print('First count is: {}'.format(first_counter)) + print("First count is: {}".format(first_counter)) time.sleep(wait_time) second_counter = pcf.read_counter() - print('Second count is: {}'.format(second_counter)) + print("Second count is: {}".format(second_counter)) freq = (second_counter - first_counter) / wait_time - print('Frequency is: {}'.format(freq)) - print('Flow if this is an FT-210: {:.3f}mL/min'.format(60 * freq / 22)) + print("Frequency is: {}".format(freq)) + print("Flow if this is an FT-210: {:.3f}mL/min".format(60 * freq / 22)) diff --git a/PyExpLabSys/drivers/ocs_3l_oxygen_sensor.py b/PyExpLabSys/drivers/ocs_3l_oxygen_sensor.py index 6b3d90ae..a123bd6d 100644 --- a/PyExpLabSys/drivers/ocs_3l_oxygen_sensor.py +++ b/PyExpLabSys/drivers/ocs_3l_oxygen_sensor.py @@ -108,15 +108,16 @@ def _bitbanged_read(self): # These bit-patterns are fixed, errors here would indicate a # read error in the bit-banged read. sanity_check = ( - parsed_bytes[8:0:-1] == [0, 0, 0, 1, 0, 1, 1, 0] and - parsed_bytes[18:10:-1] == [0, 0, 0, 0, 1, 0, 0, 1] and - parsed_bytes[28:20:-1] == [0, 0, 0, 0, 0, 0, 0, 1] and - parsed_bytes[58:50:-1] == [0, 0, 0, 0, 0, 0, 0, 0] and + parsed_bytes[8:0:-1] == [0, 0, 0, 1, 0, 1, 1, 0] + and parsed_bytes[18:10:-1] == [0, 0, 0, 0, 1, 0, 0, 1] + and parsed_bytes[28:20:-1] == [0, 0, 0, 0, 0, 0, 0, 1] + and parsed_bytes[58:50:-1] == [0, 0, 0, 0, 0, 0, 0, 0] + and # According to datsheet, this should be 0x63, but turns out # to be always 0x00 - parsed_bytes[68:60:-1] == [0, 0, 0, 0, 0, 0, 0, 0] and - parsed_bytes[98:90:-1] == [0, 0, 0, 0, 0, 0, 0, 0] and - parsed_bytes[108:100:-1] == [0, 0, 0, 0, 0, 0, 0, 0] + parsed_bytes[68:60:-1] == [0, 0, 0, 0, 0, 0, 0, 0] + and parsed_bytes[98:90:-1] == [0, 0, 0, 0, 0, 0, 0, 0] + and parsed_bytes[108:100:-1] == [0, 0, 0, 0, 0, 0, 0, 0] ) if not sanity_check: parsed_bytes = None @@ -141,11 +142,13 @@ def _read_temperature(self, parsed_bytes): def _check_full_checksum(self, parsed_bytes): expected_checksum = 0 for val in [ - 0x16, 0x09, 0x01, - self._checksum(parsed_bytes[38:30:-1]), - self._checksum(parsed_bytes[48:40:-1]), - self._checksum(parsed_bytes[78:70:-1]), - self._checksum(parsed_bytes[88:80:-1]) + 0x16, + 0x09, + 0x01, + self._checksum(parsed_bytes[38:30:-1]), + self._checksum(parsed_bytes[48:40:-1]), + self._checksum(parsed_bytes[78:70:-1]), + self._checksum(parsed_bytes[88:80:-1]), ]: expected_checksum += val expected_checksum = expected_checksum % 256 @@ -179,13 +182,13 @@ def read_oxygen_and_temperature(self): # msg = 'Oxygen concentration: {}%, Temperature: {}C. Checksum: {}' # print(msg.format(concentration, temperature, checksum)) - return(concentration, temperature) + return (concentration, temperature) -if __name__ == '__main__': +if __name__ == "__main__": # oxygen_sensor = OCS3L(port='/dev/serial1') oxygen_sensor = OCS3L(gpio_pin=26) while True: readout = oxygen_sensor.read_oxygen_and_temperature() if readout is not None: - print('Oxygen: {}%. Temperature: {}C'.format(readout[0], readout[1])) + print("Oxygen: {}%. Temperature: {}C".format(readout[0], readout[1])) diff --git a/PyExpLabSys/drivers/omega_D6400.py b/PyExpLabSys/drivers/omega_D6400.py index 61f9163c..d65fa317 100644 --- a/PyExpLabSys/drivers/omega_D6400.py +++ b/PyExpLabSys/drivers/omega_D6400.py @@ -4,15 +4,18 @@ import logging import minimalmodbus from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) + class OmegaD6400(object): - """ Driver for Omega D6400 daq card """ - def __init__(self, address=1, port='/dev/ttyUSB0'): + """Driver for Omega D6400 daq card""" + + def __init__(self, address=1, port="/dev/ttyUSB0"): self.instrument = minimalmodbus.Instrument(port, address) self.instrument.serial.baudrate = 9600 self.instrument.serial.timeout = 1.0 # Default setting leads to comm-errors @@ -20,19 +23,22 @@ def __init__(self, address=1, port='/dev/ttyUSB0'): self.ranges = [0] * 8 for i in range(1, 8): self.ranges[i] = {} - self.ranges[i]['action'] = 'disable' - self.ranges[i]['fullrange'] = '0' - self.ranges[1]['action'] = 'voltage' - self.ranges[1]['fullrange'] = '10' + self.ranges[i]["action"] = "disable" + self.ranges[i]["fullrange"] = "0" + self.ranges[1]["action"] = "voltage" + self.ranges[1]["fullrange"] = "10" for i in range(1, 8): print(i) - self.update_range_and_function(i, fullrange=self.ranges[i]['fullrange'], - action=self.ranges[i]['action']) - print('!') + self.update_range_and_function( + i, + fullrange=self.ranges[i]["fullrange"], + action=self.ranges[i]["action"], + ) + print("!") def comm(self, command, value=None): - """ Communicates with the device """ + """Communicates with the device""" reply = None error = True @@ -44,87 +50,86 @@ def comm(self, command, value=None): self.instrument.write_register(command, value) error = False except ValueError: - LOGGER.warning('D6400 driver: Value Error') + LOGGER.warning("D6400 driver: Value Error") self.instrument.serial.read(self.instrument.serial.inWaiting()) time.sleep(0.1) error = True except IOError: - LOGGER.warning('D6400 driver: IOError') + LOGGER.warning("D6400 driver: IOError") self.instrument.serial.read(self.instrument.serial.inWaiting()) error = True time.sleep(0.1) return reply def read_value(self, channel): - """ Read a measurement value from a channel """ + """Read a measurement value from a channel""" value = None reply = self.comm(47 + channel) - if self.ranges[channel]['action'] == 'voltage': - num_value = reply - 2 ** 15 - scale = 1.0 * 2 ** 15 / float(self.ranges[channel]['fullrange']) + if self.ranges[channel]["action"] == "voltage": + num_value = reply - 2**15 + scale = 1.0 * 2**15 / float(self.ranges[channel]["fullrange"]) value = num_value / scale - if self.ranges[channel]['action'] == 'tc': - scale = 1.0 * 2 ** 16 / 1400 - value = (reply/scale) - 150 + if self.ranges[channel]["action"] == "tc": + scale = 1.0 * 2**16 / 1400 + value = (reply / scale) - 150 return value def read_address(self): - """ Read the RS485 address of the device """ + """Read the RS485 address of the device""" old_address = self.comm(0) return old_address - def write_enable(self): - """ Enable changes to setup values """ + """Enable changes to setup values""" self.comm(240, 2) time.sleep(0.8) return True def range_codes(self, fullrange=0, action=None): - """ Returns the code corresponding to a given range - """ + """Returns the code corresponding to a given range""" codes = {} - codes['tc'] = {} - codes['tc']['J'] = 21 - codes['tc']['K'] = 34 - codes['tc']['T'] = 23 - codes['tc']['E'] = 24 - codes['tc']['R'] = 25 - codes['tc']['S'] = 26 - codes['tc']['B'] = 27 - codes['tc']['C'] = 28 - codes['voltage'] = {} - codes['voltage']['10'] = 1 - codes['voltage']['5'] = 2 - codes['voltage']['1'] = 3 - codes['voltage']['0.1'] = 4 - codes['voltage']['0.05'] = 5 - codes['voltage']['0.025'] = 6 - codes['disable'] = 0 - codes['current'] = 3 - - if action in ('tc', 'voltage'): + codes["tc"] = {} + codes["tc"]["J"] = 21 + codes["tc"]["K"] = 34 + codes["tc"]["T"] = 23 + codes["tc"]["E"] = 24 + codes["tc"]["R"] = 25 + codes["tc"]["S"] = 26 + codes["tc"]["B"] = 27 + codes["tc"]["C"] = 28 + codes["voltage"] = {} + codes["voltage"]["10"] = 1 + codes["voltage"]["5"] = 2 + codes["voltage"]["1"] = 3 + codes["voltage"]["0.1"] = 4 + codes["voltage"]["0.05"] = 5 + codes["voltage"]["0.025"] = 6 + codes["disable"] = 0 + codes["current"] = 3 + + if action in ("tc", "voltage"): code = codes[action][fullrange] - if action in ('disable', 'current'): + if action in ("disable", "current"): code = codes[action] return code def update_range_and_function(self, channel, fullrange=None, action=None): - """ Set the range and measurement type for a channel """ + """Set the range and measurement type for a channel""" if not action is None: self.write_enable() code = self.range_codes(fullrange, action) self.comm(95 + channel, code) - print('##') + print("##") time.sleep(0.1) - self.ranges[channel]['action'] = action - self.ranges[channel]['fullrange'] = fullrange + self.ranges[channel]["action"] = action + self.ranges[channel]["fullrange"] = fullrange return self.comm(95 + channel) -if __name__ == '__main__': - OMEGA = OmegaD6400(1, port='/dev/ttyUSB0') - OMEGA.update_range_and_function(1, action='voltage', fullrange='10') - OMEGA.update_range_and_function(2, action='voltage', fullrange='10') - print('***') + +if __name__ == "__main__": + OMEGA = OmegaD6400(1, port="/dev/ttyUSB0") + OMEGA.update_range_and_function(1, action="voltage", fullrange="10") + OMEGA.update_range_and_function(2, action="voltage", fullrange="10") + print("***") print(OMEGA.read_value(1)) print(OMEGA.read_value(2)) diff --git a/PyExpLabSys/drivers/omega_cn7800.py b/PyExpLabSys/drivers/omega_cn7800.py index f0fb7ac1..d369eb8b 100644 --- a/PyExpLabSys/drivers/omega_cn7800.py +++ b/PyExpLabSys/drivers/omega_cn7800.py @@ -9,9 +9,10 @@ LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller -#LOGGER.addHandler(logging.NullHandler()) +# LOGGER.addHandler(logging.NullHandler()) LOGGER.addHandler(logging.StreamHandler()) + class CN7800(object): """Driver for the omega CN7800""" @@ -22,32 +23,32 @@ def __init__(self, port): self.comm.serial.timeout = 0.5 self.temperature = -999 - def read_temperature(self): - """ Read the temperature from the device """ + """Read the temperature from the device""" self.temperature = self.comm.read_register(0x1000, 1) return self.temperature - + def read_setpoint(self): - """ Read the temperature setpoint """ - setpoint = self.comm.read_register(0x1001,1) + """Read the temperature setpoint""" + setpoint = self.comm.read_register(0x1001, 1) return setpoint def write_setpoint(self, new_setpoint): - """ Write a new setpoint to the device """ - self.comm.write_register(0x1001,new_setpoint,1) + """Write a new setpoint to the device""" + self.comm.write_register(0x1001, new_setpoint, 1) -def main(): +def main(): port = "usb-FTDI_USB-RS485_Cable_FT1F9WC2-if00-port0" -# port = "/dev/ttyUSB0" + # port = "/dev/ttyUSB0" omega = CN7800(port) print("Temperature is:", omega.read_temperature()) print("Set point is:", omega.read_setpoint()) print("Temperature type is:", type(omega.read_temperature())) print("Set point is:", omega.write_setpoint(float(30))) print("Set point is:", omega.read_setpoint()) + + if __name__ == "__main__": # Execute only if run as script main() - diff --git a/PyExpLabSys/drivers/omega_cni.py b/PyExpLabSys/drivers/omega_cni.py index da175659..9ff880c6 100644 --- a/PyExpLabSys/drivers/omega_cni.py +++ b/PyExpLabSys/drivers/omega_cni.py @@ -7,31 +7,37 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class ISeries(object): """Driver for the iSeries omega temperature controllers""" pre_string = chr(42) end_string = chr(13) - def __init__(self, port, baudrate=19200, comm_stnd='rs232'): + def __init__(self, port, baudrate=19200, comm_stnd="rs232"): """Initialize internal parameters :param port: A serial port designation as understood by `pySerial `_ """ - LOGGER.debug('Initialize driver') - self.serial = serial.Serial(port, baudrate, bytesize=serial.SEVENBITS, - parity=serial.PARITY_ODD, - stopbits=serial.STOPBITS_ONE, - timeout=2) + LOGGER.debug("Initialize driver") + self.serial = serial.Serial( + port, + baudrate, + bytesize=serial.SEVENBITS, + parity=serial.PARITY_ODD, + stopbits=serial.STOPBITS_ONE, + timeout=2, + ) self.comm_stnd = comm_stnd time.sleep(0.1) - LOGGER.info('Driver initialized') + LOGGER.info("Driver initialized") def command(self, command, response_length=None, address=None): """Run a command and return the result @@ -43,15 +49,14 @@ def command(self, command, response_length=None, address=None): response from the device. :type response_length: int """ - if address is None and self.comm_stnd == 'rs485': - address = '1' + if address is None and self.comm_stnd == "rs485": + address = "1" - LOGGER.debug('command called with {}, {}'.format(command, - response_length)) - if self.comm_stnd == 'rs485': - command = '0' + str(address) + command - comm_string = (self.pre_string + command + self.end_string).encode('ascii') - #print('Comm string: ' + comm_string) + LOGGER.debug("command called with {}, {}".format(command, response_length)) + if self.comm_stnd == "rs485": + command = "0" + str(address) + command + comm_string = (self.pre_string + command + self.end_string).encode("ascii") + # print('Comm string: ' + comm_string) self.serial.write(comm_string) @@ -70,46 +75,45 @@ def command(self, command, response_length=None, address=None): response2 = [] for el in response: response2.append(ord(el)) - #print('RES2: ' + str(response2)) + # print('RES2: ' + str(response2)) # Strip \r from responseRemove the echo response from the device - LOGGER.debug('comand return {}'.format(response[:-1])) - if response[0:len(command)] == command: - response = response[len(command):] + LOGGER.debug("comand return {}".format(response[:-1])) + if response[0 : len(command)] == command: + response = response[len(command) :] return response[:-1] def reset_device(self, address=None): """Reset the device""" - command = 'Z02' - print('Reseting device') + command = "Z02" + print("Reseting device") return self.command(command, address=address) def identify_device(self, address=None): """Return the identity of the device""" - command = 'R26' + command = "R26" return self.command(command, address=address) def read_temperature(self, address=None): """Return the temperature""" - LOGGER.debug('read_temperature called') - command = 'X01' + LOGGER.debug("read_temperature called") + command = "X01" error = 1 while (error > 0) and (error < 10): try: - response = float(self.command(command, - address=address)) + response = float(self.command(command, address=address)) error = 0 except ValueError: error = error + 1 - #print('AAA') + # print('AAA') response = None - LOGGER.debug('read_temperature return {}'.format(response)) + LOGGER.debug("read_temperature return {}".format(response)) return response def close(self): """Close the connection to the device""" - LOGGER.debug('Driver asked to close') + LOGGER.debug("Driver asked to close") self.serial.close() - LOGGER.info('Driver closed') + LOGGER.info("Driver closed") class CNi3244_C24(ISeries): @@ -123,10 +127,11 @@ def __init__(self, port): """ super(CNi3244_C24, self).__init__(port) -if __name__ == '__main__': + +if __name__ == "__main__": # This port name should be chages to a local port to do a local test - OMEGA = ISeries('/dev/ttyUSB0', 9600, comm_stnd='rs485') - #print(OMEGA.identify_device(1)) - #print(OMEGA.identify_device(2)) + OMEGA = ISeries("/dev/ttyUSB0", 9600, comm_stnd="rs485") + # print(OMEGA.identify_device(1)) + # print(OMEGA.identify_device(2)) print(OMEGA.read_temperature()) print(OMEGA.read_temperature(2)) diff --git a/PyExpLabSys/drivers/omegabus.py b/PyExpLabSys/drivers/omegabus.py index 597ff7bd..08e803b7 100644 --- a/PyExpLabSys/drivers/omegabus.py +++ b/PyExpLabSys/drivers/omegabus.py @@ -4,24 +4,27 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class OmegaBus(object): - """ Driver for OmegaBus devices """ - def __init__(self, device='/dev/ttyUSB0', model='D5251', baud=300): + """Driver for OmegaBus devices""" + + def __init__(self, device="/dev/ttyUSB0", model="D5251", baud=300): self.ser = serial.Serial(device, baud) self.setup = {} - self.setup['model'] = model - self.read_setup() # Read temperature unit, if relevant + self.setup["model"] = model + self.read_setup() # Read temperature unit, if relevant time.sleep(0.1) def comm(self, command): - """ Handles serial protocol """ + """Handles serial protocol""" command = command + "\r" - command = command.encode('ascii') + command = command.encode("ascii") self.ser.write(command) time.sleep(1) answer = self.ser.read(self.ser.inWaiting()) @@ -29,78 +32,83 @@ def comm(self, command): return answer def read_value(self, channel, convert_to_celcius=True): - """ Read the measurement value """ + """Read the measurement value""" value_string = self.comm("$" + str(channel) + "RD") # The value string is after the * - if '*' in value_string: - value_string = value_string.split('*', 1)[1] + if "*" in value_string: + value_string = value_string.split("*", 1)[1] value = float(value_string) - if convert_to_celcius and self.setup['model'] in ['D5311', 'D5321', 'D5331', 'D5431']: - if self.setup['temp_unit'] == 'F': - value = 5 * (value - 32)/9 + if convert_to_celcius and self.setup["model"] in [ + "D5311", + "D5321", + "D5331", + "D5431", + ]: + if self.setup["temp_unit"] == "F": + value = 5 * (value - 32) / 9 return value def read_max(self, channel): - """ The maximum read-out value """ + """The maximum read-out value""" temp_string = self.comm("$" + str(channel) + "RMX") if temp_string[1] == "*": temp_string = temp_string[3:] return float(temp_string) def read_min(self, channel): - """ The minimum read-out value """ + """The minimum read-out value""" temp_string = self.comm("$" + str(channel) + "RMN") if temp_string[1] == "*": temp_string = temp_string[2:] return float(temp_string) def read_setup(self): - """ Read Device setup information """ + """Read Device setup information""" rs_string = self.comm("$" + "1RS") - if '*' in rs_string: - rs_string = rs_string.split('*', 1)[1] + if "*" in rs_string: + rs_string = rs_string.split("*", 1)[1] byte1 = rs_string[0:2] byte2 = rs_string[2:4] byte3 = rs_string[4:6] - #byte4 = rs_string[6:8] + # byte4 = rs_string[6:8] setupstring = "" setupstring += "Base adress: " + chr(int(byte1, 16)) + "\n" bits_2 = (bin(int(byte2, 16))[2:]).zfill(8) - setupstring += "No linefeed\n" if bits_2[0] == '0' else "Linefeed\n" - if bits_2[2] == '0': #bits_2[1] will contain the parity if not none - setupstring += "Parity: None" + "\n" - setupstring += "Normal addressing\n" if bits_2[3] == '0' else "Extended addressing\n" - if bits_2[4:8] == '0010': - setupstring += "Baud rate: 9600" + "\n" + setupstring += "No linefeed\n" if bits_2[0] == "0" else "Linefeed\n" + if bits_2[2] == "0": # bits_2[1] will contain the parity if not none + setupstring += "Parity: None" + "\n" + setupstring += "Normal addressing\n" if bits_2[3] == "0" else "Extended addressing\n" + if bits_2[4:8] == "0010": + setupstring += "Baud rate: 9600" + "\n" bits_3 = (bin(int(byte3, 16))[2:]).zfill(8) - setupstring += "Channel 3 enabled\n" if bits_3[0] == '1' else "Channel 3 disabled\n" - setupstring += "Channel 2 enabled\n" if bits_3[1] == '1' else "Channel 2 disabled\n" - setupstring += "Channel 1 enabled\n" if bits_3[2] == '1' else "Channel 1 disabled\n" - if bits_3[3] == '1': + setupstring += "Channel 3 enabled\n" if bits_3[0] == "1" else "Channel 3 disabled\n" + setupstring += "Channel 2 enabled\n" if bits_3[1] == "1" else "Channel 2 disabled\n" + setupstring += "Channel 1 enabled\n" if bits_3[2] == "1" else "Channel 1 disabled\n" + if bits_3[3] == "1": setupstring += "No cold junction compensation\n" else: setupstring += "Cold junction compensation enabled\n" - setupstring += "Unit: Fahrenheit\n" if bits_3[4] == '1' else "Unit: Celsius\n" - if bits_3[4] == '1': - self.setup['temp_unit'] = 'F' + setupstring += "Unit: Fahrenheit\n" if bits_3[4] == "1" else "Unit: Celsius\n" + if bits_3[4] == "1": + self.setup["temp_unit"] = "F" else: - self.setup['temp_unit'] = 'C' - #print (bin(int(byte4,16))[2:]).zfill(8 + self.setup["temp_unit"] = "C" + # print (bin(int(byte4,16))[2:]).zfill(8 return setupstring if __name__ == "__main__": - OMEGA = OmegaBus(model='D5251') + OMEGA = OmegaBus(model="D5251") print(OMEGA.read_setup()) print(OMEGA.read_value(1)) print(OMEGA.read_value(2)) print(OMEGA.read_value(3)) print(OMEGA.read_value(4)) - #print(OMEGA.read_min(1)) - #print(OMEGA.read_max(1)) + # print(OMEGA.read_min(1)) + # print(OMEGA.read_max(1)) diff --git a/PyExpLabSys/drivers/omron_d6fph.py b/PyExpLabSys/drivers/omron_d6fph.py index 96b0b209..47df6965 100644 --- a/PyExpLabSys/drivers/omron_d6fph.py +++ b/PyExpLabSys/drivers/omron_d6fph.py @@ -3,25 +3,27 @@ import smbus import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class OmronD6fph(object): - """ Class for reading pressure and temperature from Omron D6F-PH series - Ranging not implemented for all models """ + """Class for reading pressure and temperature from Omron D6F-PH series + Ranging not implemented for all models""" def __init__(self): self.bus = smbus.SMBus(1) - self.device_address = 0x6c + self.device_address = 0x6C self.full_range = 1000.0 self.init_device() def init_device(self): - """ Sensor needs to be initiated after power up """ + """Sensor needs to be initiated after power up""" init_command = [0x0B, 0x00] self.bus.write_i2c_block_data(self.device_address, 0, init_command) def read_value(self, command): - """ Read a value from the sensor """ + """Read a value from the sensor""" mcu_mode_command = [0xD0, 0x40, 0x18, 0x06] self.bus.write_i2c_block_data(self.device_address, 0, mcu_mode_command) time.sleep(0.033) @@ -33,20 +35,20 @@ def read_value(self, command): return value def read_pressure(self): - """ Read the pressure value """ + """Read the pressure value""" value = self.read_value([0xD0, 0x51, 0x2C]) - #TODO: Implement range calculation for all sensor models - pressure = (value - 1024) * self.full_range / 60000 - self.full_range/2 + # TODO: Implement range calculation for all sensor models + pressure = (value - 1024) * self.full_range / 60000 - self.full_range / 2 return pressure def read_temperature(self): - """ Read the temperature value """ + """Read the temperature value""" value = self.read_value([0xD0, 0x61, 0x2C]) temperature = (value - 10214) / 37.39 return temperature -if __name__ == '__main__': +if __name__ == "__main__": OMRON = OmronD6fph() print(OMRON.read_pressure()) print(OMRON.read_temperature()) diff --git a/PyExpLabSys/drivers/pfeiffer.py b/PyExpLabSys/drivers/pfeiffer.py index 80d4695b..d5686796 100644 --- a/PyExpLabSys/drivers/pfeiffer.py +++ b/PyExpLabSys/drivers/pfeiffer.py @@ -10,26 +10,26 @@ # Code translations constants MEASUREMENT_STATUS = { - 0: 'Measurement data okay', - 1: 'Underrange', - 2: 'Overrange', - 3: 'Sensor error', - 4: 'Sensor off (IKR, PKR, IMR, PBR)', - 5: 'No sensor (output: 5,2.0000E-2 [mbar])', - 6: 'Identification error' + 0: "Measurement data okay", + 1: "Underrange", + 2: "Overrange", + 3: "Sensor error", + 4: "Sensor off (IKR, PKR, IMR, PBR)", + 5: "No sensor (output: 5,2.0000E-2 [mbar])", + 6: "Identification error", } GAUGE_IDS = { - 'TPR': 'Pirani Gauge or Pirani Capacitive gauge', - 'IKR9': 'Cold Cathode Gauge 10E-9 ', - 'IKR11': 'Cold Cathode Gauge 10E-11 ', - 'PKR': 'FullRange CC Gauge', - 'PBR': 'FullRange BA Gauge', - 'IMR': 'Pirani / High Pressure Gauge', - 'CMR': 'Linear gauge', - 'noSEn': 'no SEnsor', - 'noid': 'no identifier' + "TPR": "Pirani Gauge or Pirani Capacitive gauge", + "IKR9": "Cold Cathode Gauge 10E-9 ", + "IKR11": "Cold Cathode Gauge 10E-11 ", + "PKR": "FullRange CC Gauge", + "PBR": "FullRange BA Gauge", + "IMR": "Pirani / High Pressure Gauge", + "CMR": "Linear gauge", + "noSEn": "no SEnsor", + "noid": "no identifier", } -PRESSURE_UNITS = {0: 'mbar/bar', 1: 'Torr', 2: 'Pascal'} +PRESSURE_UNITS = {0: "mbar/bar", 1: "Torr", 2: "Pascal"} class TPG26x(object): @@ -62,7 +62,7 @@ class TPG26x(object): ACK = chr(6) # \x06 NAK = chr(21) # \x15 - def __init__(self, port='/dev/ttyUSB0', baudrate=9600): + def __init__(self, port="/dev/ttyUSB0", baudrate=9600): """Initialize internal variables and serial connection :param port: The COM port to open. See the documentation for @@ -99,11 +99,12 @@ def _send_command(self, command): self.serial.write(self._cr_lf(command)) response = self.serial.readline() if response == self._cr_lf(self.NAK): - message = 'Serial communication returned negative acknowledge' + message = "Serial communication returned negative acknowledge" raise IOError(message) elif response != self._cr_lf(self.ACK): - message = 'Serial communication returned unknown response:\n{}'\ - ''.format(repr(response)) + message = "Serial communication returned unknown response:\n{}" "".format( + repr(response) + ) raise IOError(message) def _get_data(self): @@ -119,9 +120,9 @@ def _get_data(self): def _clear_output_buffer(self): """Clear the output buffer""" time.sleep(0.1) - just_read = 'start value' - out = '' - while just_read != '': + just_read = "start value" + out = "" + while just_read != "": just_read = self.serial.read() out += just_read return out @@ -132,7 +133,7 @@ def program_number(self): :returns: the firmware version :rtype: str """ - self._send_command('PNR') + self._send_command("PNR") return self._get_data() def pressure_gauge(self, gauge=1): @@ -145,12 +146,12 @@ def pressure_gauge(self, gauge=1): :rtype: tuple """ if gauge not in [1, 2]: - message = 'The input gauge number can only be 1 or 2' + message = "The input gauge number can only be 1 or 2" raise ValueError(message) - self._send_command('PR' + str(gauge)) + self._send_command("PR" + str(gauge)) reply = self._get_data() - status_code = int(reply.split(',')[0]) - value = float(reply.split(',')[1]) + status_code = int(reply.split(",")[0]) + value = float(reply.split(",")[1]) return value, (status_code, MEASUREMENT_STATUS[status_code]) def pressure_gauges(self): @@ -160,15 +161,19 @@ def pressure_gauges(self): (status_code2, status_message2)) :rtype: tuple """ - self._send_command('PRX') + self._send_command("PRX") reply = self._get_data() # The reply is on the form: x,sx.xxxxEsxx,y,sy.yyyyEsyy - status_code1 = int(reply.split(',')[0]) - value1 = float(reply.split(',')[1]) - status_code2 = int(reply.split(',')[2]) - value2 = float(reply.split(',')[3]) - return (value1, (status_code1, MEASUREMENT_STATUS[status_code1]), - value2, (status_code2, MEASUREMENT_STATUS[status_code2])) + status_code1 = int(reply.split(",")[0]) + value1 = float(reply.split(",")[1]) + status_code2 = int(reply.split(",")[2]) + value2 = float(reply.split(",")[3]) + return ( + value1, + (status_code1, MEASUREMENT_STATUS[status_code1]), + value2, + (status_code2, MEASUREMENT_STATUS[status_code2]), + ) def gauge_identification(self): """Return the gauge identication @@ -176,9 +181,9 @@ def gauge_identification(self): :return: (id_code_1, id_1, id_code_2, id_2) :rtype: tuple """ - self._send_command('TID') + self._send_command("TID") reply = self._get_data() - id1, id2 = reply.split(',') + id1, id2 = reply.split(",") return id1, GAUGE_IDS[id1], id2, GAUGE_IDS[id2] def pressure_unit(self): @@ -187,7 +192,7 @@ def pressure_unit(self): :return: the pressure unit :rtype: str """ - self._send_command('UNI') + self._send_command("UNI") unit_code = int(self._get_data()) return PRESSURE_UNITS[unit_code] @@ -197,21 +202,21 @@ def rs232_communication_test(self): :return: the status of the communication test :rtype: bool """ - self._send_command('RST') + self._send_command("RST") self.serial.write(self.ENQ) self._clear_output_buffer() - test_string_out = '' - for char in 'a1': + test_string_out = "" + for char in "a1": self.serial.write(char) test_string_out += self._get_data().rstrip(self.ENQ) self._send_command(self.ETX) - return test_string_out == 'a1' + return test_string_out == "a1" class TPG262(TPG26x): """Driver for the TPG 262 dual channel measurement and control unit""" - def __init__(self, port='/dev/ttyUSB0', baudrate=9600): + def __init__(self, port="/dev/ttyUSB0", baudrate=9600): """Initialize internal variables and serial connection :param port: The COM port to open. See the documentation for @@ -219,7 +224,7 @@ def __init__(self, port='/dev/ttyUSB0', baudrate=9600): of the possible value. The default value is '/dev/ttyUSB0'. :type port: str or int :param baudrate: 9600, 19200, 38400 where 9600 is the default - :type baudrate: int + :type baudrate: int """ super(TPG262, self).__init__(port=port, baudrate=baudrate) @@ -227,7 +232,7 @@ def __init__(self, port='/dev/ttyUSB0', baudrate=9600): class TPG261(TPG26x): """Driver for the TPG 261 dual channel measurement and control unit""" - def __init__(self, port='/dev/ttyUSB0', baudrate=9600): + def __init__(self, port="/dev/ttyUSB0", baudrate=9600): """Initialize internal variables and serial connection :param port: The COM port to open. See the documentation for diff --git a/PyExpLabSys/drivers/pfeiffer_qmg420.py b/PyExpLabSys/drivers/pfeiffer_qmg420.py index 5d2992a8..194f8db8 100644 --- a/PyExpLabSys/drivers/pfeiffer_qmg420.py +++ b/PyExpLabSys/drivers/pfeiffer_qmg420.py @@ -7,10 +7,11 @@ # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) -class qmg_420(): +class qmg_420: def speeds(self, n): speeds = {} + # fmt: off speeds[0] = 0.0005 speeds[1] = 0.001 speeds[2] = 0.002 @@ -27,10 +28,11 @@ def speeds(self, n): speeds[13] = 10 speeds[14] = 20 speeds[15] = 60 + # fmt: on return speeds[n] - def ranges(self, index, reverse = False): - """ Return the physical range of a returned index """ + def ranges(self, index, reverse=False): + """Return the physical range of a returned index""" range_values = [0] * 8 range_values[0] = -5 range_values[1] = -6 @@ -51,14 +53,14 @@ def ranges(self, index, reverse = False): else: return range_values[int(index)] - def __init__(self, switch_range = False): - self.f = serial.Serial('/dev/ttyUSB0', 9600) + def __init__(self, switch_range=False): + self.f = serial.Serial("/dev/ttyUSB0", 9600) self.switch_9_and_11 = switch_range - self.type = '420' + self.type = "420" self.communication_mode(computer_control=True) def comm(self, command): - """ Communicates with Baltzers/Pferiffer Mass Spectrometer + """Communicates with Baltzers/Pferiffer Mass Spectrometer Implements the low-level protocol for RS-232 communication with the instrument. High-level protocol can be implemented using this as a @@ -68,123 +70,133 @@ def comm(self, command): LOGGER.debug("Command in progress: " + command) waiting = self.f.inWaiting() - if waiting > 0: #Skip characters that are currently waiting in line + if waiting > 0: # Skip characters that are currently waiting in line debug_info = self.f.read(waiting) - LOGGER.debug("Elements not read: " + str(waiting) + - ": Contains: " + debug_info) - - commands_without_reply = ['SEM', 'EMI', 'SEV', 'OPM', 'CHA', - 'CHM', 'SPE', 'FIR', 'WID','RUN', - 'STP', 'RAN', 'CHA', 'SYN', 'CYC', - 'STA', 'DET', 'CTR'] - self.f.write(command + '\r') - mem = command.split(' ')[0] + LOGGER.debug("Elements not read: " + str(waiting) + ": Contains: " + debug_info) + + commands_without_reply = [ + "SEM", + "EMI", + "SEV", + "OPM", + "CHA", + "CHM", + "SPE", + "FIR", + "WID", + "RUN", + "STP", + "RAN", + "CHA", + "SYN", + "CYC", + "STA", + "DET", + "CTR", + ] + self.f.write(command + "\r") + mem = command.split(" ")[0] if not mem in commands_without_reply: ret_string = self.f.readline() else: ret_string = "" - ret_string = ret_string.replace('\n','') - ret_string = ret_string.replace('\r','') + ret_string = ret_string.replace("\n", "") + ret_string = ret_string.replace("\r", "") return ret_string - def status(self, command, index): status = self.comm(command) - data = status[:-2].split(',') + data = status[:-2].split(",") ret_string = data[index] return ret_string def simulation(self): - """ Produces a simulated spectrum, does not work on qmg420 """ + """Produces a simulated spectrum, does not work on qmg420""" pass def sem_status(self, voltage=-1, turn_off=False, turn_on=False): - """ Get or set the SEM status """ + """Get or set the SEM status""" if voltage > -1: - self.comm('SEM ' + str(voltage)) - ret_string = self.status('RDE', 4) - else: #NOT IMPLEMENTED - ret_string = self.status('RDE', 4) + self.comm("SEM " + str(voltage)) + ret_string = self.status("RDE", 4) + else: # NOT IMPLEMENTED + ret_string = self.status("RDE", 4) sem_voltage = int(ret_string) - if turn_off ^ turn_on: #Only accept self-consistent sem-changes + if turn_off ^ turn_on: # Only accept self-consistent sem-changes if turn_off: - self.comm('SEV 0') + self.comm("SEV 0") if turn_on: - self.comm('SEV 1') + self.comm("SEV 1") - ret_string = self.status('ROP', 2) + ret_string = self.status("ROP", 2) sem_on = ret_string == "1" return sem_voltage, sem_on def speed(self, speed): - """ Set the integration speed """ + """Set the integration speed""" if speed > 3: - self.comm('SPE ' + str(speed)) + self.comm("SPE " + str(speed)) return self.speeds(speed) - def emission_status(self, current=-1, turn_off=False, turn_on=False): - """ Get or set the emission status. """ + """Get or set the emission status.""" emission_current = -1 if turn_off ^ turn_on: if turn_off: - self.comm('EMI 0') + self.comm("EMI 0") if turn_on: - self.comm('EMI 1') - ret_string = self.status('ROP', 3) + self.comm("EMI 1") + ret_string = self.status("ROP", 3) - filament_on = ret_string == '1' + filament_on = ret_string == "1" return emission_current, filament_on - def detector_status(self, SEM=False, faraday_cup=False): - return 'Not possible on this model' - + return "Not possible on this model" def read_voltages(self): - """ Read the voltages on the lens system """ - print('Not possible on this QMG model') + """Read the voltages on the lens system""" + print("Not possible on this QMG model") def set_channel(self, channel): - """ Set the active measurement channel """ - self.comm('CHA ' + str(channel)) - + """Set the active measurement channel""" + self.comm("CHA " + str(channel)) def read_sem_voltage(self): - """ Read the selected SEM voltage """ - sem_voltage = self.status('RDE', 4) + """Read the selected SEM voltage""" + sem_voltage = self.status("RDE", 4) return sem_voltage def read_preamp_range(self): - """ Read the preamp range """ - preamp_index = self.status('RDE', 1) + """Read the preamp range""" + preamp_index = self.status("RDE", 1) preamp_range = self.ranges(index=preamp_index) return preamp_range def read_timestep(self): - timestep = self.status('RSC', 5) + timestep = self.status("RSC", 5) return timestep def measurement_running(self): - """ Return true if measurement is running """ - running = self.comm('STW')[6] == '0' + """Return true if measurement is running""" + running = self.comm("STW")[6] == "0" return running def mass_time(self, ns): - """ Configure instrument for mass time """ - self.comm('OPM 1') #0, single. 1, multi - #self.comm('CTR ,0') #Trigger mode, 0=auto trigger - self.comm('CYC 1') #Number of repetitions - #self.comm('CBE ,1') #First measurement channel in multi mode - #self.comm('CEN ,' + str(ns)) #Last measurement channel in multi mod + """Configure instrument for mass time""" + self.comm("OPM 1") # 0, single. 1, multi + # self.comm('CTR ,0') #Trigger mode, 0=auto trigger + self.comm("CYC 1") # Number of repetitions + # self.comm('CBE ,1') #First measurement channel in multi mode + # self.comm('CEN ,' + str(ns)) #Last measurement channel in multi mod def start_measurement(self): - self.comm('RUN') + self.comm("RUN") def waiting_samples(self): - length = int(self.comm('RBC')) + length = int(self.comm("RBC")) if length > 2: length = length - 2 else: @@ -192,41 +204,41 @@ def waiting_samples(self): return length def communication_mode(self, computer_control=False): - """ Set the qmg420 up for rs232 controll """ - self.comm('CTR 1') + """Set the qmg420 up for rs232 controll""" + self.comm("CTR 1") return True def first_mass(self, mass): - """ Set the first mass of a mass scan or the current mass """ - self.comm('FIR ' + str(mass)) + """Set the first mass of a mass scan or the current mass""" + self.comm("FIR " + str(mass)) def get_multiple_samples(self, number): - """ Retrive more than one sample from the device """ + """Retrive more than one sample from the device""" values = [0] * number for i in range(0, number): val = self.comm(chr(5)) - if not val == '': + if not val == "": values[i] = val return values def get_single_sample(self): - """ Retrieve a single sample from the device """ + """Retrieve a single sample from the device""" error = 0 while (self.waiting_samples() == 0) and (error < 40): time.sleep(0.2) error = error + 1 if error > 39: - LOGGER.error('Sample did arrive on time') + LOGGER.error("Sample did arrive on time") value = "" else: value = self.comm(chr(5)) return value def config_channel(self, channel, mass=-1, speed=-1, amp_range=-1, enable=""): - """ Config a MS channel for measurement """ + """Config a MS channel for measurement""" self.set_channel(channel) - self.comm('OPM 1') - LOGGER.error('Wanted range, channel ' + str(channel) + ': ' + str(amp_range)) + self.comm("OPM 1") + LOGGER.error("Wanted range, channel " + str(channel) + ": " + str(amp_range)) if mass > -1: self.first_mass(mass) @@ -236,30 +248,29 @@ def config_channel(self, channel, mass=-1, speed=-1, amp_range=-1, enable=""): if amp_range < -2: range_index = self.ranges(amp_range, reverse=True) - LOGGER.error('Range, channel ' + str(channel) + str(range_index)) - self.comm('RAN ' + str(range_index)) + LOGGER.error("Range, channel " + str(channel) + str(range_index)) + self.comm("RAN " + str(range_index)) if enable == "yes": - self.comm('STA 1') + self.comm("STA 1") if enable == "no": - self.comm('STA 0') + self.comm("STA 0") - #Default values, not currently choosable from function parameters - #self.comm('DSE ,0') #Use default SEM voltage - #self.comm('DTY ,1') #Use SEM for ion detection - self.comm('CHM 2') #Single mass measurement (opposed to mass-scan) - #self.comm('CHM 3') #peak processor - #self.comm('MRE ,15') #Peak resolution + # Default values, not currently choosable from function parameters + # self.comm('DSE ,0') #Use default SEM voltage + # self.comm('DTY ,1') #Use SEM for ion detection + self.comm("CHM 2") # Single mass measurement (opposed to mass-scan) + # self.comm('CHM 3') #peak processor + # self.comm('MRE ,15') #Peak resolution def mass_scan(self, first_mass, scan_width, amp_range=-7): - self.comm('CHA 0') - self.comm('OPM 0') - self.comm('FIR ' + str(first_mass)) - self.comm('WID ' + str(scan_width)) - self.comm('SPE ' + str(10)) + self.comm("CHA 0") + self.comm("OPM 0") + self.comm("FIR " + str(first_mass)) + self.comm("WID " + str(scan_width)) + self.comm("SPE " + str(10)) range_index = self.ranges(amp_range, reverse=True) - self.comm('RAN ' + str(range_index)) - self.comm('CHM 0') # Mass scan, to enable FIR filter, set value to 1 - self.comm('STA 1') - self.comm('DET 1') # Use SEM for ion detection - + self.comm("RAN " + str(range_index)) + self.comm("CHM 0") # Mass scan, to enable FIR filter, set value to 1 + self.comm("STA 1") + self.comm("DET 1") # Use SEM for ion detection diff --git a/PyExpLabSys/drivers/pfeiffer_qmg422.py b/PyExpLabSys/drivers/pfeiffer_qmg422.py index 78a614c2..1286c26f 100644 --- a/PyExpLabSys/drivers/pfeiffer_qmg422.py +++ b/PyExpLabSys/drivers/pfeiffer_qmg422.py @@ -18,29 +18,31 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller LOGGER.addHandler(logging.NullHandler()) + class qmg_422(object): - """ The actual driver class. """ - def __init__(self, port='/dev/ttyS0', speed=19200): - """ Initialize the module - """ + """The actual driver class.""" + + def __init__(self, port="/dev/ttyS0", speed=19200): + """Initialize the module""" self.serial = serial.Serial(port, speed, timeout=2.0) self.reverse_range = False self.communication_mode(computer_control=True) - self.type = '422' - if self.comm('SQA') == '1': - self.series = '400' + self.type = "422" + if self.comm("SQA") == "1": + self.series = "400" else: - self.series = '125' - self.state = {'emission': 'Unknown', 'sem': 'Unknown'} + self.series = "125" + self.state = {"emission": "Unknown", "sem": "Unknown"} def comm(self, command): - """ Communicates with Baltzers/Pferiffer Mass Spectrometer + """Communicates with Baltzers/Pferiffer Mass Spectrometer Implements the low-level protocol for RS-232 communication with the instrument. High-level protocol can be implemented using this as a @@ -58,7 +60,7 @@ def comm(self, command): LOGGER.debug("Command in progress: %s", command) n = self.serial.inWaiting() - if n > 0: #Skip characters that are currently waiting in line + if n > 0: # Skip characters that are currently waiting in line debug_info = self.serial.read(n).decode() LOGGER.debug("Elements not read: " + str(n) + ": Contains: " + debug_info) @@ -66,9 +68,9 @@ def comm(self, command): error_counter = 0 while not ret[0] == chr(6): error_counter += 1 - command_text = command + '\r' + command_text = command + "\r" LOGGER.debug("Command text: %s", command_text) - self.serial.write(command_text.encode('ascii')) + self.serial.write(command_text.encode("ascii")) ret = self.serial.readline().decode() LOGGER.debug("Debug: Error counter: %d", error_counter) LOGGER.debug("ret: %d", ord(ret[0])) @@ -82,8 +84,8 @@ def comm(self, command): LOGGER.error("Communication error! Quit program!") quit() - #We are now quite sure the instrument is ready to give back data - self.serial.write(chr(5).encode('ascii')) + # We are now quite sure the instrument is ready to give back data + self.serial.write(chr(5).encode("ascii")) ret = self.serial.readline().decode() LOGGER.debug("Number in waiting after enq: %d", n) @@ -97,23 +99,22 @@ def comm(self, command): else: LOGGER.info("Wrong line termination") LOGGER.info("Ascii value of last char in ret: %s", ord(ret[-1])) - LOGGER.info('Value of string: %s', ret) + LOGGER.info("Value of string: %s", ret) time.sleep(0.5) self.serial.write(chr(5)) - ret = ' ' - while ret[-1] != '\n': + ret = " " + while ret[-1] != "\n": ret += self.serial.read(1) - #ret = self.serial.readline() + # ret = self.serial.readline() ret_string = ret.strip() LOGGER.info("Ascii value of last char in ret: %d", ord(ret[-1])) - LOGGER.info('Value of string: %s', ret) - LOGGER.info('Returning: %s', ret_string) + LOGGER.info("Value of string: %s", ret) + LOGGER.info("Returning: %s", ret_string) done = True return ret_string - def communication_mode(self, computer_control=False): - """ Returns and sets the communication mode. + """Returns and sets the communication mode. :param computer_control: Activates ASCII communication with the device :type computer_control: bool @@ -121,30 +122,30 @@ def communication_mode(self, computer_control=False): :rtype: str """ if computer_control: - ret_string = self.comm('CMO ,1') + ret_string = self.comm("CMO ,1") else: - ret_string = self.comm('CMO') + ret_string = self.comm("CMO") comm_mode = ret_string - if ret_string == '0': - comm_mode = 'Console Keybord' - if ret_string == '1': - comm_mode = 'ASCII' - if ret_string == '2': - comm_mode = 'BIN' - if ret_string == '3': - comm_mode = 'Modem' - if ret_string == '4': - comm_mode = 'LAN' + if ret_string == "0": + comm_mode = "Console Keybord" + if ret_string == "1": + comm_mode = "ASCII" + if ret_string == "2": + comm_mode = "BIN" + if ret_string == "3": + comm_mode = "Modem" + if ret_string == "4": + comm_mode = "LAN" return comm_mode def simulation(self): - """ Chekcs wheter the instruments returns real or simulated data + """Chekcs wheter the instruments returns real or simulated data :return: Message telling whether the device is in simulation mode :rtype: str """ - ret_string = self.comm('TSI ,0') + ret_string = self.comm("TSI ,0") if int(ret_string) == 0: sim_state = "Simulation not running" else: @@ -152,44 +153,44 @@ def simulation(self): return sim_state def set_channel(self, channel): - """ Set the current channel + """Set the current channel :param channel: The channel number :type channel: integer """ - self.comm('SPC ,' + str(channel)) #Select the relevant channel + self.comm("SPC ," + str(channel)) # Select the relevant channel def read_sem_voltage(self): - """ Read the SEM Voltage + """Read the SEM Voltage :return: The SEM voltage :rtype: str """ - sem_voltage = self.comm('SHV') + sem_voltage = self.comm("SHV") return sem_voltage def read_preamp_range(self): - """ Read the preamp range + """Read the preamp range This function is not fully implemented :return: The preamp range :rtype: str """ - auto_range = self.comm('AMO') - if auto_range in ('1', '2'): - preamp_range = '0' #Idicates auto-range in mysql-table + auto_range = self.comm("AMO") + if auto_range in ("1", "2"): + preamp_range = "0" # Idicates auto-range in mysql-table else: - preamp_range = self.comm('ARA') + preamp_range = self.comm("ARA") return preamp_range def read_timestep(self): - """ Reads the integration period of a measurement + """Reads the integration period of a measurement :return: The integration period in non-physical unit :rtype: str """ # TODO: Make a look-up table to make the number physical - timestep = self.comm('MSD') + timestep = self.comm("MSD") return timestep def sem_status(self, voltage=-1, turn_off=False, turn_on=False): - """ Get or set the SEM status + """Get or set the SEM status :param voltage: The wanted SEM-voltage :type voltage: integer :param turn_off: If True SEM will be turned on (unless turn_of==True) @@ -200,22 +201,22 @@ def sem_status(self, voltage=-1, turn_off=False, turn_on=False): :rtype: integer, boolan """ if voltage > -1: - ret_string = self.comm('SHV ,' + str(voltage)) + ret_string = self.comm("SHV ," + str(voltage)) else: - ret_string = self.comm('SHV') + ret_string = self.comm("SHV") sem_voltage = int(ret_string) - if turn_off ^ turn_on: #Only accept self-consistent sem-changes + if turn_off ^ turn_on: # Only accept self-consistent sem-changes if turn_off: - self.comm('SEM ,0') + self.comm("SEM ,0") if turn_on: - self.comm('SEM ,1') - ret_string = self.comm('SEM') + self.comm("SEM ,1") + ret_string = self.comm("SEM") sem_on = ret_string == "1" return sem_voltage, sem_on def emission_status(self, current=-1, turn_off=False, turn_on=False): - """ Get or set the emission status. + """Get or set the emission status. :param current: The wanted emission status. Only works for QME??? :type current: integer :param turn_off: If True emission will be turned on (unless turn_of==True) @@ -226,31 +227,31 @@ def emission_status(self, current=-1, turn_off=False, turn_on=False): :rtype: integer, boolan """ if current > -1: - ret_string = self.comm('EMI ,' + str(current)) + ret_string = self.comm("EMI ," + str(current)) emission_current = float(ret_string.strip()) else: - ret_string = self.comm('EMI') + ret_string = self.comm("EMI") emission_current = float(ret_string.strip()) if turn_off ^ turn_on: if turn_off: - self.comm('FIE ,0') + self.comm("FIE ,0") if turn_on: - self.comm('FIE ,1') - ret_string = self.comm('FIE') + self.comm("FIE ,1") + ret_string = self.comm("FIE") - filament_on = ret_string == '1' + filament_on = ret_string == "1" return emission_current, filament_on def detector_status(self, SEM=False, faraday_cup=False): - """ Choose between SEM and Faraday cup measurements""" + """Choose between SEM and Faraday cup measurements""" if SEM ^ faraday_cup: if SEM: - ret_string = self.comm('SDT ,1') + ret_string = self.comm("SDT ,1") if faraday_cup: - ret_string = self.comm('SDT ,0') + ret_string = self.comm("SDT ,0") else: - ret_string = self.comm('SDT') + ret_string = self.comm("SDT") if int(ret_string) > 0: detector = "SEM" @@ -259,58 +260,58 @@ def detector_status(self, SEM=False, faraday_cup=False): return detector def read_voltages(self): - """ Read the qme-voltages """ - print("V01: " + self.comm('VO1')) #0..150, 1V steps - print("V02: " + self.comm('VO2')) #0..125, 0.5V steps - print("V03: " + self.comm('VO3')) #-30..30, 0.25V steps - print("V04: " + self.comm('VO4')) #0..60, 0.25V steps - print("V05: " + self.comm('VO5')) #0..450, 2V steps - print("V06: " + self.comm('VO6')) #0..450, 2V steps - print("V07: " + self.comm('VO7')) #0..250, 1V steps - print("V08: " + self.comm('VO8')) #-125..125,1V steps - print("V09: " + self.comm('VO9')) #0..60 ,0.25V steps + """Read the qme-voltages""" + print("V01: " + self.comm("VO1")) # 0..150, 1V steps + print("V02: " + self.comm("VO2")) # 0..125, 0.5V steps + print("V03: " + self.comm("VO3")) # -30..30, 0.25V steps + print("V04: " + self.comm("VO4")) # 0..60, 0.25V steps + print("V05: " + self.comm("VO5")) # 0..450, 2V steps + print("V06: " + self.comm("VO6")) # 0..450, 2V steps + print("V07: " + self.comm("VO7")) # 0..250, 1V steps + print("V08: " + self.comm("VO8")) # -125..125,1V steps + print("V09: " + self.comm("VO9")) # 0..60 ,0.25V steps def update_state(self): - """ Update the knowledge of the internal knowledge of the instrument """ - raw_state = self.comm('ESQ') - LOGGER.error('QMS State, ESQ: %s', raw_state) + """Update the knowledge of the internal knowledge of the instrument""" + raw_state = self.comm("ESQ") + LOGGER.error("QMS State, ESQ: %s", raw_state) state = {} - if self.series == '125': - state['emission_state'] = raw_state.split(',')[1] - else: # Emission state is 125 specific - state['emission_state'] = 'Unknown' - raw_state = int(raw_state[:raw_state.find(',')]) + if self.series == "125": + state["emission_state"] = raw_state.split(",")[1] + else: # Emission state is 125 specific + state["emission_state"] = "Unknown" + raw_state = int(raw_state[: raw_state.find(",")]) raw_state = bin(raw_state)[2:].zfill(16) - state['running'] = 'Not running' if raw_state[15] == '0' else 'Running' - state['mode'] = 'Mono' if raw_state[14] == '0' else 'Multi' - state['emission'] = 'Off' if raw_state[13] == '0' else 'On' - state['sem'] = 'Off' if raw_state[12] == '0' else 'On' - state['4'] = '0' if raw_state[11] == '0' else '1' - state['5'] = '0' if raw_state[10] == '0' else '1' - state['6'] = '0' if raw_state[9] == '0' else '1' - state['7'] = '0' if raw_state[8] == '0' else '1' - state['8'] = '0' if raw_state[7] == '0' else '1' - state['9'] = '0' if raw_state[6] == '0' else '1' - state['10'] = '0' if raw_state[5] == '0' else '1' - state['11'] = '0' if raw_state[4] == '0' else '1' - state['12'] = '0' if raw_state[3] == '0' else '1' - state['13'] = '0' if raw_state[2] == '0' else '1' - state['ringbuffer'] = 'Partly filled' if raw_state[1] == '0' else 'Empty' - state['ringbuffer'] = state['ringbuffer'] if raw_state[0] == '0' else 'Overflow' + state["running"] = "Not running" if raw_state[15] == "0" else "Running" + state["mode"] = "Mono" if raw_state[14] == "0" else "Multi" + state["emission"] = "Off" if raw_state[13] == "0" else "On" + state["sem"] = "Off" if raw_state[12] == "0" else "On" + state["4"] = "0" if raw_state[11] == "0" else "1" + state["5"] = "0" if raw_state[10] == "0" else "1" + state["6"] = "0" if raw_state[9] == "0" else "1" + state["7"] = "0" if raw_state[8] == "0" else "1" + state["8"] = "0" if raw_state[7] == "0" else "1" + state["9"] = "0" if raw_state[6] == "0" else "1" + state["10"] = "0" if raw_state[5] == "0" else "1" + state["11"] = "0" if raw_state[4] == "0" else "1" + state["12"] = "0" if raw_state[3] == "0" else "1" + state["13"] = "0" if raw_state[2] == "0" else "1" + state["ringbuffer"] = "Partly filled" if raw_state[1] == "0" else "Empty" + state["ringbuffer"] = state["ringbuffer"] if raw_state[0] == "0" else "Overflow" self.state = state def start_measurement(self): - """ Start the measurement """ + """Start the measurement""" start = time.time() - LOGGER.error('QMS Errors, ERR: %s', self.comm('ERR')) - LOGGER.error('QMS Warnings, EWN: %s', self.comm('EWN')) - LOGGER.error('Start time: %f', time.time()-start) + LOGGER.error("QMS Errors, ERR: %s", self.comm("ERR")) + LOGGER.error("QMS Warnings, EWN: %s", self.comm("EWN")) + LOGGER.error("Start time: %f", time.time() - start) self.update_state() - LOGGER.error('Start time: %f', time.time()-start) - self.comm('CRU ,2') + LOGGER.error("Start time: %f", time.time() - start) + self.comm("CRU ,2") def actual_range(self, amp_range): - """ Returns the range that should be send to achieve the desired range """ + """Returns the range that should be send to achieve the desired range""" real_range = amp_range if self.reverse_range is True: if amp_range == -9: @@ -324,40 +325,42 @@ def actual_range(self, amp_range): return real_range def get_single_sample(self): - """ Read a single sample from the device """ + """Read a single sample from the device""" samples = 0 while samples == 0: try: - status = self.comm('MBH') + status = self.comm("MBH") except: samples = samples - 1 - status = 'Error' - LOGGER.error('Serial timeout, continuing measurement') - LOGGER.info('Status: %s', status) + status = "Error" + LOGGER.error("Serial timeout, continuing measurement") + LOGGER.info("Status: %s", status) try: - status = status.split(',') + status = status.split(",") # Sometimes an error occurs in this response (most often "526,0" instead of # "1,1,9,1,0". The correct response seems to lie unread in the machine # Solved now by just resending the command 'MBH'. - if len(status) != 5: # try again - LOGGER.warning('Could not read status properly - trying again') + if len(status) != 5: # try again + LOGGER.warning("Could not read status properly - trying again") LOGGER.warning(status) continue samples = int(status[3]) except: - LOGGER.warning('Could not read status, continuing measurement') + LOGGER.warning("Could not read status, continuing measurement") LOGGER.warning(status) samples = samples - 1 - if samples < -30: # This will only be invoked if status.split(',')[3] returns -30 ? + if ( + samples < -30 + ): # This will only be invoked if status.split(',')[3] returns -30 ? usefull_value = False value = -1 break if samples > 0: try: - value = self.comm('MDB') + value = self.comm("MDB") usefull_value = True except: - LOGGER.error('Error in MDB command') + LOGGER.error("Error in MDB command") value = -1 usefull_value = False else: @@ -366,48 +369,47 @@ def get_single_sample(self): return value, usefull_value def get_multiple_samples(self, number): - """ Read multiple samples from the device """ + """Read multiple samples from the device""" values = [0] * number for i in range(0, number): - values[i] = self.comm('MDB') + values[i] = self.comm("MDB") return values - def config_channel(self, channel, mass=-1, speed=-1, enable="", amp_range=0): - """ Config a MS channel for measurement """ - self.comm('SPC ,' + str(channel)) #SPC: Select current parameter channel + """Config a MS channel for measurement""" + self.comm("SPC ," + str(channel)) # SPC: Select current parameter channel if mass > -1: - self.comm('MFM ,' + str(mass)) + self.comm("MFM ," + str(mass)) if speed > -1: - self.comm('MSD ,' + str(speed)) + self.comm("MSD ," + str(speed)) if enable == "yes": - self.comm('AST ,0') + self.comm("AST ,0") if enable == "no": - self.comm('AST ,1') + self.comm("AST ,1") if amp_range == 0: - self.comm('AMO, 1') #Auto range with lower limit + self.comm("AMO, 1") # Auto range with lower limit # TODO: Lower limit should be read from config file - self.comm('ARL, -11') # Lower auto range level + self.comm("ARL, -11") # Lower auto range level else: - self.comm('AMO, 0') #Fix range - self.comm('ARA, ' + str(self.actual_range(amp_range))) - - #Default values, not currently choosable from function parameters - self.comm('DSE ,0') #Use default SEM voltage - self.comm('DTY ,1') #Use SEM for ion detection - self.comm('SDT ,1') #Use SEM for ion detection - #self.comm('DTY ,0') #Use Faraday cup for ion detection - #self.comm('SDT ,0') #Use Faraday cup for ion detection - self.comm('MMO ,3') #Single mass measurement (opposed to mass-scan) - self.comm('MRE ,15') #Peak resolution + self.comm("AMO, 0") # Fix range + self.comm("ARA, " + str(self.actual_range(amp_range))) + + # Default values, not currently choosable from function parameters + self.comm("DSE ,0") # Use default SEM voltage + self.comm("DTY ,1") # Use SEM for ion detection + self.comm("SDT ,1") # Use SEM for ion detection + # self.comm('DTY ,0') #Use Faraday cup for ion detection + # self.comm('SDT ,0') #Use Faraday cup for ion detection + self.comm("MMO ,3") # Single mass measurement (opposed to mass-scan) + self.comm("MRE ,15") # Peak resolution def measurement_running(self): - """ Check if a measurement is running """ + """Check if a measurement is running""" error = 0 while error < 10: - status = self.comm('MBH') - status = status.split(',') + status = self.comm("MBH") + status = status.split(",") try: running = int(status[0]) break @@ -420,14 +422,14 @@ def measurement_running(self): return return_val def waiting_samples(self): - """ Return number of waiting samples """ - header = self.comm('MBH') - header = header.split(',') + """Return number of waiting samples""" + header = self.comm("MBH") + header = header.split(",") number_of_samples = int(header[3]) return number_of_samples def mass_scan(self, first_mass, scan_width, amp_range=0, speed=9): - """ Setup the mass spec for a mass scan """ + """Setup the mass spec for a mass scan""" speed_list = { 0: 0.0005, 1: 0.001, @@ -444,57 +446,59 @@ def mass_scan(self, first_mass, scan_width, amp_range=0, speed=9): 12: 5, 13: 10, 14: 20, - 15: 60} # unit: [s/amu] + 15: 60, + } # unit: [s/amu] try: total_time = scan_width * speed_list[speed] except: total_time = -1 if amp_range == 0: - self.comm('AMO, 1') #Auto range with lower limit + self.comm("AMO, 1") # Auto range with lower limit # TODO: Lower limit should be read from config file - self.comm('ARL, -11') # Lower auto range level + self.comm("ARL, -11") # Lower auto range level else: - self.comm('AMO, 0') #Fix range - self.comm('ARA, ' + str(self.actual_range(amp_range))) - - self.comm('CYM, 0') #0, single. 1, multi - self.comm('SMC, 0') #Channel 0 - self.comm('DSE ,0') #Use default SEM voltage - self.comm('DTY ,1') #Use SEM for ion detection - self.comm('SDT ,1') #Use SEM for ion detection - self.comm('MRE ,1') #Resolve peak - self.comm('MMO, 0') #Mass scan, to enable FIR filter, set value to 1 - self.comm('MST, 0') #Steps 0: 1: 2: 64/amu - self.comm('MSD, ' + str(speed)) #Speed - self.comm('MFM, ' + str(first_mass)) #First mass - self.comm('MWI, ' + str(scan_width)) #Scan width + self.comm("AMO, 0") # Fix range + self.comm("ARA, " + str(self.actual_range(amp_range))) + + self.comm("CYM, 0") # 0, single. 1, multi + self.comm("SMC, 0") # Channel 0 + self.comm("DSE ,0") # Use default SEM voltage + self.comm("DTY ,1") # Use SEM for ion detection + self.comm("SDT ,1") # Use SEM for ion detection + self.comm("MRE ,1") # Resolve peak + self.comm("MMO, 0") # Mass scan, to enable FIR filter, set value to 1 + self.comm("MST, 0") # Steps 0: 1: 2: 64/amu + self.comm("MSD, " + str(speed)) # Speed + self.comm("MFM, " + str(first_mass)) # First mass + self.comm("MWI, " + str(scan_width)) # Scan width return total_time def mass_time(self, ns): - """ Setup the mass spec for a mass-time measurement """ - self.comm('CYM ,1') #0, single. 1, multi - self.comm('CTR ,0') #Trigger mode, 0=auto trigger - self.comm('CYS ,1') #Number of repetitions - self.comm('CBE ,1') #First measurement channel in multi mode - self.comm('CEN ,' + str(ns)) #Last measurement channel in multi mod - -if __name__ == '__main__': - qmg = qmg_422(port='/dev/ttyUSB0') + """Setup the mass spec for a mass-time measurement""" + self.comm("CYM ,1") # 0, single. 1, multi + self.comm("CTR ,0") # Trigger mode, 0=auto trigger + self.comm("CYS ,1") # Number of repetitions + self.comm("CBE ,1") # First measurement channel in multi mode + self.comm("CEN ," + str(ns)) # Last measurement channel in multi mod + + +if __name__ == "__main__": + qmg = qmg_422(port="/dev/ttyUSB0") print(qmg.communication_mode(computer_control=True)) print(qmg.read_voltages()) print(qmg.detector_status()) - print(qmg.comm('SMR')) - print('---') - print('DTY: ' + qmg.comm('DTY')) # Signal source, 0: Faraday, 1: SEM - print('DSE: ' + qmg.comm('SHV')) # SEM Voltage - print('ECU: ' + qmg.comm('ECU')) - print('SEM: ' + qmg.comm('SEM')) # SEM Voltage - print('SQA: ' + qmg.comm('SQA')) # Type of analyzer, 0: 125, 1: 400, 4:200 - print('SMR: ' + qmg.comm('SMR')) # Mass-range, this needs to go in a config-file - print('SDT: ' + qmg.comm('SDT')) # Detector type - print('SIT: ' + qmg.comm('SIT')) # Ion source - print('AIN: ' + qmg.comm('AIN')) # Analog in + print(qmg.comm("SMR")) + print("---") + print("DTY: " + qmg.comm("DTY")) # Signal source, 0: Faraday, 1: SEM + print("DSE: " + qmg.comm("SHV")) # SEM Voltage + print("ECU: " + qmg.comm("ECU")) + print("SEM: " + qmg.comm("SEM")) # SEM Voltage + print("SQA: " + qmg.comm("SQA")) # Type of analyzer, 0: 125, 1: 400, 4:200 + print("SMR: " + qmg.comm("SMR")) # Mass-range, this needs to go in a config-file + print("SDT: " + qmg.comm("SDT")) # Detector type + print("SIT: " + qmg.comm("SIT")) # Ion source + print("AIN: " + qmg.comm("AIN")) # Analog in print(qmg.state) qmg.update_state() print(qmg.state) diff --git a/PyExpLabSys/drivers/pfeiffer_turbo_pump.py b/PyExpLabSys/drivers/pfeiffer_turbo_pump.py index 9b0adc8c..e3fbb397 100644 --- a/PyExpLabSys/drivers/pfeiffer_turbo_pump.py +++ b/PyExpLabSys/drivers/pfeiffer_turbo_pump.py @@ -8,13 +8,15 @@ import logging import serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) + class CursesTui(threading.Thread): - """ Text gui for controlling the pump """ + """Text gui for controlling the pump""" def __init__(self, turbo_instance): # TODO: Add support for several pumps in one gui @@ -31,143 +33,149 @@ def __init__(self, turbo_instance): def run(self): while not self.quit: - self.screen.addstr(3, 2, 'Turbo controller running') - #if self.turbo.status['pump_accelerating']: - self.screen.addstr(3, 41, '| Vent mode: ' \ - + self.turbo.status['vent_mode'] + ' ') + self.screen.addstr(3, 2, "Turbo controller running") + # if self.turbo.status['pump_accelerating']: + self.screen.addstr( + 3, 41, "| Vent mode: " + self.turbo.status["vent_mode"] + " " + ) # self.screen.clrtoeol() - #else: + # else: # self.screen.addstr(3, 30, 'Pump at constant speed') - self.screen.addstr(4, 2, 'Gas mode: ' + self.turbo.status['gas_mode'] + ' ') - tmp = '| Venting setpoint: {:d}% ' - self.screen.addstr(4, 41, tmp.format(self.turbo.status['vent_freq'])) - self.screen.addstr(5, 2, 'Acc A1: ' + self.turbo.status['A1'] + ' ') - tmp = '| Venting time: {0:.2f} minutes ' - self.screen.addstr(5, 41, tmp.format(self.turbo.status['vent_time'])) - self.screen.addstr(6, 2, 'Sealing gas: ' + - self.turbo.status['sealing_gas'] + ' ') + self.screen.addstr(4, 2, "Gas mode: " + self.turbo.status["gas_mode"] + " ") + tmp = "| Venting setpoint: {:d}% " + self.screen.addstr(4, 41, tmp.format(self.turbo.status["vent_freq"])) + self.screen.addstr(5, 2, "Acc A1: " + self.turbo.status["A1"] + " ") + tmp = "| Venting time: {0:.2f} minutes " + self.screen.addstr(5, 41, tmp.format(self.turbo.status["vent_time"])) + self.screen.addstr( + 6, 2, "Sealing gas: " + self.turbo.status["sealing_gas"] + " " + ) tmp = "Rotation speed: {0:.2f}Hz " - self.screen.addstr(8, 2, tmp.format(self.turbo.status['rotation_speed'])) + self.screen.addstr(8, 2, tmp.format(self.turbo.status["rotation_speed"])) tmp = "Setpoint speed: {0:.2f}Hz " - self.screen.addstr(8, 28, tmp.format(self.turbo.status['set_rotation_speed'])) + self.screen.addstr(8, 28, tmp.format(self.turbo.status["set_rotation_speed"])) tmp = "Drive current: {0:.2f}A " - self.screen.addstr(9, 2, tmp.format(self.turbo.status['drive_current'])) + self.screen.addstr(9, 2, tmp.format(self.turbo.status["drive_current"])) tmp = "Drive power: {0:.0f}W " - self.screen.addstr(10, 2, tmp.format(self.turbo.status['drive_power'])) + self.screen.addstr(10, 2, tmp.format(self.turbo.status["drive_power"])) tmp = "Temperature, Electronics: {0:.0f}C " - self.screen.addstr(12, 2, tmp.format(self.turbo.status['temp_electronics'])) + self.screen.addstr(12, 2, tmp.format(self.turbo.status["temp_electronics"])) tmp = "Temperature, Bottom: {0:.0f}C " - self.screen.addstr(13, 2, tmp.format(self.turbo.status['temp_bottom'])) + self.screen.addstr(13, 2, tmp.format(self.turbo.status["temp_bottom"])) tmp = "Temperature, Bearings: {0:.0f}C " - self.screen.addstr(14, 2, tmp.format(self.turbo.status['temp_bearings'])) + self.screen.addstr(14, 2, tmp.format(self.turbo.status["temp_bearings"])) tmp = "Temperature, Motor: {0:.0f}C " - self.screen.addstr(15, 2, tmp.format(self.turbo.status['temp_motor'])) + self.screen.addstr(15, 2, tmp.format(self.turbo.status["temp_motor"])) tmp = "Operating hours: {0:.0f} ({1:.1f}days) " - hours = self.turbo.status['operating_hours'] + hours = self.turbo.status["operating_hours"] self.screen.addstr(18, 2, tmp.format(hours, hours / 24.0)) tmp = "Driver runtime: {0:.1f}s " - self.screen.addstr(19, 2, tmp.format(self.turbo.status['runtime'])) - self.screen.addstr(20, 2, 'Port: ' + self.turbo.serial.port) - self.screen.addstr(21, 2, 'q: quit, u: spin up, d: spin down') + self.screen.addstr(19, 2, tmp.format(self.turbo.status["runtime"])) + self.screen.addstr(20, 2, "Port: " + self.turbo.serial.port) + self.screen.addstr(21, 2, "q: quit, u: spin up, d: spin down") char_num = self.screen.getch() - if char_num == ord('q'): + if char_num == ord("q"): self.turbo.running = False self.quit = True - self.screen.addstr(2, 2, 'Quitting...') - if char_num == ord('d'): - self.turbo.status['spin_down'] = True - if char_num == ord('u'): - self.turbo.status['spin_up'] = True + self.screen.addstr(2, 2, "Quitting...") + if char_num == ord("d"): + self.turbo.status["spin_down"] = True + if char_num == ord("u"): + self.turbo.status["spin_up"] = True self.screen.refresh() time.sleep(0.2) - LOGGER.info('TUI ended') + LOGGER.info("TUI ended") def stop(self): - """ Cleanup terminal """ + """Cleanup terminal""" curses.nocbreak() self.screen.keypad(False) curses.echo() curses.endwin() + class TurboReader(threading.Thread): - """ Keeps track of all data from a turbo pump with the intend of logging them """ + """Keeps track of all data from a turbo pump with the intend of logging them""" + def __init__(self, turbo_instance): - #TODO: Add support for several pumps + # TODO: Add support for several pumps threading.Thread.__init__(self) self.mal = 20 # Moving average length self.turbo = turbo_instance self.log = {} - self.log['rotation_speed'] = {} - self.log['rotation_speed']['time'] = 600 - self.log['rotation_speed']['change'] = 1.03 - self.log['rotation_speed']['mean'] = [0] * self.mal - self.log['rotation_speed']['last_recorded_value'] = 0 - self.log['rotation_speed']['last_recorded_time'] = 0 - - self.log['drive_current'] = {} - self.log['drive_current']['time'] = 600 - self.log['drive_current']['change'] = 1.05 - self.log['drive_current']['mean'] = [0] * self.mal - self.log['drive_current']['last_recorded_value'] = 0 - self.log['drive_current']['last_recorded_time'] = 0 - - self.log['drive_power'] = {} - self.log['drive_power']['time'] = 600 - self.log['drive_power']['change'] = 1.05 - self.log['drive_power']['mean'] = [0] * self.mal - self.log['drive_power']['last_recorded_value'] = 0 - self.log['drive_power']['last_recorded_time'] = 0 - - self.log['temp_motor'] = {} - self.log['temp_motor']['time'] = 600 - self.log['temp_motor']['change'] = 1.05 - self.log['temp_motor']['mean'] = [0] * self.mal - self.log['temp_motor']['last_recorded_value'] = 0 - self.log['temp_motor']['last_recorded_time'] = 0 - - self.log['temp_electronics'] = {} - self.log['temp_electronics']['time'] = 600 - self.log['temp_electronics']['change'] = 1.05 - self.log['temp_electronics']['mean'] = [0] * self.mal - self.log['temp_electronics']['last_recorded_value'] = 0 - self.log['temp_electronics']['last_recorded_time'] = 0 - - self.log['temp_bottom'] = {} - self.log['temp_bottom']['time'] = 600 - self.log['temp_bottom']['change'] = 1.05 - self.log['temp_bottom']['mean'] = [0] * self.mal - self.log['temp_bottom']['last_recorded_value'] = 0 - self.log['temp_bottom']['last_recorded_time'] = 0 - - self.log['temp_bearings'] = {} - self.log['temp_bearings']['time'] = 600 - self.log['temp_bearings']['change'] = 1.05 - self.log['temp_bearings']['mean'] = [0] * self.mal - self.log['temp_bearings']['last_recorded_value'] = 0 - self.log['temp_bearings']['last_recorded_time'] = 0 + self.log["rotation_speed"] = {} + self.log["rotation_speed"]["time"] = 600 + self.log["rotation_speed"]["change"] = 1.03 + self.log["rotation_speed"]["mean"] = [0] * self.mal + self.log["rotation_speed"]["last_recorded_value"] = 0 + self.log["rotation_speed"]["last_recorded_time"] = 0 + + self.log["drive_current"] = {} + self.log["drive_current"]["time"] = 600 + self.log["drive_current"]["change"] = 1.05 + self.log["drive_current"]["mean"] = [0] * self.mal + self.log["drive_current"]["last_recorded_value"] = 0 + self.log["drive_current"]["last_recorded_time"] = 0 + + self.log["drive_power"] = {} + self.log["drive_power"]["time"] = 600 + self.log["drive_power"]["change"] = 1.05 + self.log["drive_power"]["mean"] = [0] * self.mal + self.log["drive_power"]["last_recorded_value"] = 0 + self.log["drive_power"]["last_recorded_time"] = 0 + + self.log["temp_motor"] = {} + self.log["temp_motor"]["time"] = 600 + self.log["temp_motor"]["change"] = 1.05 + self.log["temp_motor"]["mean"] = [0] * self.mal + self.log["temp_motor"]["last_recorded_value"] = 0 + self.log["temp_motor"]["last_recorded_time"] = 0 + + self.log["temp_electronics"] = {} + self.log["temp_electronics"]["time"] = 600 + self.log["temp_electronics"]["change"] = 1.05 + self.log["temp_electronics"]["mean"] = [0] * self.mal + self.log["temp_electronics"]["last_recorded_value"] = 0 + self.log["temp_electronics"]["last_recorded_time"] = 0 + + self.log["temp_bottom"] = {} + self.log["temp_bottom"]["time"] = 600 + self.log["temp_bottom"]["change"] = 1.05 + self.log["temp_bottom"]["mean"] = [0] * self.mal + self.log["temp_bottom"]["last_recorded_value"] = 0 + self.log["temp_bottom"]["last_recorded_time"] = 0 + + self.log["temp_bearings"] = {} + self.log["temp_bearings"]["time"] = 600 + self.log["temp_bearings"]["change"] = 1.05 + self.log["temp_bearings"]["mean"] = [0] * self.mal + self.log["temp_bearings"]["last_recorded_value"] = 0 + self.log["temp_bearings"]["last_recorded_time"] = 0 def run(self): for i in range(0, self.mal): time.sleep(0.5) for param in self.log: - self.log[param]['mean'][i] = self.turbo.status[param] + self.log[param]["mean"][i] = self.turbo.status[param] - #Mean values now populated with meaningfull data + # Mean values now populated with meaningfull data while True: for i in range(0, self.mal): time.sleep(0.5) for param in self.log: log = self.log[param] - log['mean'][i] = self.turbo.status[param] + log["mean"][i] = self.turbo.status[param] + class TurboLogger(threading.Thread): - """ Read a specific value and determine whether it should be logged """ + """Read a specific value and determine whether it should be logged""" + def __init__(self, turboreader, parameter, maximumtime=600): threading.Thread.__init__(self) self.turboreader = turboreader @@ -180,34 +188,35 @@ def __init__(self, turboreader, parameter, maximumtime=600): self.trigged = False def read_value(self): - """ Read the value of the logger """ + """Read the value of the logger""" return self.value def run(self): while not self.quit: time.sleep(2.5) log = self.turboreader.log[self.parameter] - mean = sum(log['mean']) / float(len(log['mean'])) + mean = sum(log["mean"]) / float(len(log["mean"])) self.value = mean time_trigged = (time.time() - self.last_recorded_time) > self.maximumtime - val_trigged = not (self.last_recorded_value * 0.9 < self.value < - self.last_recorded_value * 1.1) - if (time_trigged or val_trigged): + val_trigged = not ( + self.last_recorded_value * 0.9 < self.value < self.last_recorded_value * 1.1 + ) + if time_trigged or val_trigged: self.trigged = True self.last_recorded_time = time.time() self.last_recorded_value = self.value class TurboDriver(threading.Thread): - """ The actual driver that will communicate with the pump """ + """The actual driver that will communicate with the pump""" - def __init__(self, adress=1, port='/dev/ttyUSB0'): + def __init__(self, adress=1, port="/dev/ttyUSB0"): threading.Thread.__init__(self) - with open('turbo.txt', 'w'): + with open("turbo.txt", "w"): pass logging.basicConfig(filename="turbo.txt", level=logging.INFO) - logging.info('Program started.') + logging.info("Program started.") logging.basicConfig(level=logging.INFO) self.serial = serial.Serial(port, 9600) @@ -215,30 +224,30 @@ def __init__(self, adress=1, port='/dev/ttyUSB0'): self.serial.timeout = 0.1 self.adress = adress self.status = {} # Hold parameters to be accessible by gui - self.status['starttime'] = time.time() - self.status['runtime'] = 0 - self.status['rotation_speed'] = 0 - self.status['set_rotation_speed'] = 0 - self.status['operating_hours'] = 0 - self.status['pump_accelerating'] = False - self.status['gas_mode'] = '' - self.status['vent_mode'] = '' - self.status['A1'] = '' - self.status['vent_freq'] = 50 - self.status['vent_time'] = 0 - self.status['sealing_gas'] = '' - self.status['drive_current'] = 0 - self.status['drive_power'] = 0 - self.status['temp_electronics'] = 0 - self.status['temp_bottom'] = 0 - self.status['temp_bearings'] = 0 - self.status['temp_motor'] = 0 - self.status['spin_down'] = False - self.status['spin_up'] = False + self.status["starttime"] = time.time() + self.status["runtime"] = 0 + self.status["rotation_speed"] = 0 + self.status["set_rotation_speed"] = 0 + self.status["operating_hours"] = 0 + self.status["pump_accelerating"] = False + self.status["gas_mode"] = "" + self.status["vent_mode"] = "" + self.status["A1"] = "" + self.status["vent_freq"] = 50 + self.status["vent_time"] = 0 + self.status["sealing_gas"] = "" + self.status["drive_current"] = 0 + self.status["drive_power"] = 0 + self.status["temp_electronics"] = 0 + self.status["temp_bottom"] = 0 + self.status["temp_bearings"] = 0 + self.status["temp_motor"] = 0 + self.status["spin_down"] = False + self.status["spin_up"] = False self.running = True def comm(self, command, read=True): - """ Implementaion of the communication protocol with the pump. + """Implementaion of the communication protocol with the pump. The function deals with common syntax need for all commands. :param command: The command to send to the pump @@ -251,30 +260,30 @@ def comm(self, command, read=True): adress_string = str(self.adress).zfill(3) if read: - action = '00' - datatype = '=?' + action = "00" + datatype = "=?" length = str(len(datatype)).zfill(2) command = action + command + length + datatype crc = self.crc_calc(adress_string + command) - command = adress_string + command + crc + '\r' + command = adress_string + command + crc + "\r" LOGGER.debug(command) - self.serial.write(command.encode('ascii')) + self.serial.write(command.encode("ascii")) response = self.serial.readline() try: length = int(response[8:10]) - reply = response[10:10 + length] - crc = response[10 + length:10 + length + 3] + reply = response[10 : 10 + length] + crc = response[10 + length : 10 + length + 3] except ValueError: - logging.warn('Value error, unreadable reply') + logging.warn("Value error, unreadable reply") reply = -1 # TODO: Implement real crc check except serial.SerialException: - logging.warn('Serial connection problem') + logging.warn("Serial connection problem") reply = -1 return reply - + def crc_calc(self, command): - """ Helper function to calculate crc for commands + """Helper function to calculate crc for commands :param command: The command for which to calculate crc :type command: str :return: The crc value @@ -288,96 +297,94 @@ def crc_calc(self, command): return crc_string def read_rotation_speed(self): - """ Read the rotational speed of the pump + """Read the rotational speed of the pump :return: The rotaional speed in Hz :rtype: Float """ - command = '398' + command = "398" reply = self.comm(command, True) val = int(reply) / 60.0 return val def read_set_rotation_speed(self): - """ Read the intended rotational speed of the pump + """Read the intended rotational speed of the pump :return: The intended rotaional speed in Hz :rtype: Int """ - command = '308' + command = "308" reply = self.comm(command, True) val = int(reply) return val def read_operating_hours(self): - """ Read the number of operating hours + """Read the number of operating hours :return: Number of operating hours :rtype: Int """ - command = '311' + command = "311" reply = self.comm(command, True) val = int(reply) return val def read_gas_mode(self): - """ Read the gas mode + """Read the gas mode :return: The gas mode :rtype: Str """ - command = '027' + command = "027" reply = self.comm(command, True) mode = int(reply) - mode_string = '' + mode_string = "" if mode == 0: - mode_string = 'Heavy gasses' + mode_string = "Heavy gasses" if mode == 1: - mode_string = 'Light gasses' + mode_string = "Light gasses" if mode == 2: - mode_string = 'Helium' + mode_string = "Helium" return mode_string def read_vent_mode(self): - """ Read the venting mode + """Read the venting mode :return: The venting mode :rtype: Str """ - command = '030' + command = "030" reply = self.comm(command, True) mode = int(reply) - mode_string = '' + mode_string = "" if mode == 0: - mode_string = 'Delayed Venting' + mode_string = "Delayed Venting" if mode == 1: - mode_string = 'No Venting' + mode_string = "No Venting" if mode == 2: - mode_string = 'Direct Venting' + mode_string = "Direct Venting" return mode_string - + def read_vent_rotation(self): - """ Adjust the rotation speed below which + """Adjust the rotation speed below which the turbo starts venting """ - command = '720' + command = "720" reply = self.comm(command, True) val = int(reply) return val def read_vent_time(self): - """ Read the time the venting valve is kept open - """ - command = '721' + """Read the time the venting valve is kept open""" + command = "721" reply = self.comm(command, True) - val = int(reply)/60.0 + val = int(reply) / 60.0 return val - + def read_acc_a1(self): - """ Read the status of accessory A1 - """ - command = '035' + """Read the status of accessory A1""" + command = "035" reply = self.comm(command, True) mode = int(reply) - mode_string = '' + mode_string = "" if mode == 0: mode_string = "Fan (continous)" elif mode == 1: @@ -387,138 +394,137 @@ def read_acc_a1(self): else: mode_string = "Mode is: " + str(mode) return mode_string - + def read_sealing_gas(self): - """ Read whether sealing gas is applied + """Read whether sealing gas is applied :return: The sealing gas mode :rtype: Str """ - command = '050' + command = "050" reply = self.comm(command, True) mode = int(reply) - mode_string = '' + mode_string = "" if mode == 0: - mode_string = 'No sealing gas' + mode_string = "No sealing gas" if mode == 1: - mode_string = 'Sealing gas on' + mode_string = "Sealing gas on" return mode_string def is_pump_accelerating(self): - """ Read if pump is accelerating + """Read if pump is accelerating :return: True if pump is accelerating, false if not :rtype: Boolean """ - command = '307' + command = "307" reply = self.comm(command, True) return int(reply) == 1 def turn_pump_on(self, off=False): - """ Spin the pump up or down + """Spin the pump up or down :param off: If True the pump will spin down :type off: Boolean :return: Always returns True :rtype: Boolean """ if not off: - command = '1001006111111' + command = "1001006111111" else: - command = '1001006000000' + command = "1001006000000" self.comm(command, False) return True def read_temperature(self): - """ Read the various measured temperatures of the pump + """Read the various measured temperatures of the pump :return: Dictionary with temperatures :rtype: Dict """ - command = '326' + command = "326" reply = self.comm(command, True) elec = int(reply) - command = '330' + command = "330" reply = self.comm(command, True) bottom = int(reply) - command = '342' + command = "342" reply = self.comm(command, True) bearings = int(reply) - command = '346' + command = "346" reply = self.comm(command, True) motor = int(reply) return_val = {} - return_val['elec'] = elec - return_val['bottom'] = bottom - return_val['bearings'] = bearings - return_val['motor'] = motor + return_val["elec"] = elec + return_val["bottom"] = bottom + return_val["bearings"] = bearings + return_val["motor"] = motor return return_val def read_drive_power(self): - """ Read the current power consumption of the pump + """Read the current power consumption of the pump :return: Dictionary containing voltage, current and power :rtype: Dict """ - command = '310' + command = "310" reply = self.comm(command, True) - current = int(reply)/100.0 + current = int(reply) / 100.0 - command = '313' + command = "313" reply = self.comm(command, True) - voltage = int(reply)/100.0 + voltage = int(reply) / 100.0 - command = '316' + command = "316" reply = self.comm(command, True) power = int(reply) return_val = {} - return_val['voltage'] = voltage - return_val['current'] = current - return_val['power'] = power + return_val["voltage"] = voltage + return_val["current"] = current + return_val["power"] = power return return_val def run(self): round_robin_counter = 0 while self.running: - #time.sleep(0.1) - self.status['runtime'] = time.time() - self.status['starttime'] - self.status['rotation_speed'] = self.read_rotation_speed() + # time.sleep(0.1) + self.status["runtime"] = time.time() - self.status["starttime"] + self.status["rotation_speed"] = self.read_rotation_speed() power = self.read_drive_power() - self.status['drive_current'] = power['current'] - self.status['drive_power'] = power['power'] + self.status["drive_current"] = power["current"] + self.status["drive_power"] = power["power"] if round_robin_counter == 0: temp = self.read_temperature() - self.status['temp_electronics'] = temp['elec'] - self.status['temp_bottom'] = temp['bottom'] - self.status['temp_bearings'] = temp['bearings'] - self.status['temp_motor'] = temp['motor'] + self.status["temp_electronics"] = temp["elec"] + self.status["temp_bottom"] = temp["bottom"] + self.status["temp_bearings"] = temp["bearings"] + self.status["temp_motor"] = temp["motor"] if round_robin_counter == 1: - self.status['pump_accelerating'] = self.is_pump_accelerating() - self.status['set_rotation_speed'] = self.read_set_rotation_speed() - self.status['gas_mode'] = self.read_gas_mode() - self.status['vent_mode'] = self.read_vent_mode() - self.status['A1'] = self.read_acc_a1() - self.status['vent_freq'] = self.read_vent_rotation() - self.status['vent_time'] = self.read_vent_time() - self.status['sealing_gas'] = self.read_sealing_gas() - self.status['operating_hours'] = self.read_operating_hours() + self.status["pump_accelerating"] = self.is_pump_accelerating() + self.status["set_rotation_speed"] = self.read_set_rotation_speed() + self.status["gas_mode"] = self.read_gas_mode() + self.status["vent_mode"] = self.read_vent_mode() + self.status["A1"] = self.read_acc_a1() + self.status["vent_freq"] = self.read_vent_rotation() + self.status["vent_time"] = self.read_vent_time() + self.status["sealing_gas"] = self.read_sealing_gas() + self.status["operating_hours"] = self.read_operating_hours() round_robin_counter += 1 round_robin_counter = round_robin_counter % 2 - if self.status['spin_up']: + if self.status["spin_up"]: self.turn_pump_on() - self.status['spin_up'] = False - if self.status['spin_down']: + self.status["spin_up"] = False + if self.status["spin_down"]: self.turn_pump_on(off=True) - self.status['spin_down'] = False - + self.status["spin_down"] = False -if __name__ == '__main__': +if __name__ == "__main__": # Initialize communication with the turbo TURBO = TurboDriver() TURBO.start() diff --git a/PyExpLabSys/drivers/polyscience_4100.py b/PyExpLabSys/drivers/polyscience_4100.py index 37564c8b..081ca541 100644 --- a/PyExpLabSys/drivers/polyscience_4100.py +++ b/PyExpLabSys/drivers/polyscience_4100.py @@ -2,127 +2,131 @@ from __future__ import print_function import serial from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class Polyscience4100(object): - """ Driver for Polyscience 4100 chiller """ - def __init__(self, port='/dev/ttyUSB0'): + """Driver for Polyscience 4100 chiller""" + + def __init__(self, port="/dev/ttyUSB0"): self.ser = serial.Serial(port, 9600, timeout=0.5) self.max_setpoint = 30 self.min_setpoint = 10 assert self.min_setpoint < self.max_setpoint def comm(self, command): - """ Send serial commands to the instrument """ - command = command + '\r' - command = command.encode('ascii') + """Send serial commands to the instrument""" + command = command + "\r" + command = command.encode("ascii") self.ser.write(command) reply = self.ser.readline().decode() return reply[:-1] def set_setpoint(self, value): - """ Set the temperature setpoint """ + """Set the temperature setpoint""" if value > self.max_setpoint: value = self.max_setpoint if value < self.min_setpoint: value = self.min_setpoint - string = '{0:.0f}'.format(value) + string = "{0:.0f}".format(value) if len(string) == 1: - string = '00' + string + string = "00" + string else: - string = '0' + string + string = "0" + string assert len(string) == 3 - value = self.comm('SS' + string) - success = (value == '!') + value = self.comm("SS" + string) + success = value == "!" return success def turn_unit_on(self, turn_on): - """ Turn on or off the unit """ + """Turn on or off the unit""" if turn_on is True: - value = self.comm('SO1') + value = self.comm("SO1") if turn_on is False: - value = self.comm('SO0') + value = self.comm("SO0") return value def read_setpoint(self): - """ Read the current value of the setpoint """ + """Read the current value of the setpoint""" try: - value = float(self.comm('RS')) + value = float(self.comm("RS")) except ValueError: - value = float('NaN') + value = float("NaN") return float(value) def read_unit(self): - """ Read the measure unit """ - value = self.comm('RU') + """Read the measure unit""" + value = self.comm("RU") return value def read_temperature(self): - """ Read the actual temperature of the water """ + """Read the actual temperature of the water""" try: - status = self.comm('RW') - if status == '1': - value = float(self.comm('RT')) + status = self.comm("RW") + if status == "1": + value = float(self.comm("RT")) else: - value = float('nan') + value = float("nan") except ValueError: - value = float('nan') + value = float("nan") return value def read_pressure(self): - """ Read the output pressure """ + """Read the output pressure""" try: - status = self.comm('RW') - if status == '1': - value = float(self.comm('RK')) / 100.0 + status = self.comm("RW") + if status == "1": + value = float(self.comm("RK")) / 100.0 else: - value = float('nan') + value = float("nan") except ValueError: - value = float('nan') + value = float("nan") return value def read_flow_rate(self): - """ Read the flow rate """ + """Read the flow rate""" try: - status = self.comm('RW') - if status == '1': - value = float(self.comm('RL')) + status = self.comm("RW") + if status == "1": + value = float(self.comm("RL")) else: - value = float('nan') + value = float("nan") except ValueError: - value = float('nan') + value = float("nan") return value def read_ambient_temperature(self): - """ Read the ambient temperature in the device """ + """Read the ambient temperature in the device""" try: - status = self.comm('RW') - if status == '1': - value = float(self.comm('RA')) + status = self.comm("RW") + if status == "1": + value = float(self.comm("RA")) else: - value = float('nan') + value = float("nan") except ValueError: - value = float('nan') + value = float("nan") return value def read_status(self): - """ Answers if the device is turned on """ - value = self.comm('RW') - status = 'error' - if value == '0': - status = 'Off' - if value == '1': - status = 'On' + """Answers if the device is turned on""" + value = self.comm("RW") + status = "error" + if value == "0": + status = "Off" + if value == "1": + status = "On" return status -if __name__ == '__main__': - CHILLER = Polyscience4100('/dev/ttyUSB0') + +if __name__ == "__main__": + CHILLER = Polyscience4100("/dev/ttyUSB0") print(CHILLER.read_status()) - print('Setpoint: {0:.1f}'.format(CHILLER.read_setpoint())) - print('Temperature: {0:.1f}'.format(CHILLER.read_temperature())) - print('Flow rate: {0:.2f}'.format(CHILLER.read_flow_rate())) - print('Pressure: {0:.3f}'.format(CHILLER.read_pressure())) - print('Status: ' + CHILLER.read_status()) - print('Ambient temperature: {0:.2f}'.format(CHILLER.read_ambient_temperature())) + print("Setpoint: {0:.1f}".format(CHILLER.read_setpoint())) + print("Temperature: {0:.1f}".format(CHILLER.read_temperature())) + print("Flow rate: {0:.2f}".format(CHILLER.read_flow_rate())) + print("Pressure: {0:.3f}".format(CHILLER.read_pressure())) + print("Status: " + CHILLER.read_status()) + print("Ambient temperature: {0:.2f}".format(CHILLER.read_ambient_temperature())) diff --git a/PyExpLabSys/drivers/powerwalker.py b/PyExpLabSys/drivers/powerwalker.py index c7eb7eb7..323e6f97 100644 --- a/PyExpLabSys/drivers/powerwalker.py +++ b/PyExpLabSys/drivers/powerwalker.py @@ -10,26 +10,27 @@ class PowerWalker(object): https://networkupstools.org/protocols/megatec.html Apparantly, for the available model, none of the control works. """ + def comm(self, command, start_byte): raise NotImplementedError def device_information(self): - reply = self.comm('I', '#') + reply = self.comm("I", "#") information = { - 'company': reply[0:15].strip(), - 'model': reply[16:26].strip(), - 'version': reply[27:].strip() + "company": reply[0:15].strip(), + "model": reply[16:26].strip(), + "version": reply[27:].strip(), } return information def device_ratings(self): - reply = self.comm('F', '#') - values = reply.split(' ') + reply = self.comm("F", "#") + values = reply.split(" ") ratings = { - 'rated_voltage': float(values[0]), - 'rated_current': float(values[1]), - 'battery_voltage': float(values[2]), - 'rated_frequency': float(values[3]), + "rated_voltage": float(values[0]), + "rated_current": float(values[1]), + "battery_voltage": float(values[2]), + "rated_frequency": float(values[3]), } return ratings @@ -37,56 +38,56 @@ def device_status(self): values = [] errors = 0 while -1 < errors < 10: - reply = self.comm('Q1', '(') + reply = self.comm("Q1", "(") # print('Reply: {}'.format(reply)) - values = reply.split(' ') + values = reply.split(" ") try: - assert(len(values) == 8) - assert(len(values[7]) == 8) + assert len(values) == 8 + assert len(values[7]) == 8 errors = -1 except AssertionError: errors += 1 time.sleep(0.01) # print(values) if errors > 0: - raise Exception('Unable to get status from UPS!') + raise Exception("Unable to get status from UPS!") # bat_volt_string = values[5] # For on-line units battery voltage/cell is provided in the form S.SS. # For standby units actual battery voltage is provided in the form SS.S. # UPS type in UPS status will determine which reading was obtained. ups_status = [] status_description = { # Todo, add e - 0: 'Beeper On', - 1: 'Shutdown Active', - 2: 'Test in Progress', - 3: 'Standby', # Otherwise: Online - 4: 'UPS Failed', - 5: 'Bypass/Boost or Buck Active', - 6: 'Battery Low', - 7: 'Utility Fail' + 0: "Beeper On", + 1: "Shutdown Active", + 2: "Test in Progress", + 3: "Standby", # Otherwise: Online + 4: "UPS Failed", + 5: "Bypass/Boost or Buck Active", + 6: "Battery Low", + 7: "Utility Fail", } status_string = values[7] for i in range(0, 8): - bit_value = (status_string[-1 - i] == '1') + bit_value = status_string[-1 - i] == "1" if bit_value: ups_status.append(status_description[i]) status = { - 'input_voltage': float(values[0]), - 'input_fault_voltage': float(values[1]), - 'output_voltage': float(values[2]), - 'output_current': float(values[3]) / 10, - 'input_frequency': float(values[4]), - 'battery_voltage': float(values[5]), - 'temperature': float(values[6]), - 'status': ups_status, + "input_voltage": float(values[0]), + "input_fault_voltage": float(values[1]), + "output_voltage": float(values[2]), + "output_current": float(values[3]) / 10, + "input_frequency": float(values[4]), + "battery_voltage": float(values[5]), + "temperature": float(values[6]), + "status": ups_status, } return status class PowerWalkerUsb(PowerWalker): - def __init__(self, port='/dev/ttyUSB0'): + def __init__(self, port="/dev/ttyUSB0"): # USB reverse engineering by # allican.be/blog/2017/01/28/reverse-engineering-cypress-serial-usb.html vendorId = 0x0665 @@ -98,12 +99,12 @@ def __init__(self, port='/dev/ttyUSB0'): self.dev.set_interface_altsetting(0, 0) def getCommand(self, cmd): - cmd = cmd.encode('utf-8') - crc = crc16.crc16xmodem(cmd).to_bytes(2, 'big') + cmd = cmd.encode("utf-8") + crc = crc16.crc16xmodem(cmd).to_bytes(2, "big") cmd = cmd + crc - cmd = cmd + b'\r' + cmd = cmd + b"\r" while len(cmd) < 8: - cmd = cmd + b'\0' + cmd = cmd + b"\0" return cmd def sendCommand(self, cmd): @@ -112,11 +113,9 @@ def sendCommand(self, cmd): def getResult(self, timeout=100): res = "" i = 0 - while '\r' not in res and i < 20: + while "\r" not in res and i < 20: try: - res += "".join( - [chr(i) for i in self.dev.read(0x81, 8, timeout) if i != 0x00] - ) + res += "".join([chr(i) for i in self.dev.read(0x81, 8, timeout) if i != 0x00]) except usb.core.USBError as e: if e.errno == 110: pass @@ -126,32 +125,32 @@ def getResult(self, timeout=100): return res def comm(self, command, start_byte): - self.sendCommand(self.getCommand(command + '\r')) + self.sendCommand(self.getCommand(command + "\r")) res = self.getResult() reply = res[1:-1] return reply class PowerWalkerSerial(PowerWalker): - def __init__(self, port='/dev/ttyUSB0'): + def __init__(self, port="/dev/ttyUSB0"): self.serial = serial.Serial( port=port, baudrate=2400, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, - timeout=1 + timeout=1, ) def comm(self, command, start_byte): error = 0 while -1 < error < 10: - command = command + '\r' + command = command + "\r" self.serial.write(command.encode()) reply_raw = self.serial.readline() reply = reply_raw.decode() - if not (reply[0] == start_byte and reply[-1] == '\r'): + if not (reply[0] == start_byte and reply[-1] == "\r"): self.serial.flush() time.sleep(0.25) error += 1 @@ -159,13 +158,13 @@ def comm(self, command, start_byte): error = -1 if error > -1: - raise RuntimeError('Communication error with UPS') + raise RuntimeError("Communication error with UPS") reply = reply[1:-1] return reply -if __name__ == '__main__': +if __name__ == "__main__": pw = PowerWalkerSerial() # pw = PowerWalkerUsb() print(pw.device_status()) diff --git a/PyExpLabSys/drivers/powerwalker_ethernet.py b/PyExpLabSys/drivers/powerwalker_ethernet.py index 2b06fea6..7bffdaaf 100644 --- a/PyExpLabSys/drivers/powerwalker_ethernet.py +++ b/PyExpLabSys/drivers/powerwalker_ethernet.py @@ -9,6 +9,7 @@ class PowerWalkerEthernet(object): values. SNMP could also be used, but apparently most values miss a digit compared to the internal tools. """ + def __init__(self, ip_address, read_old_events=True): if read_old_events: self.latest_event = datetime.datetime.min @@ -16,8 +17,7 @@ def __init__(self, ip_address, read_old_events=True): self.latest_event = datetime.datetime.now() self.ssh = paramiko.SSHClient() self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - self.ssh.connect(ip_address, username='root', - password='12345678', look_for_keys=False) + self.ssh.connect(ip_address, username="root", password="12345678", look_for_keys=False) def _read_static_data(self): """ @@ -25,7 +25,7 @@ def _read_static_data(self): traditionally returned as two seperate calls, thus this is considered a private function. """ - command = '/var/www/html/web_pages_Galleon/cgi-bin/baseInfo.cgi' + command = "/var/www/html/web_pages_Galleon/cgi-bin/baseInfo.cgi" stdin, stdout, stderr = self.ssh.exec_command(command, timeout=0.75) raw_lines = stdout.readlines() @@ -37,38 +37,38 @@ def _read_static_data(self): nominal_input = int(lines[4][0:3]) nominal_output = int(lines[4][4:]) values = { - 'model': lines[2], - 'version': lines[6], - 'nominal_input_voltage': nominal_input, - 'nominal_output_voltage': nominal_output, - 'nominal_output_frequency': int(lines[10]) / 10.0, - 'rated_battery_voltage': int(lines[12]) / 10.0, - 'rated_va': int(lines[8]), - 'rated_output_current': int(lines[11]) / 10.0 + "model": lines[2], + "version": lines[6], + "nominal_input_voltage": nominal_input, + "nominal_output_voltage": nominal_output, + "nominal_output_frequency": int(lines[10]) / 10.0, + "rated_battery_voltage": int(lines[12]) / 10.0, + "rated_va": int(lines[8]), + "rated_output_current": int(lines[11]) / 10.0, } return values def device_information(self): statics = self._read_static_data() information = { - 'company': 'Power Walker', - 'model': statics['model'], - 'version': statics['version'] + "company": "Power Walker", + "model": statics["model"], + "version": statics["version"], } return information def device_ratings(self): statics = self._read_static_data() ratings = { - 'rated_voltage': statics['nominal_output_voltage'], - 'rated_current': statics['rated_output_current'], - 'battery_voltage': statics['rated_battery_voltage'], - 'rated_frequency': statics['nominal_output_frequency'] + "rated_voltage": statics["nominal_output_voltage"], + "rated_current": statics["rated_output_current"], + "battery_voltage": statics["rated_battery_voltage"], + "rated_frequency": statics["nominal_output_frequency"], } return ratings def device_status(self): - command = '/var/www/html/web_pages_Galleon/cgi-bin/realInfo.cgi' + command = "/var/www/html/web_pages_Galleon/cgi-bin/realInfo.cgi" stdin, stdout, stderr = self.ssh.exec_command(command, timeout=0.75) raw_lines = stdout.readlines() @@ -77,22 +77,22 @@ def device_status(self): if line.strip(): lines.append(line.strip()) - status = [] # TODO! - if not lines[1] == 'Line Mode': - status.append('Utility Fail') # Compatibility with serial interface + status = [] # TODO! + if not lines[1] == "Line Mode": + status.append("Utility Fail") # Compatibility with serial interface values = { - 'input_voltage': int(lines[12]) / 10.0, - 'output_voltage': int(lines[15]) / 10.0, - 'output_current': int(lines[35]) / 10.0, - 'input_frequency': int(lines[11]) / 10.0, - 'battery_voltage': int(lines[8]) / 10.0, - 'temperature': int(lines[2]) / 10.0, - 'status': status, - 'battery_capacity': int(lines[9]), - 'remaining_battery': lines[10], # minutes - 'output_frequency': int(lines[14]) / 10.0, - 'load_level': int(lines[17]) + "input_voltage": int(lines[12]) / 10.0, + "output_voltage": int(lines[15]) / 10.0, + "output_current": int(lines[35]) / 10.0, + "input_frequency": int(lines[11]) / 10.0, + "battery_voltage": int(lines[8]) / 10.0, + "temperature": int(lines[2]) / 10.0, + "status": status, + "battery_capacity": int(lines[9]), + "remaining_battery": lines[10], # minutes + "output_frequency": int(lines[14]) / 10.0, + "load_level": int(lines[17]), } # WARNING (appears in web front-end - find how to read) # FAULT (appears in web front-end - find how to read) @@ -104,27 +104,27 @@ def read_events(self, only_new=False): raw_lines = stdout.readlines() if len(raw_lines) < 2: - print('PowerWalker Ethernet: Too few lines in event file') + print("PowerWalker Ethernet: Too few lines in event file") return None events = [] for line in raw_lines[1:]: - split_line = line.strip().split(',') - timestamp = datetime.datetime.strptime(split_line[0], - '%Y/%m/%d %H:%M:%S') + split_line = line.strip().split(",") + timestamp = datetime.datetime.strptime(split_line[0], "%Y/%m/%d %H:%M:%S") if only_new and timestamp <= self.latest_event: continue event = { - 'timestamp': timestamp, - 'event': split_line[1], - 'source': split_line[2] + "timestamp": timestamp, + "event": split_line[1], + "source": split_line[2], } events.append(event) self.latest_event = timestamp return events -if __name__ == '__main__': - pw = PowerWalkerEthernet(ip_address='192.168.2.100') + +if __name__ == "__main__": + pw = PowerWalkerEthernet(ip_address="192.168.2.100") print(pw.device_status()) print() diff --git a/PyExpLabSys/drivers/rosemount_nga2000.py b/PyExpLabSys/drivers/rosemount_nga2000.py index 7f758bda..7f22e402 100644 --- a/PyExpLabSys/drivers/rosemount_nga2000.py +++ b/PyExpLabSys/drivers/rosemount_nga2000.py @@ -1,81 +1,83 @@ import serial import time -class AK_comm(): - def __init__(self,port): - self.f = serial.Serial(port,9600,xonxoff=True,timeout=2) +class AK_comm: + def __init__(self, port): + self.f = serial.Serial(port, 9600, xonxoff=True, timeout=2) time.sleep(0.1) - def comm(self,command): + def comm(self, command): pre_string = chr(2) + chr(32) end_string = chr(3) self.f.write(pre_string + command + end_string) time.sleep(0.2) return_string = self.f.read(self.f.inWaiting()) - #The first two and the last character is not part of the message - #print ord(return_string[-1]) #Check that last character is chr(3) + # The first two and the last character is not part of the message + # print ord(return_string[-1]) #Check that last character is chr(3) try: return_string = return_string[2:] return_string = return_string[:-1] - error_byte = return_string[5] #Check error byte! - return_string = return_string[7:] #The first part of the message is an echo of the command + error_byte = return_string[5] # Check error byte! + return_string = return_string[ + 7: + ] # The first part of the message is an echo of the command except IndexError: return_string = "Serial Error" - #Here we should properly raise a home-made error + # Here we should properly raise a home-made error return return_string - + def IdentifyDevice(self): - command = 'AGID K0' + command = "AGID K0" id = self.comm(command) - return_string = '' - try: - dev_id = id.split('/') + return_string = "" + try: + dev_id = id.split("/") return_string += "Name and s/n: " + dev_id[0] + "\n" return_string += "Program version: " + dev_id[1] + "\n" return_string += "Data: " + dev_id[2] + "\n" except: - return_string = 'Error' + return_string = "Error" return return_string - + def ReadConcentration(self): - command = 'AKON K1' + command = "AKON K1" signal = self.comm(command) - #print "Signal: " + signal + # print "Signal: " + signal if signal[0] == "#": signal = signal[1:] signal = signal.strip() signal = signal.strip(chr(3)) - return(float(signal)) + return float(signal) def ReadTemperature(self): - command = 'ATEM K1' + command = "ATEM K1" signal = self.comm(command) - return(float(signal)-273.15) + return float(signal) - 273.15 def ReadUncorrelatedAnalogValue(self): - command = 'AUKA K1' + command = "AUKA K1" signal = self.comm(command) - #print "Signal: " + signal - #range = int(signal[1]) + # print "Signal: " + signal + # range = int(signal[1]) sensor_output = signal[3:] - #for i in range(0, len(sensor_output)): + # for i in range(0, len(sensor_output)): # print ord(sensor_output[i]) sensor_output = sensor_output.strip() sensor_output = sensor_output.strip(chr(3)) - #print "Sensor output: " + sensor_output - return(int(float(sensor_output))) + # print "Sensor output: " + sensor_output + return int(float(sensor_output)) def ReadOperationalHours(self): - command = 'ABST K1' + command = "ABST K1" signal = self.comm(command) - return(signal) + return signal -if __name__ == '__main__': - AK = AK_comm('/dev/ttyUSB0') +if __name__ == "__main__": + AK = AK_comm("/dev/ttyUSB0") print("Concentration: " + str(AK.ReadConcentration())) print("Temperature: " + str(AK.ReadTemperature())) diff --git a/PyExpLabSys/drivers/scpi.py b/PyExpLabSys/drivers/scpi.py index 1a47fbd4..72c1c358 100644 --- a/PyExpLabSys/drivers/scpi.py +++ b/PyExpLabSys/drivers/scpi.py @@ -4,11 +4,13 @@ import logging import telnetlib import serial + try: import usbtmc except ImportError: usbtmc = None from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) LOGGER = logging.getLogger(__name__) @@ -17,90 +19,101 @@ class SCPI(object): - """ Driver for scpi communication """ - def __init__(self, interface, device='', tcp_port=5025, hostname='', baudrate=9600, - visa_string='', line_ending='\r'): + """Driver for scpi communication""" + + def __init__( + self, + interface, + device="", + tcp_port=5025, + hostname="", + baudrate=9600, + visa_string="", + line_ending="\r", + ): self.device = device self.line_ending = line_ending self.interface = interface - if self.interface == 'file': - self.comm_dev = open(self.device, 'w') + if self.interface == "file": + self.comm_dev = open(self.device, "w") self.comm_dev.close() - if self.interface == 'serial': + if self.interface == "serial": self.comm_dev = serial.Serial(self.device, baudrate, timeout=2, xonxoff=True) - if self.interface == 'lan': + if self.interface == "lan": self.comm_dev = telnetlib.Telnet(hostname, tcp_port) - if self.interface == 'usbtmc': + if self.interface == "usbtmc": if usbtmc is None: - exit('usbtmc is not availalbe') + exit("usbtmc is not availalbe") self.comm_dev = usbtmc.Instrument(visa_string) - def scpi_comm(self, command, expect_return=False): - """ Implements actual communication with SCPI instrument """ + """Implements actual communication with SCPI instrument""" return_string = "" - if self.interface == 'file': - self.comm_dev = open(self.device, 'w') + if self.interface == "file": + self.comm_dev = open(self.device, "w") self.comm_dev.write(command) time.sleep(0.02) self.comm_dev.close() time.sleep(0.05) - if command.find('?') > -1: - self.comm_dev = open(self.device, 'r') + if command.find("?") > -1: + self.comm_dev = open(self.device, "r") return_string = self.comm_dev.readline() self.comm_dev.close() command_text = command + self.line_ending - if self.interface == 'serial': - self.comm_dev.write(command_text.encode('ascii')) - if command.endswith('?') or (expect_return is True): - return_string = ''.encode('ascii') + if self.interface == "serial": + self.comm_dev.write(command_text.encode("ascii")) + if command.endswith("?") or (expect_return is True): + return_string = "".encode("ascii") while True: next_char = self.comm_dev.read(1) - #print(ord(next_char)) - #print(ord(self.line_ending)) + # print(ord(next_char)) + # print(ord(self.line_ending)) if ord(next_char) == ord(self.line_ending): break return_string += next_char return_string = return_string.decode() - if self.interface == 'lan': + if self.interface == "lan": lan_time = time.time() - self.comm_dev.write(command_text.encode('ascii')) - if (command.find('?') > -1) or (expect_return is True): - return_string = self.comm_dev.read_until(chr(10).encode('ascii'), - 2).decode() - LOGGER.info('Return string length: ' + str(len(return_string))) - #time.sleep(0.025) - LOGGER.info('lan_time for coomand ' + command_text.strip() + - ': ' + str(time.time() - lan_time)) + self.comm_dev.write(command_text.encode("ascii")) + if (command.find("?") > -1) or (expect_return is True): + return_string = self.comm_dev.read_until(chr(10).encode("ascii"), 2).decode() + LOGGER.info("Return string length: " + str(len(return_string))) + # time.sleep(0.025) + LOGGER.info( + "lan_time for coomand " + + command_text.strip() + + ": " + + str(time.time() - lan_time) + ) - if self.interface == 'usbtmc': - if command.find('?') > -1: + if self.interface == "usbtmc": + if command.find("?") > -1: return_string = self.comm_dev.ask(command_text) else: self.comm_dev.write(command_text) - return_string = 'command_text' + return_string = "command_text" return return_string def read_software_version(self): - """ Read version string from device """ + """Read version string from device""" version_string = self.scpi_comm("*IDN?") version_string = version_string.strip() return version_string def reset_device(self): - """ Rest device """ + """Rest device""" self.scpi_comm("*RST") return True def device_clear(self): - """ Stop current operation """ + """Stop current operation""" self.scpi_comm("*abort") return True def clear_error_queue(self): - """ Clear error queue """ + """Clear error queue""" error = self.scpi_comm("*ESR?") self.scpi_comm("*cls") return error diff --git a/PyExpLabSys/drivers/seeed_studio_relay.py b/PyExpLabSys/drivers/seeed_studio_relay.py index fee42654..e57b20e3 100644 --- a/PyExpLabSys/drivers/seeed_studio_relay.py +++ b/PyExpLabSys/drivers/seeed_studio_relay.py @@ -3,40 +3,49 @@ bus = smbus.SMBus(1) -class i2c_relay(): + +class i2c_relay: global bus def __init__(self): self.address = 0x20 self.reg_mode = 0x06 - self.reg_data = 0xff + self.reg_data = 0xFF self.write_bus() def write_bus(self): bus.write_byte_data(self.address, self.reg_mode, self.reg_data) - def ON(self,num,echo=False): - if num not in [1,2,3,4]: - raise ValueError('No relay port numbered ', num, ' available. Please specify a number from 1 to 4.') + def ON(self, num, echo=False): + if num not in [1, 2, 3, 4]: + raise ValueError( + "No relay port numbered ", + num, + " available. Please specify a number from 1 to 4.", + ) if echo: - print('Turning relay', num, 'ON') - self.reg_data &= ~(0x1 << (num-1)) + print("Turning relay", num, "ON") + self.reg_data &= ~(0x1 << (num - 1)) self.write_bus() - - def OFF(self,num,echo=False): - if num not in [1,2,3,4]: - raise ValueError('No relay port numbered ', num, ' available. Please specify a number from 1 to 4.') + + def OFF(self, num, echo=False): + if num not in [1, 2, 3, 4]: + raise ValueError( + "No relay port numbered ", + num, + " available. Please specify a number from 1 to 4.", + ) if echo: - print('Turning relay', num, 'OFF') - self.reg_data |= (0x1 << (num-1)) + print("Turning relay", num, "OFF") + self.reg_data |= 0x1 << (num - 1) self.write_bus() -if __name__ == '__main__': + +if __name__ == "__main__": relay = i2c_relay() - for i in [1,2,3,4]: + for i in [1, 2, 3, 4]: relay.ON(i) time.sleep(0.5) - for i in [1,2,3,4]: + for i in [1, 2, 3, 4]: relay.OFF(i) time.sleep(0.5) - \ No newline at end of file diff --git a/PyExpLabSys/drivers/sensirion_sps30.py b/PyExpLabSys/drivers/sensirion_sps30.py index 305fd5c7..8aad25de 100644 --- a/PyExpLabSys/drivers/sensirion_sps30.py +++ b/PyExpLabSys/drivers/sensirion_sps30.py @@ -5,16 +5,17 @@ import logging -class SensirionSPS30(): +class SensirionSPS30: """Driver for r""" - def __init__(self, port='/dev/serial0'): + + def __init__(self, port="/dev/serial0"): self.serial = serial.Serial( port, baudrate=115200, timeout=1, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, - bytesize=serial.EIGHTBITS + bytesize=serial.EIGHTBITS, ) def _calculate_checksum(self, command_bytes): @@ -30,22 +31,18 @@ def _calculate_checksum(self, command_bytes): def comm(self, command, data=[]): read_success = True length = len(data) - first = [ - 0x7E, # Start - 0x00 # Addr, always 0 for this device - ] + [command, length] + data + first = ( + [0x7E, 0x00] + [command, length] + data # Start # Addr, always 0 for this device + ) checksum = self._calculate_checksum(first[1:]) - last = [ - checksum, - 0x7E # Stop - ] + last = [checksum, 0x7E] # Stop actual_command = first + last self.serial.write(actual_command) - if not ord(self.serial.read(1)) == 0x7e: + if not ord(self.serial.read(1)) == 0x7E: read_success = False - logging.warning('First char should have been 0x7e') + logging.warning("First char should have been 0x7e") chars = bytes() char = self.serial.read(1) @@ -54,12 +51,12 @@ def comm(self, command, data=[]): char = self.serial.read(1) # Reverse byte-stuffing - chars = chars.replace(b'\x7D\x5E', b'\x7E') - chars = chars.replace(b'\x7D\x5D', b'\x7D') - chars = chars.replace(b'\x7D\x31', b'\x11') - chars = chars.replace(b'\x7D\x33', b'\x13') + chars = chars.replace(b"\x7D\x5E", b"\x7E") + chars = chars.replace(b"\x7D\x5D", b"\x7D") + chars = chars.replace(b"\x7D\x31", b"\x11") + chars = chars.replace(b"\x7D\x33", b"\x13") - if not chars[0] == 0: + if not chars[0] == 0: # Address, 0x7E is already stripped pass expected_checksum = self._calculate_checksum(chars[:-1]) @@ -67,10 +64,9 @@ def comm(self, command, data=[]): state = chars[2] if state == 0x43: - print('SPS30: Command {} not allowd in current state'.format( - hex(command))) + print("SPS30: Command {} not allowd in current state".format(hex(command))) elif state > 0: - print('SPS30: Error state: {}'.format(state)) + print("SPS30: Error state: {}".format(state)) # Do not return header and checksum chars = chars[4:-1] @@ -83,8 +79,9 @@ def device_info(self): data = [0x03] serial = self.comm(command, data) - info = 'Product type: {}. Serial no.: {}'.format(product_type.decode(), - serial.decode()) + info = "Product type: {}. Serial no.: {}".format( + product_type.decode(), serial.decode() + ) return info def clean_fan(self): @@ -94,8 +91,9 @@ def read_version(self): command = 0xD1 data = self.comm(command) - version = 'Firmware: {}.{}; Hardware: {}; SHDC: {}.{}'.format( - data[0], data[1], data[3], data[5], data[6]) + version = "Firmware: {}.{}; Hardware: {}; SHDC: {}.{}".format( + data[0], data[1], data[3], data[5], data[6] + ) return version def device_status(self): @@ -115,10 +113,7 @@ def device_status(self): def start_measuring(self): command = 0x00 # Start - data = [ - 0x01, # Protocol, must be 0x01 - 0x03 # Big-endian IEEE754 float values - ] + data = [0x01, 0x03] # Protocol, must be 0x01 # Big-endian IEEE754 float values self.comm(command, data) # No reply from this command return True @@ -132,14 +127,15 @@ def read_measurement(self): data = self.comm(command) error = -1 except AssertionError: - error +=1 + error += 1 if error > 0: return None # Unpack 10 big-endian floats - 40 bytes in total parsed_data = struct.unpack(">ffffffffff", data) return parsed_data -if __name__ == '__main__': + +if __name__ == "__main__": dust_sensor = SensirionSPS30() print(dust_sensor.device_info()) @@ -151,13 +147,13 @@ def read_measurement(self): for i in range(0, 3): time.sleep(1.1) # Todo: Read status to know if measurement is ready parsed_data = dust_sensor.read_measurement() - print('MC PM1.0 [μg/m3]: {:.2f}'.format(parsed_data[0])) - print('MC PM2.5 [μg/m3]: {:.2f}'.format(parsed_data[1])) - print('MC PM4.0 [μg/m3]: {:.2f}'.format(parsed_data[2])) - print('MC PM10 [μg/m3]: {:.2f}'.format(parsed_data[3])) - print('NC NM0.5 [#/cm3]: {:.2f}'.format(parsed_data[4])) - print('NC NM1.0 [#/cm3]: {:.2f}'.format(parsed_data[5])) - print('NC NM2.5 [#/cm3]: {:.2f}'.format(parsed_data[6])) - print('NC NM4.0 [#/cm3]: {:.2f}'.format(parsed_data[7])) - print('NC NM10 [#/cm3]: {:.2f}'.format(parsed_data[8])) - print('Typical size: [μm]: {:.2f}'.format(parsed_data[9])) + print("MC PM1.0 [μg/m3]: {:.2f}".format(parsed_data[0])) + print("MC PM2.5 [μg/m3]: {:.2f}".format(parsed_data[1])) + print("MC PM4.0 [μg/m3]: {:.2f}".format(parsed_data[2])) + print("MC PM10 [μg/m3]: {:.2f}".format(parsed_data[3])) + print("NC NM0.5 [#/cm3]: {:.2f}".format(parsed_data[4])) + print("NC NM1.0 [#/cm3]: {:.2f}".format(parsed_data[5])) + print("NC NM2.5 [#/cm3]: {:.2f}".format(parsed_data[6])) + print("NC NM4.0 [#/cm3]: {:.2f}".format(parsed_data[7])) + print("NC NM10 [#/cm3]: {:.2f}".format(parsed_data[8])) + print("Typical size: [μm]: {:.2f}".format(parsed_data[9])) diff --git a/PyExpLabSys/drivers/sparkfun_quad_relay.py b/PyExpLabSys/drivers/sparkfun_quad_relay.py index 5f28cd6d..be86d113 100644 --- a/PyExpLabSys/drivers/sparkfun_quad_relay.py +++ b/PyExpLabSys/drivers/sparkfun_quad_relay.py @@ -3,8 +3,9 @@ class SparkFunQuadRelay: - """Driver for the QWIIC SparkFun Quad Relay """ - def __init__(self, address=0x6d): + """Driver for the QWIIC SparkFun Quad Relay""" + + def __init__(self, address=0x6D): # Get I2C bus self.bus = smbus.SMBus(1) self.device_address = address @@ -23,7 +24,7 @@ def relay_status(self, relay_index): return reply > 0 -if __name__ == '__main__': +if __name__ == "__main__": relay = SparkFunQuadRelay() relay.set_relay(4, False) diff --git a/PyExpLabSys/drivers/specs_XRC1000.py b/PyExpLabSys/drivers/specs_XRC1000.py index 4df8be8a..b08e0d29 100644 --- a/PyExpLabSys/drivers/specs_XRC1000.py +++ b/PyExpLabSys/drivers/specs_XRC1000.py @@ -12,11 +12,12 @@ import json EXCEPTION = None -log = open('error_log.txt', 'w') +log = open("error_log.txt", "w") class CursesTui(threading.Thread): - """ Defines a fallback text-gui for the source control. """ + """Defines a fallback text-gui for the source control.""" + def __init__(self, sourcecontrol): threading.Thread.__init__(self) self.sc = sourcecontrol @@ -33,46 +34,84 @@ def __init__(self, sourcecontrol): def run(self): while self.running: - self.screen.addstr(3, 2, 'X-ray Source Control, ID: ' + str(self.sc.status['ID'])) + self.screen.addstr(3, 2, "X-ray Source Control, ID: " + str(self.sc.status["ID"])) - if self.sc.status['degas']: + if self.sc.status["degas"]: self.screen.addstr(4, 2, "Degassing") - if self.sc.status['remote']: + if self.sc.status["remote"]: self.screen.addstr(5, 2, "Control mode: Remote") else: self.screen.addstr(5, 2, "Control mode: Local") - if self.sc.status['standby']: + if self.sc.status["standby"]: self.screen.addstr(6, 2, "Device status, Standby: ON ") else: self.screen.addstr(6, 2, "Device status, Standby: OFF ") - - if self.sc.status['hv']: + + if self.sc.status["hv"]: self.screen.addstr(7, 2, "Device status: HV ON ") else: self.screen.addstr(7, 2, "Device status: HV OFF ") - if self.sc.status['operate']: + if self.sc.status["operate"]: self.screen.addstr(8, 2, "Device status: Operate ON ") else: self.screen.addstr(8, 2, "Device status, Operate: OFF ") - - #if self.sc.status['error'] != None: + + # if self.sc.status['error'] != None: # self.screen.addstr(9, 2, "Error: " + str(self.sc.status['error'])) try: - self.screen.addstr(10, 2, "Filament bias: {0:.3f}V ".format(self.sc.status['filament_bias'])) - self.screen.addstr(11, 2, "Filament Current: {0:.2f}A ".format(self.sc.status['filament_current'])) - self.screen.addstr(12, 2, "Filament Power: {0:.2f}W ".format(self.sc.status['filament_power'])) - self.screen.addstr(13, 2, "Emission Current: {0:.4f}A ".format(self.sc.status['emission_current'])) - self.screen.addstr(14, 2, "Anode Voltage: {0:.2f}V ".format(self.sc.status['anode_voltage'])) - self.screen.addstr(15, 2, "Anode Power: {0:.2f}W ".format(self.sc.status['anode_power'])) - self.screen.addstr(16, 2, "Water flow: {0:.2f}L/min ".format(self.sc.status['water_flow'])) + self.screen.addstr( + 10, + 2, + "Filament bias: {0:.3f}V ".format( + self.sc.status["filament_bias"] + ), + ) + self.screen.addstr( + 11, + 2, + "Filament Current: {0:.2f}A ".format( + self.sc.status["filament_current"] + ), + ) + self.screen.addstr( + 12, + 2, + "Filament Power: {0:.2f}W ".format( + self.sc.status["filament_power"] + ), + ) + self.screen.addstr( + 13, + 2, + "Emission Current: {0:.4f}A ".format( + self.sc.status["emission_current"] + ), + ) + self.screen.addstr( + 14, + 2, + "Anode Voltage: {0:.2f}V ".format( + self.sc.status["anode_voltage"] + ), + ) + self.screen.addstr( + 15, + 2, + "Anode Power: {0:.2f}W ".format(self.sc.status["anode_power"]), + ) + self.screen.addstr( + 16, + 2, + "Water flow: {0:.2f}L/min ".format(self.sc.status["water_flow"]), + ) except Exception as exception: global EXCEPTION EXCEPTION = exception - #self.screen.addstr(10,2, exception.message) + # self.screen.addstr(10,2, exception.message) self.screen.addstr(10, 2, "Filament bias: - ") self.screen.addstr(11, 2, "Filament Current: - ") self.screen.addstr(12, 2, "Filament Power: - ") @@ -80,50 +119,69 @@ def run(self): self.screen.addstr(14, 2, "Anode Voltage: - ") self.screen.addstr(15, 2, "Anode Power: - ") self.screen.addstr(16, 2, "water flow: - ") - if self.sc.status['error'] != None: - self.screen.addstr(18, 2, "Latest error message: " + str(self.sc.status['error']) + " at time: " + str(self.sc.status['error time'])) - - self.screen.addstr(19, 2, "Runtime: {0:.0f}s ".format(time.time() - self.time)) + if self.sc.status["error"] != None: + self.screen.addstr( + 18, + 2, + "Latest error message: " + + str(self.sc.status["error"]) + + " at time: " + + str(self.sc.status["error time"]), + ) + + self.screen.addstr( + 19, 2, "Runtime: {0:.0f}s ".format(time.time() - self.time) + ) if self.countdown: - self.screen.addstr(18, 2, "Time until shutdown: {0:.0f}s ".format(self.countdown_end_time -time.time())) + self.screen.addstr( + 18, + 2, + "Time until shutdown: {0:.0f}s ".format( + self.countdown_end_time - time.time() + ), + ) if time.time() > self.countdown_end_time: self.sc.goto_off = True self.countdown = False - - self.screen.addstr(21, 2, 'q: quit program, s: standby, o: operate, c: cooling, x: shutdown gun') - self.screen.addstr(22, 2, ' 3: shutdown in 3h, r: change to remote') - - self.screen.addstr(24, 2, ' Latest key: ' + str(self.last_key)) + + self.screen.addstr( + 21, + 2, + "q: quit program, s: standby, o: operate, c: cooling, x: shutdown gun", + ) + self.screen.addstr(22, 2, " 3: shutdown in 3h, r: change to remote") + + self.screen.addstr(24, 2, " Latest key: " + str(self.last_key)) n = self.screen.getch() - if n == ord('q'): + if n == ord("q"): self.sc.running = False self.running = False self.last_key = chr(n) - elif n == ord('s'): + elif n == ord("s"): self.sc.goto_standby = True self.last_key = chr(n) - elif n == ord('o'): + elif n == ord("o"): self.sc.goto_operate = True self.last_key = chr(n) - elif n == ord('c'): + elif n == ord("c"): self.sc.goto_cooling = True self.last_key = chr(n) - elif n == ord('x'): + elif n == ord("x"): self.sc.goto_off = True self.last_key = chr(n) - elif n == ord('r'): + elif n == ord("r"): self.sc.goto_remote = True self.last_key = chr(n) - elif n == ord('3'): + elif n == ord("3"): self.countdown = True - self.countdown_end_time = float(time.time() + 3*3600.0) # second + self.countdown_end_time = float(time.time() + 3 * 3600.0) # second self.last_key = chr(n) - + # disable s o key - #if n == ord('s'): + # if n == ord('s'): # self.sc.goto_standby = True - #if n == ord('o'): + # if n == ord('o'): # self.sc.goto_operate = True self.screen.refresh() @@ -133,7 +191,7 @@ def run(self): print(EXCEPTION) def stop(self): - """ Cleanup the terminal """ + """Cleanup the terminal""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -141,39 +199,39 @@ def stop(self): class XRC1000(threading.Thread): - """ Driver for X-ray Source Control - XRC 1000""" + """Driver for X-ray Source Control - XRC 1000""" def __init__(self, port=None): - """ Initialize module + """Initialize module Establish serial connection and create status variable to expose the status for the instrument for the various gui's """ threading.Thread.__init__(self) - + if port == None: - port = '/dev/ttyUSB0' + port = "/dev/ttyUSB0" self.status = {} # Hold parameters to be accecible by gui - self.status['hv'] = None - self.status['standby'] = None - self.status['operate'] = None - self.status['degas'] = None - self.status['remote'] = None - self.status['error'] = None - self.status['error time'] = None - self.status['start time'] = time.time() - self.status['cooling'] = None - self.status['off'] = None - self.status['sputter_current'] = None - self.status['filament_bias'] = None - self.status['filament_current'] = None - self.status['filament_power'] = None - self.status['emission_current'] = None - self.status['anode_voltage'] = None - self.status['anode_power'] = None - self.status['water_flow'] = None - self.status['ID'] = None + self.status["hv"] = None + self.status["standby"] = None + self.status["operate"] = None + self.status["degas"] = None + self.status["remote"] = None + self.status["error"] = None + self.status["error time"] = None + self.status["start time"] = time.time() + self.status["cooling"] = None + self.status["off"] = None + self.status["sputter_current"] = None + self.status["filament_bias"] = None + self.status["filament_current"] = None + self.status["filament_power"] = None + self.status["emission_current"] = None + self.status["anode_voltage"] = None + self.status["anode_power"] = None + self.status["water_flow"] = None + self.status["ID"] = None self.running = True self.goto_standby = False self.goto_operate = False @@ -181,42 +239,56 @@ def __init__(self, port=None): self.goto_cooling = False self.goto_remote = False self.simulate = False - #self.update_status() + # self.update_status() self.list_of_errors = [] - self.list_of_errors += ['>E251: Remote Locked !\n'] - self.list_of_errors += ['>E250: Not in Remote !\n'] - self.list_of_errors += ['>E251: Misplaced Query !\n'] - self.list_of_errors += ['>E251: Argument missing !\n'] - self.list_of_errors += ['>E251: Value to big or to low !\n'] - self.list_of_errors += ['>E251: Parameter unknown !\n'] - self.list_of_errors += ['>E251: Command not found !\n'] - self.list_of_errors += ['>E251: Unexpected Error code !\n'] - self.get_commands = ['REM?', 'IEM?', 'UAN?', 'IHV?', 'IFI?', 'UFI?', 'PAN?', 'SERNO?', 'ANO?', 'STAT?', 'OPE?'] - #self.simulate = simulate + self.list_of_errors += [">E251: Remote Locked !\n"] + self.list_of_errors += [">E250: Not in Remote !\n"] + self.list_of_errors += [">E251: Misplaced Query !\n"] + self.list_of_errors += [">E251: Argument missing !\n"] + self.list_of_errors += [">E251: Value to big or to low !\n"] + self.list_of_errors += [">E251: Parameter unknown !\n"] + self.list_of_errors += [">E251: Command not found !\n"] + self.list_of_errors += [">E251: Unexpected Error code !\n"] + self.get_commands = [ + "REM?", + "IEM?", + "UAN?", + "IHV?", + "IFI?", + "UFI?", + "PAN?", + "SERNO?", + "ANO?", + "STAT?", + "OPE?", + ] + # self.simulate = simulate self.f = serial.Serial(port, 9600, timeout=0.25) - #baud: 9600, bits: 8, parity: None - return_string = self.comm('SERNO?') - self.status['ID'] = return_string - if self.status['ID'] == '000003AADEBD28': + # baud: 9600, bits: 8, parity: None + return_string = self.comm("SERNO?") + self.status["ID"] = return_string + if self.status["ID"] == "000003AADEBD28": pass else: - print('Error SERIAL Number: ' + return_string) + print("Error SERIAL Number: " + return_string) print(len(return_string)) - print(len('SERNO:000003AADEBD28\n>')) + print(len("SERNO:000003AADEBD28\n>")) for el in return_string: print(ord(el)) self.get_status() - if self.status['remote'] == False: + if self.status["remote"] == False: self.remote_enable() self.get_status() self.init_socket() - def init_socket(self,): - self.address_port = ('rasppi00',9000) + def init_socket( + self, + ): + self.address_port = ("rasppi00", 9000) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def comm(self, command): - """ Communication with the instrument + """Communication with the instrument Implements the synatx need to send commands to instrument including handling carrige returns and extra lines of 'OK' and other @@ -226,101 +298,103 @@ def comm(self, command): :type command: str :return: The reply to the command striped for protocol technicalities :rtype: str - + posible comands: REM?, IEM?, UAN?, IHV?, IFI?, UFI?, PAN?, SERNO?, ANO?, STAT?, OPE? REM, LOC, IEM 20e-3, UAN 10e3, OFF, COOL, STAN, UAON, OPE, ANO 1, ANO 2 """ n = self.f.inWaiting() if n > 1: - print('Error') + print("Error") reply = self.f.read(n) - self.status['error']='n = '+str(n) + ' ' + str(reply) + self.status["error"] = "n = " + str(n) + " " + str(reply) else: self.f.read(n) - self.f.write(command + '\r') + self.f.write(command + "\r") time.sleep(0.2) reply = self.f.readline() - #if '>' in reply[0]: # sanity character + # if '>' in reply[0]: # sanity character # #print 'Valid command' # pass - #else: + # else: # print 'None valid command/reply' # print reply - if command in self.get_commands and ':' in reply: - echo, value = reply.split(':') + if command in self.get_commands and ":" in reply: + echo, value = reply.split(":") return_string = value.strip() # get value from space to -2 # posible answer to 'UAN?' true echo # '>UAN: 12.00e3\n' # posible answer to 'OPE?' non true echo # '>OPERATE: 4.000\n' - #return_string = reply + # return_string = reply else: return_string = True - return(return_string) - + return return_string + def direct_comm(self, command): - self.f.write(command + '\r') + self.f.write(command + "\r") time.sleep(0.2) reply = self.f.readline() return_string = reply - return(return_string) + return return_string - def read_water_flow(self,): + def read_water_flow( + self, + ): """read the water flow from external hardware :return: water flow in L/min :rtype float """ try: - self.sock.sendto('stm312_xray_waterflow#json', self.address_port) + self.sock.sendto("stm312_xray_waterflow#json", self.address_port) answer = self.sock.recvfrom(1024) water_flow_time, water_flow = json.loads(answer[0]) except: water_flow = -1.0 return water_flow - def read_emission_current(self): #need testing - """ Read the emission current. Unit A + def read_emission_current(self): # need testing + """Read the emission current. Unit A :return: The emission current :rtype: float """ - reply = self.comm('IEM?') # 'IEM 20e-3\r' - #print(reply) + reply = self.comm("IEM?") # 'IEM 20e-3\r' + # print(reply) try: - value = float(reply)/1.0 + value = float(reply) / 1.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value - def read_filament_voltage(self): #need testing - """ Read the filament voltage. Unit V + def read_filament_voltage(self): # need testing + """Read the filament voltage. Unit V :return: The filament voltage :rtype: float """ - reply = self.comm('UFI?') + reply = self.comm("UFI?") try: value = float(reply) / 1.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value - def read_filament_current(self): #need testing - """ Read the filament current. Unit A + def read_filament_current(self): # need testing + """Read the filament current. Unit A :return: The filament current :rtype: float """ - reply = self.comm('IFI?') + reply = self.comm("IFI?") try: value = float(reply) / 1.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value - #def read_emission_current(self): + # def read_emission_current(self): # """ Read the emission current. Unit mA # :return: The emission current # :rtype: float @@ -333,197 +407,205 @@ def read_filament_current(self): #need testing # value = None # return(value) - def read_anode_voltage(self): #need testing - """ Read the anode voltage. Unit V + def read_anode_voltage(self): # need testing + """Read the anode voltage. Unit V :return: The anode voltage :rtype: float """ - reply = self.comm('UAN?') + reply = self.comm("UAN?") try: value = float(reply) / 1.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) - - def read_anode_power(self): #need testing - """ Read the anode voltage. Unit W + return value + + def read_anode_power(self): # need testing + """Read the anode voltage. Unit W :return: The anode voltage :rtype: float """ - reply = self.comm('PAN?') + reply = self.comm("PAN?") try: value = float(reply) / 1.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value - def standby(self):#need testing - """ Set the device on standby + def standby(self): # need testing + """Set the device on standby The function is not working entirely as intended. TODO: Implement check to see if the device is alrady in standby :return: The direct reply from the device :rtype: str """ - #self.direct_comm('ANO 2') - reply = self.comm('STAN') + # self.direct_comm('ANO 2') + reply = self.comm("STAN") time.sleep(1) self.update_status() - return(reply) + return reply - def operate(self):#need testing - """ Set the device in operation mode + def operate(self): # need testing + """Set the device in operation mode TODO: This function should only be activated from standby!!! :return: The direct reply from the device :rtype: str """ - #self.comm('ANO 2') - #time.sleep(1) - self.comm('UAN 12e3') + # self.comm('ANO 2') + # time.sleep(1) + self.comm("UAN 12e3") time.sleep(1) - self.comm('IEM 20e-3') + self.comm("IEM 20e-3") time.sleep(1) - reply = self.comm('OPE') + reply = self.comm("OPE") self.update_status() - return(reply) + return reply - def remote_enable(self, local=False):#need testing - """ Enable or disable remote mode + def remote_enable(self, local=False): # need testing + """Enable or disable remote mode :param local: If True the device is set to local, otherwise to remote :type local: Boolean :return: The direct reply from the device :rtype: str """ if local: - reply = self.comm('LOC') + reply = self.comm("LOC") else: - reply = self.comm('REM') + reply = self.comm("REM") time.sleep(1) self.update_status() - return(reply) - def change_control(self):#need testing - """ Enable or disable remote mode + return reply + + def change_control(self): # need testing + """Enable or disable remote mode :param local: If True the device is set to local, otherwise to remote :type local: Boolean :return: The direct reply from the device :rtype: str """ - if self.status['remote']: - reply = self.comm('LOC') + if self.status["remote"]: + reply = self.comm("LOC") else: - reply = self.comm('REM') + reply = self.comm("REM") time.sleep(1) self.update_status() - return(reply) - + return reply + def cooling(self): - """ Enable or disable water cooling + """Enable or disable water cooling :type local: Boolean :return: The direct reply from the device :rtype: str """ - reply = self.comm('COOL') + reply = self.comm("COOL") self.update_status() - return(reply) - + return reply + def get_status(self): - reply = self.comm('STAT?') - if reply[0:2] == '00': - self.status['remote'] = False - if reply[0:2] == '02': - self.status['remote'] = True - - if reply[2:4] == '00': - self.status['off'] = True - self.status['cooling'] = False - self.status['standby'] = False - self.status['hv'] = False - self.status['operate'] = False - if reply[2:4] == '01': - self.status['off'] = False - self.status['cooling'] = True - self.status['standby'] = False - self.status['hv'] = False - self.status['operate'] = False - if reply[2:4] == '02': - self.status['off'] = False - self.status['cooling'] = False - self.status['standby'] = True - self.status['hv'] = False - self.status['operate'] = False - if reply[2:4] == '03': - self.status['off'] = False - self.status['cooling'] = False - self.status['standby'] = False - self.status['hv'] = True - self.status['operate'] = False - if reply[2:4] == '04': - self.status['off'] = False - self.status['cooling'] = False - self.status['standby'] = False - self.status['hv'] = False - self.status['operate'] = True - - if reply[4:6] == '00': - self.status['error'] = False + reply = self.comm("STAT?") + if reply[0:2] == "00": + self.status["remote"] = False + if reply[0:2] == "02": + self.status["remote"] = True + + if reply[2:4] == "00": + self.status["off"] = True + self.status["cooling"] = False + self.status["standby"] = False + self.status["hv"] = False + self.status["operate"] = False + if reply[2:4] == "01": + self.status["off"] = False + self.status["cooling"] = True + self.status["standby"] = False + self.status["hv"] = False + self.status["operate"] = False + if reply[2:4] == "02": + self.status["off"] = False + self.status["cooling"] = False + self.status["standby"] = True + self.status["hv"] = False + self.status["operate"] = False + if reply[2:4] == "03": + self.status["off"] = False + self.status["cooling"] = False + self.status["standby"] = False + self.status["hv"] = True + self.status["operate"] = False + if reply[2:4] == "04": + self.status["off"] = False + self.status["cooling"] = False + self.status["standby"] = False + self.status["hv"] = False + self.status["operate"] = True + + if reply[4:6] == "00": + self.status["error"] = False else: - if self.status['error'] == False: - self.status['error time'] = time.time() - self.status['start time'] - self.status['error'] = reply[4:6] - #error_bin = bin(int(reply[4:5])) - #if error_bin[0:1] == '' + if self.status["error"] == False: + self.status["error time"] = time.time() - self.status["start time"] + self.status["error"] = reply[4:6] + # error_bin = bin(int(reply[4:5])) + # if error_bin[0:1] == '' + def interlocks(self): - self.status['interlocks'] = {'Failure':True,'HVLock':True,'Vacuum':True,'Water':True,'ERR_ILIM':True,'ERR_TOUT':True} - - - + self.status["interlocks"] = { + "Failure": True, + "HVLock": True, + "Vacuum": True, + "Water": True, + "ERR_ILIM": True, + "ERR_TOUT": True, + } + def automated_operate(self): - self.direct_comm('ANO 1') - self.direct_comm('STAN') - self.direct_comm('UAON') - self.direct_comm('UAN 12e3') # 12kV - self.direct_comm('OPE') + self.direct_comm("ANO 1") + self.direct_comm("STAN") + self.direct_comm("UAON") + self.direct_comm("UAN 12e3") # 12kV + self.direct_comm("OPE") wait = True n = 0 while wait: - if self.direct_comm('UAN?') == '>UAN: 12.00e3\n': + if self.direct_comm("UAN?") == ">UAN: 12.00e3\n": wait = False elif n > 5: wait = False else: - n+=1 + n += 1 time.sleep(5) - self.direct_comm('IEM 20e-3') # 20mA + self.direct_comm("IEM 20e-3") # 20mA wait = True n = 0 while wait: - if self.direct_comm('IEM?') == '>IEM: 20.06e-3\n': + if self.direct_comm("IEM?") == ">IEM: 20.06e-3\n": wait = False elif n > 5: wait = False else: - n+=1 + n += 1 time.sleep(5) return True + def turn_off(self): self.update_status() - if self.status['operate']: - self.comm('UAON') + if self.status["operate"]: + self.comm("UAON") time.sleep(2) self.update_status() - if self.status['hv']: - self.comm('STAN') + if self.status["hv"]: + self.comm("STAN") time.sleep(2) self.update_status() - if self.status['standby']: - self.comm('OFF') + if self.status["standby"]: + self.comm("OFF") self.update_status() # Update key parameters return True - def update_status(self): # not done - """ Update the status of the instrument + def update_status(self): # not done + """Update the status of the instrument Runs a number of status queries and updates self.status @@ -531,18 +613,20 @@ def update_status(self): # not done :rtype: str """ - #self.status['temperature'] = self.read_temperature_energy_module() - self.status['filament_bias'] = self.read_filament_voltage() - self.status['filament_current'] = self.read_filament_current() - self.status['filament_power'] = self.status['filament_bias'] * self.status['filament_current'] - self.status['emission_current'] = self.read_emission_current() - self.status['anode_voltage'] = self.read_anode_voltage() - self.status['anode_power'] = self.read_anode_power() - self.status['water_flow'] = self.read_water_flow() - #log.write(str(self.status) + '\n') + # self.status['temperature'] = self.read_temperature_energy_module() + self.status["filament_bias"] = self.read_filament_voltage() + self.status["filament_current"] = self.read_filament_current() + self.status["filament_power"] = ( + self.status["filament_bias"] * self.status["filament_current"] + ) + self.status["emission_current"] = self.read_emission_current() + self.status["anode_voltage"] = self.read_anode_voltage() + self.status["anode_power"] = self.read_anode_power() + self.status["water_flow"] = self.read_water_flow() + # log.write(str(self.status) + '\n') self.get_status() - return(True) + return True def run(self): while self.running: @@ -562,21 +646,19 @@ def run(self): self.cooling() self.goto_cooling = False if self.goto_remote: - self.remote_enable(local=self.status['remote']) + self.remote_enable(local=self.status["remote"]) self.goto_remote = False - - -if __name__ == '__main__': - sc = XRC1000(port='/dev/serial/by-id/usb-1a86_USB2.0-Ser_-if00-port0') - #print sc.read_emission_current() - #print sc.read_filament_voltage() - #print sc.read_filament_current() - #print sc.read_anode_voltage() - #print sc.read_anode_power() - #command_list=['REM?', 'IEM?', 'UAN?', 'IHV?', 'IFI?', 'UFI?', 'PAN?', 'SERNO?', 'ANO?', 'STAT?', 'OPE?'] - #for command in command_list: +if __name__ == "__main__": + sc = XRC1000(port="/dev/serial/by-id/usb-1a86_USB2.0-Ser_-if00-port0") + # print sc.read_emission_current() + # print sc.read_filament_voltage() + # print sc.read_filament_current() + # print sc.read_anode_voltage() + # print sc.read_anode_power() + # command_list=['REM?', 'IEM?', 'UAN?', 'IHV?', 'IFI?', 'UFI?', 'PAN?', 'SERNO?', 'ANO?', 'STAT?', 'OPE?'] + # for command in command_list: # print(str(command) + ' : ' + str(sc.direct_comm(command))) sc.start() @@ -585,17 +667,17 @@ def run(self): tui.daemon = True tui.start() - #print('Temperature: ' + str(sputter.read_temperature_energy_module())) - #print('Sputter current: ' + str(sputter.read_sputter_current())) - #print('Temperature: ' + str(sputter.read_temperature_energy_module())) - #print('Filament voltage: ' + str(sc.read_filament_voltage())) - #print('Filament current: ' + str(sc.read_filament_current())) - #print('Emission current: ' + str(sc.read_emission_current()) + 'A') - #print('Anode voltage: ' + str(sc.read_anode_voltage())) - #print('Anode power: ' + str(sc.read_anode_power()) + 'W') - - #sputter.update_status() - #print('Enable:') - #print(sputter.remote_enable(local=False)) - #print('Status:') - #print(sputter.status) + # print('Temperature: ' + str(sputter.read_temperature_energy_module())) + # print('Sputter current: ' + str(sputter.read_sputter_current())) + # print('Temperature: ' + str(sputter.read_temperature_energy_module())) + # print('Filament voltage: ' + str(sc.read_filament_voltage())) + # print('Filament current: ' + str(sc.read_filament_current())) + # print('Emission current: ' + str(sc.read_emission_current()) + 'A') + # print('Anode voltage: ' + str(sc.read_anode_voltage())) + # print('Anode power: ' + str(sc.read_anode_power()) + 'W') + + # sputter.update_status() + # print('Enable:') + # print(sputter.remote_enable(local=False)) + # print('Status:') + # print(sputter.status) diff --git a/PyExpLabSys/drivers/specs_iqe11.py b/PyExpLabSys/drivers/specs_iqe11.py index ec96d448..affbb6ad 100644 --- a/PyExpLabSys/drivers/specs_iqe11.py +++ b/PyExpLabSys/drivers/specs_iqe11.py @@ -11,7 +11,8 @@ class CursesTui(threading.Thread): - """ Defines a fallback text-gui for the sputter gun. """ + """Defines a fallback text-gui for the sputter gun.""" + def __init__(self, sputtergun): threading.Thread.__init__(self) self.sg = sputtergun @@ -25,27 +26,63 @@ def __init__(self, sputtergun): def run(self): while True: - self.screen.addstr(3, 2, 'Sputter Gun Control') + self.screen.addstr(3, 2, "Sputter Gun Control") - if self.sg.status['degas']: + if self.sg.status["degas"]: self.screen.addstr(4, 2, "Degassing") - if self.sg.status['remote']: + if self.sg.status["remote"]: self.screen.addstr(5, 2, "Remote control") - if self.sg.status['standby']: + if self.sg.status["standby"]: self.screen.addstr(6, 2, "Device status: Standby ") - if self.sg.status['operate']: + if self.sg.status["operate"]: self.screen.addstr(6, 2, "Device status: Operate! ") try: - self.screen.addstr(9, 2, "Temperature, electronics: {0:.0f}C ".format(self.sg.status['temperature'])) - self.screen.addstr(10, 2, "Sputter Current: {0:.4f}mA ".format(self.sg.status['sputter_current'])) - self.screen.addstr(11, 2, "Filament bias: {0:.3f}V ".format(self.sg.status['filament_bias'])) - self.screen.addstr(12, 2, "Filament Current: {0:.2f}A ".format(self.sg.status['filament_current'])) - self.screen.addstr(13, 2, "Emission current: {0:.4f}mA ".format(self.sg.status['emission_current'])) - self.screen.addstr(14, 2, "Acceleration Voltage: {0:.2f}V ".format(self.sg.status['accel_voltage'])) + self.screen.addstr( + 9, + 2, + "Temperature, electronics: {0:.0f}C ".format( + self.sg.status["temperature"] + ), + ) + self.screen.addstr( + 10, + 2, + "Sputter Current: {0:.4f}mA ".format( + self.sg.status["sputter_current"] + ), + ) + self.screen.addstr( + 11, + 2, + "Filament bias: {0:.3f}V ".format( + self.sg.status["filament_bias"] + ), + ) + self.screen.addstr( + 12, + 2, + "Filament Current: {0:.2f}A ".format( + self.sg.status["filament_current"] + ), + ) + self.screen.addstr( + 13, + 2, + "Emission current: {0:.4f}mA ".format( + self.sg.status["emission_current"] + ), + ) + self.screen.addstr( + 14, + 2, + "Acceleration Voltage: {0:.2f}V ".format( + self.sg.status["accel_voltage"] + ), + ) except ValueError: self.screen.addstr(9, 2, "Temperature, electronics: - ") self.screen.addstr(10, 2, "Sputter Current: - ") @@ -54,24 +91,26 @@ def run(self): self.screen.addstr(13, 2, "Emission current: - ") self.screen.addstr(14, 2, "Acceleration Voltage: - ") - #self.screen.addstr(16, 2, "Latest error message: " + self.sg.status['error']) + # self.screen.addstr(16, 2, "Latest error message: " + self.sg.status['error']) - self.screen.addstr(17, 2, "Runtime: {0:.0f}s ".format(time.time() - self.time)) - self.screen.addstr(18, 2, 'q: quit, s: standby, o: operate') + self.screen.addstr( + 17, 2, "Runtime: {0:.0f}s ".format(time.time() - self.time) + ) + self.screen.addstr(18, 2, "q: quit, s: standby, o: operate") n = self.screen.getch() - if n == ord('q'): + if n == ord("q"): self.sg.running = False - if n == ord('s'): + if n == ord("s"): self.sg.goto_standby = True - if n == ord('o'): + if n == ord("o"): self.sg.goto_operate = True self.screen.refresh() time.sleep(1) def stop(self): - """ Cleanup the terminal """ + """Cleanup the terminal""" curses.nocbreak() self.screen.keypad(0) curses.echo() @@ -79,10 +118,10 @@ def stop(self): class Puiqe11(threading.Thread): - """ Driver for ion sputter guns from SPECS """ + """Driver for ion sputter guns from SPECS""" def __init__(self, simulate=False): - """ Initialize module + """Initialize module Establish serial connection and create status variable to expose the status for the instrument for the various gui's @@ -90,35 +129,35 @@ def __init__(self, simulate=False): threading.Thread.__init__(self) self.simulate = simulate - self.f = serial.Serial('/dev/ttyS0', 1200, timeout=0.25) - self.f.write('e0' + '\r') # Echo off + self.f = serial.Serial("/dev/ttyS0", 1200, timeout=0.25) + self.f.write("e0" + "\r") # Echo off time.sleep(1) ok = self.f.read(self.f.inWaiting()) - if ok.find('OK') > -1: + if ok.find("OK") > -1: pass else: if self.simulate is not True: - print('ERROR!!!') + print("ERROR!!!") self.status = {} # Hold parameters to be accecible by gui - self.status['hv'] = None - self.status['standby'] = None - self.status['operate'] = None - self.status['degas'] = None - self.status['remote'] = None - self.status['error'] = '' - self.status['temperature'] = None - self.status['sputter_current'] = None - self.status['filament_bias'] = None - self.status['filament_current'] = None - self.status['emission_current'] = None - self.status['accel_voltage'] = None + self.status["hv"] = None + self.status["standby"] = None + self.status["operate"] = None + self.status["degas"] = None + self.status["remote"] = None + self.status["error"] = "" + self.status["temperature"] = None + self.status["sputter_current"] = None + self.status["filament_bias"] = None + self.status["filament_current"] = None + self.status["emission_current"] = None + self.status["accel_voltage"] = None self.running = True self.goto_standby = False self.goto_operate = False - #self.update_status() + # self.update_status() def comm(self, command): - """ Communication with the instrument + """Communication with the instrument Implements the synatx need to send commands to instrument including handling carrige returns and extra lines of 'OK' and other @@ -131,10 +170,10 @@ def comm(self, command): """ n = self.f.inWaiting() if n > 1: - print('Error') + print("Error") else: self.f.read(n) - self.f.write(command + '\r') + self.f.write(command + "\r") time.sleep(0.1) reply = self.f.readline() self.f.read(1) # Empty buffer for extra newline @@ -142,137 +181,137 @@ def comm(self, command): ok_reply = self.f.readline() # Wait for OK - cr_count = reply.count('\r') - #Check that no old commands is still in buffer and that the reply - #is actually intended for the requested parameter + cr_count = reply.count("\r") + # Check that no old commands is still in buffer and that the reply + # is actually intended for the requested parameter cr_check = cr_count == 1 - command_check = reply[0:len(command) - 1] == command.strip('?') - ok_check = ok_reply.find('OK') > -1 + command_check = reply[0 : len(command) - 1] == command.strip("?") + ok_check = ok_reply.find("OK") > -1 if cr_check and command_check and ok_check: echo_length = len(command) return_string = reply[echo_length:] - elif(command == 'os'): + elif command == "os": return_string = reply else: if self.simulate is False: - return_string = 'Communication error!' + return_string = "Communication error!" else: - return(1) - return(return_string) + return 1 + return return_string def read_sputter_current(self): - """ Read the sputter current. Unit mA + """Read the sputter current. Unit mA :return: The sputter current :rtype: float """ - reply = self.comm('eni?') + reply = self.comm("eni?") try: value = float(reply) / 1000 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def read_filament_voltage(self): - """ Read the filament voltage. Unit V + """Read the filament voltage. Unit V :return: The filament voltage :rtype: float """ - reply = self.comm('fu?') + reply = self.comm("fu?") try: value = float(reply) / 100.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def read_filament_current(self): - """ Read the filament current. Unit A + """Read the filament current. Unit A :return: The filament current :rtype: float """ - reply = self.comm('fi?') + reply = self.comm("fi?") try: value = float(reply) / 10.0 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def read_emission_current(self): - """ Read the emission current. Unit mA + """Read the emission current. Unit mA :return: The emission current :rtype: float """ - reply = self.comm('ec?') + reply = self.comm("ec?") try: value = float(reply) / 1000 except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def read_acceleration_voltage(self): - """ Read the acceleration voltage. Unit V + """Read the acceleration voltage. Unit V :return: The acceleration voltage :rtype: float """ - reply = self.comm('ec?') + reply = self.comm("ec?") try: value = float(reply) except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def read_temperature_energy_module(self): - """ Read the temperature of the electronics module + """Read the temperature of the electronics module This value is not extremely correct, use only as guideline. :return: The temperature :rtype: float """ - reply = self.comm('ent?') + reply = self.comm("ent?") try: value = float(reply) except ValueError: - self.status['error'] = reply + self.status["error"] = reply value = None - return(value) + return value def standby(self): - """ Set the device on standby + """Set the device on standby The function is not working entirely as intended. TODO: Implement check to see if the device is alrady in standby :return: The direct reply from the device :rtype: str """ - reply = self.comm('sb') - return(reply) + reply = self.comm("sb") + return reply def operate(self): - """ Set the device in operation mode + """Set the device in operation mode TODO: This function should only be activated from standby!!! :return: The direct reply from the device :rtype: str """ - reply = self.comm('op') - return(reply) + reply = self.comm("op") + return reply def remote_enable(self, local=False): - """ Enable or disable remote mode + """Enable or disable remote mode :param local: If True the device is set to local, otherwise to remote :type local: Boolean :return: The direct reply from the device :rtype: str """ if local: - reply = self.comm('lo') + reply = self.comm("lo") else: - reply = self.comm('re') - return(reply) + reply = self.comm("re") + return reply def update_status(self): - """ Update the status of the instrument + """Update the status of the instrument Runs a number of status queries and updates self.status @@ -280,47 +319,47 @@ def update_status(self): :rtype: str """ - self.status['temperature'] = self.read_temperature_energy_module() - self.status['filament_bias'] = self.read_filament_voltage() - self.status['sputter_current'] = self.read_sputter_current() - self.status['filament_current'] = self.read_filament_current() - self.status['emission_current'] = self.read_emission_current() - self.status['accel_voltage'] = self.read_acceleration_voltage() + self.status["temperature"] = self.read_temperature_energy_module() + self.status["filament_bias"] = self.read_filament_voltage() + self.status["sputter_current"] = self.read_sputter_current() + self.status["filament_current"] = self.read_filament_current() + self.status["emission_current"] = self.read_emission_current() + self.status["accel_voltage"] = self.read_acceleration_voltage() - reply = self.comm('os').lower() + reply = self.comm("os").lower() if self.simulate is not True: hv = None else: hv = False - if reply.find('he') > -1: + if reply.find("he") > -1: hv = False - if reply.find('ha') > -1: + if reply.find("ha") > -1: hv = True - assert(hv is True or hv is False) - self.status['hv'] = hv + assert hv is True or hv is False + self.status["hv"] = hv - if reply.find('re') > -1: - self.status['remote'] = True + if reply.find("re") > -1: + self.status["remote"] = True else: - self.status['remote'] = False + self.status["remote"] = False - if reply.find('sb') > -1: - self.status['standby'] = True + if reply.find("sb") > -1: + self.status["standby"] = True else: - self.status['standby'] = False + self.status["standby"] = False - if reply.find('op') > -1: - self.status['operate'] = True + if reply.find("op") > -1: + self.status["operate"] = True else: - self.status['operate'] = False + self.status["operate"] = False #!TODO: Update status to also accept neither operate or standby - if reply.find('dg') > -1: - self.status['degas'] = True + if reply.find("dg") > -1: + self.status["degas"] = True else: - self.status['degas'] = False + self.status["degas"] = False - return(reply) + return reply def run(self): while self.running: @@ -334,7 +373,8 @@ def run(self): self.operate() self.goto_operate = False -if __name__ == '__main__': + +if __name__ == "__main__": sputter = Puiqe11() sputter.start() @@ -342,16 +382,16 @@ def run(self): tui.daemon = True tui.start() - #print('Sputter current: ' + str(sputter.read_sputter_current())) - #print('Temperature: ' + str(sputter.read_temperature_energy_module())) - #print('Sputter current: ' + str(sputter.read_sputter_current())) - #print('Temperature: ' + str(sputter.read_temperature_energy_module())) - #print('Filament voltage: ' + str(sputter.read_filament_voltage())) - #print('Filament current: ' + str(sputter.read_filament_current())) - #print('Emission current: ' + str(sputter.read_emission_current())) - #print('Acceleration voltage: ' + str(sputter.read_acceleration_voltage())) - #sputter.update_status() - #print('Enable:') - #print(sputter.remote_enable(local=False)) - #print('Status:') - #print(sputter.status) + # print('Sputter current: ' + str(sputter.read_sputter_current())) + # print('Temperature: ' + str(sputter.read_temperature_energy_module())) + # print('Sputter current: ' + str(sputter.read_sputter_current())) + # print('Temperature: ' + str(sputter.read_temperature_energy_module())) + # print('Filament voltage: ' + str(sputter.read_filament_voltage())) + # print('Filament current: ' + str(sputter.read_filament_current())) + # print('Emission current: ' + str(sputter.read_emission_current())) + # print('Acceleration voltage: ' + str(sputter.read_acceleration_voltage())) + # sputter.update_status() + # print('Enable:') + # print(sputter.remote_enable(local=False)) + # print('Status:') + # print(sputter.status) diff --git a/PyExpLabSys/drivers/srs_sr630.py b/PyExpLabSys/drivers/srs_sr630.py index 002dafc5..7a196d96 100644 --- a/PyExpLabSys/drivers/srs_sr630.py +++ b/PyExpLabSys/drivers/srs_sr630.py @@ -4,101 +4,104 @@ import time import logging from PyExpLabSys.common.supported_versions import python2_and_3 + # Configure logger as library logger and set supported python versions LOGGER = logging.getLogger(__name__) LOGGER.addHandler(logging.NullHandler()) python2_and_3(__file__) # Legal values for units -UNITS = ['ABS', 'CENT', 'FHRN', 'MDC', 'DC'] +UNITS = ["ABS", "CENT", "FHRN", "MDC", "DC"] + -class SRS_SR630(): - """ Driver for Standford Research Systems, Model SR630 """ +class SRS_SR630: + """Driver for Standford Research Systems, Model SR630""" def __init__(self, port): self.ser = serial.Serial(port, 9600, timeout=2) - #print self.f - #self.f.xonxoff = True - #self.f.rtscts = False - #self.f.dsrdtr = False - #print self.f + # print self.f + # self.f.xonxoff = True + # self.f.rtscts = False + # self.f.dsrdtr = False + # print self.f time.sleep(0.1) def comm(self, command): - """ Ensures correct protocol for instrument """ - endstring = '\r' - self.ser.write((command + endstring).encode('ascii')) - if command.find('?') > -1: + """Ensures correct protocol for instrument""" + endstring = "\r" + self.ser.write((command + endstring).encode("ascii")) + if command.find("?") > -1: return_string = self.ser.readline()[:-2].decode() else: return_string = True return return_string def config_analog_channel(self, channel, follow_temperature=False, value=0): - """ Configure an analog out channel """ + """Configure an analog out channel""" if (value < -10) or (value > 10): return False if follow_temperature: - command = 'VMOD ' + str(channel) + ',0' + command = "VMOD " + str(channel) + ",0" self.comm(command) else: - command = 'VMOD ' + str(channel) + ',1' + command = "VMOD " + str(channel) + ",1" self.comm(command) - command = 'VOUT ' + str(channel) + ',' + str(value) + command = "VOUT " + str(channel) + "," + str(value) self.comm(command) return True def set_unit(self, channel, unit): - """ Set the measurement unit for a channel """ + """Set the measurement unit for a channel""" if not unit in UNITS: return False - command = 'UNIT ' + str(channel) + ',' + unit + command = "UNIT " + str(channel) + "," + unit self.comm(command) - time.sleep(0.2) # Need a bit of time to return correct unit + time.sleep(0.2) # Need a bit of time to return correct unit return True def tc_types(self): - """ List all configuration of all channels """ + """List all configuration of all channels""" types = {} - command = 'TTYP? ' + command = "TTYP? " for i in range(1, 17): types[i] = self.comm(command + str(i)) return types def read_open_status(self): - """ Check for open output on all channels """ + """Check for open output on all channels""" for i in range(1, 17): self.read_channel(i) - command = 'OPEN?' + command = "OPEN?" # TODO: Parse the output open_status = bin(int(self.comm(command))) return open_status def read_serial_number(self): - """ Return the serial number of the device """ - return self.comm('*IDN?') - + """Return the serial number of the device""" + return self.comm("*IDN?") + def read_channel(self, channel): - """ Read the actual value of a channel """ - command = 'CHAN?' + """Read the actual value of a channel""" + command = "CHAN?" current_channel = self.comm(command) if int(current_channel) == channel: - command = 'MEAS? ' + str(channel) + command = "MEAS? " + str(channel) value = self.comm(command) else: - command = 'CHAN ' + str(channel) + command = "CHAN " + str(channel) self.comm(command) - command = 'MEAS? ' + str(channel) + command = "MEAS? " + str(channel) value = self.comm(command) return float(value) -if __name__ == '__main__': - SRS = SRS_SR630('/dev/ttyUSB0') + +if __name__ == "__main__": + SRS = SRS_SR630("/dev/ttyUSB0") print(SRS.read_serial_number()) print(str(SRS.read_channel(2))) - print(SRS.set_unit(2, 'CENT')) + print(SRS.set_unit(2, "CENT")) print(str(SRS.read_channel(2))) print(SRS.read_open_status()) print(SRS.tc_types()) - #print(SRS.config_analog_channel(1, follow_temperature=False, value=0.2)) + # print(SRS.config_analog_channel(1, follow_temperature=False, value=0.2)) diff --git a/PyExpLabSys/drivers/stahl_hv_400.py b/PyExpLabSys/drivers/stahl_hv_400.py index 2a8c4429..3de17a10 100644 --- a/PyExpLabSys/drivers/stahl_hv_400.py +++ b/PyExpLabSys/drivers/stahl_hv_400.py @@ -2,109 +2,118 @@ from __future__ import print_function import serial from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class StahlHV400(object): - """ Driver for Stahl HV 400 Ion Optics Supply """ - def __init__(self, port='/dev/ttyUSB0'): - """ Driver for Stahl HV 400 Ion Optics Supply """ + """Driver for Stahl HV 400 Ion Optics Supply""" + + def __init__(self, port="/dev/ttyUSB0"): + """Driver for Stahl HV 400 Ion Optics Supply""" self.serial = serial.Serial(port, 9600, timeout=0.5) self.serial_number = None self.max_voltage = None self.number_of_channels = None self.bipolar = None - self.identify_device() # Update device info + self.identify_device() # Update device info def comm(self, command): - """ Perform actual communication with instrument """ - command = command + '\r' - command = command.encode('ascii') - reply = '' + """Perform actual communication with instrument""" + command = command + "\r" + command = command.encode("ascii") + reply = "" iterations = 0 while (len(reply) < 2) and (iterations < 20): self.serial.write(command) reply = self.serial.readline() - reply = reply.decode('latin-1') - iterations = iterations + 1 # Make sure not to end in infinite lop + reply = reply.decode("latin-1") + iterations = iterations + 1 # Make sure not to end in infinite lop return reply[:-1] def identify_device(self): - """ Return the serial number of the device """ - reply = self.comm('IDN') + """Return the serial number of the device""" + reply = self.comm("IDN") reply = reply.split() self.serial_number = reply[0] self.max_voltage = int(reply[1]) self.number_of_channels = int(reply[2]) - self.bipolar = (reply[3][0] == 'b') + self.bipolar = reply[3][0] == "b" return reply def query_voltage(self, channel): - """ Something is all wrong here... """ + """Something is all wrong here...""" error = 20 while error > 0: try: - reply = self.comm(self.serial_number + ' Q' + str(channel).zfill(2)) - value = float(reply[:-1].replace(',', '.')) + reply = self.comm(self.serial_number + " Q" + str(channel).zfill(2)) + value = float(reply[:-1].replace(",", ".")) error = -1 except ValueError: error = error - 1 - value = '-99999' + value = "-99999" return value def set_voltage(self, channel, value): - """ Set the voltage of a channel """ + """Set the voltage of a channel""" if self.bipolar: fraction = float(value) / (2 * self.max_voltage) + 0.5 else: fraction = float(value) / self.max_voltage assert isinstance(channel, int) - self.comm(self.serial_number + ' CH' + str(channel).zfill(2) + - ' ' + '{0:.6f}'.format(fraction)) - command = self.serial_number + ' DIS L CH' + str(channel).zfill(2) + ' {0:.2f}V' + self.comm( + self.serial_number + + " CH" + + str(channel).zfill(2) + + " " + + "{0:.6f}".format(fraction) + ) + command = self.serial_number + " DIS L CH" + str(channel).zfill(2) + " {0:.2f}V" self.comm(command.format(value)) - return True # Consider to run check_channel_status + return True # Consider to run check_channel_status def read_temperature(self): - """ Read temperature of device """ - reply = self.comm(self.serial_number + ' TEMP') - temperature = reply[4:-2] # Remove word TEMP and unit + """Read temperature of device""" + reply = self.comm(self.serial_number + " TEMP") + temperature = reply[4:-2] # Remove word TEMP and unit return float(temperature) def check_channel_status(self): - """ Check status of channel """ - reply = self.comm(self.serial_number + ' LOCK') + """Check status of channel""" + reply = self.comm(self.serial_number + " LOCK") channel_1_4 = bin(ord(reply[0]))[-4:] channel_5_8 = bin(ord(reply[1]))[-4:] channel_status = {} - channel_status[4] = channel_1_4[0] == '0' - channel_status[3] = channel_1_4[1] == '0' - channel_status[2] = channel_1_4[2] == '0' - channel_status[1] = channel_1_4[3] == '0' - channel_status[8] = channel_5_8[0] == '0' - channel_status[7] = channel_5_8[1] == '0' - channel_status[6] = channel_5_8[2] == '0' - channel_status[5] = channel_5_8[3] == '0' + channel_status[4] = channel_1_4[0] == "0" + channel_status[3] = channel_1_4[1] == "0" + channel_status[2] = channel_1_4[2] == "0" + channel_status[1] = channel_1_4[3] == "0" + channel_status[8] = channel_5_8[0] == "0" + channel_status[7] = channel_5_8[1] == "0" + channel_status[6] = channel_5_8[2] == "0" + channel_status[5] = channel_5_8[3] == "0" return channel_status -if __name__ == '__main__': - HV400 = StahlHV400('/dev/ttyUSB0') + +if __name__ == "__main__": + HV400 = StahlHV400("/dev/ttyUSB0") HV400.set_voltage(1, -159.1) HV400.set_voltage(2, -69.1) - #HV400.set_voltage(3, -47.9) # 55.9 - #HV400.set_voltage(4, -47.9) # 46.9 - #HV400.set_voltage(5, -44.9) # 44.9 - #HV400.set_voltage(6, 0) - #HV400.set_voltage(7, 0) - #HV400.set_voltage(8, 0) - #print(HV400.read_temperature()) + # HV400.set_voltage(3, -47.9) # 55.9 + # HV400.set_voltage(4, -47.9) # 46.9 + # HV400.set_voltage(5, -44.9) # 44.9 + # HV400.set_voltage(6, 0) + # HV400.set_voltage(7, 0) + # HV400.set_voltage(8, 0) + # print(HV400.read_temperature()) print(HV400.check_channel_status()) - status = (HV400.check_channel_status()) + status = HV400.check_channel_status() print(False in status) - #print(HV400.query_voltage(1)) - #print(HV400.query_voltage(2)) - #print(HV400.query_voltage(3)) - #print(HV400.query_voltage(4)) - #print(HV400.query_voltage(5)) - #print(HV400.query_voltage(6)) - #print(HV400.query_voltage(7)) - #print(HV400.query_voltage(8)) + # print(HV400.query_voltage(1)) + # print(HV400.query_voltage(2)) + # print(HV400.query_voltage(3)) + # print(HV400.query_voltage(4)) + # print(HV400.query_voltage(5)) + # print(HV400.query_voltage(6)) + # print(HV400.query_voltage(7)) + # print(HV400.query_voltage(8)) diff --git a/PyExpLabSys/drivers/stmicroelectronics_ais328dq.py b/PyExpLabSys/drivers/stmicroelectronics_ais328dq.py index 12f17042..55eb8543 100644 --- a/PyExpLabSys/drivers/stmicroelectronics_ais328dq.py +++ b/PyExpLabSys/drivers/stmicroelectronics_ais328dq.py @@ -1,61 +1,63 @@ """ Driver for STMicroelectronics AIS328DQTR 3 axis accelerometer """ import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: pass else: import smbus import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class AIS328DQTR(object): - """ Class for reading accelerometer output """ + """Class for reading accelerometer output""" def __init__(self): self.bus = smbus.SMBus(1) self.device_address = 0x18 # Set output data rate to 100, bandwidth low-pass cut-off to 74.5Hz - print(hex(int('00101111',2))) - self.bus.write_byte_data(self.device_address, 0x20, 0x2f) + print(hex(int("00101111", 2))) + self.bus.write_byte_data(self.device_address, 0x20, 0x2F) # Set full scale range to 2g - self.full_scale = 2 # This should go in a self-consistent table... + self.full_scale = 2 # This should go in a self-consistent table... self.bus.write_byte_data(self.device_address, 0x23, 0x80) time.sleep(0.5) def who_am_i(self): - """ Device identification """ + """Device identification""" id_byte = self.bus.read_byte_data(self.device_address, 0x0F) return id_byte - - def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" byte1 = self.bus.read_byte_data(self.device_address, 0x28) byte2 = self.bus.read_byte_data(self.device_address, 0x29) x_value = byte2 * 256 + byte1 - if x_value > (2**15)-1: + if x_value > (2**15) - 1: x_value = x_value - (2**16) x_value = 1.0 * x_value * self.full_scale / (2**15) byte1 = self.bus.read_byte_data(self.device_address, 0x2A) byte2 = self.bus.read_byte_data(self.device_address, 0x2B) y_value = byte2 * 256 + byte1 - if y_value > (2**15)-1: + if y_value > (2**15) - 1: y_value = y_value - (2**16) y_value = 1.0 * y_value * self.full_scale / (2**15) byte1 = self.bus.read_byte_data(self.device_address, 0x2C) byte2 = self.bus.read_byte_data(self.device_address, 0x2D) z_value = byte2 * 256 + byte1 - if z_value > (2**15)-1: + if z_value > (2**15) - 1: z_value = z_value - (2**16) z_value = 1.0 * z_value * self.full_scale / (2**15) - - return(x_value, y_value, z_value) -if __name__ == '__main__': + return (x_value, y_value, z_value) + + +if __name__ == "__main__": AIS = AIS328DQTR() print(bin(AIS.who_am_i())) for i in range(0, 5): diff --git a/PyExpLabSys/drivers/stmicroelectronics_l3g4200d.py b/PyExpLabSys/drivers/stmicroelectronics_l3g4200d.py index 6f1e9c6b..772c8b00 100644 --- a/PyExpLabSys/drivers/stmicroelectronics_l3g4200d.py +++ b/PyExpLabSys/drivers/stmicroelectronics_l3g4200d.py @@ -1,16 +1,19 @@ """ Driver for STMicroelectronics L3G4200D 3 axis gyroscope """ import time import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: pass else: import smbus from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) + class L3G4200D(object): - """ Class for reading accelerometer output """ + """Class for reading accelerometer output""" def __init__(self): self.bus = smbus.SMBus(1) @@ -18,42 +21,43 @@ def __init__(self): # Set output data rate to 200, bandwidth cut-off to 12.5Hz self.bus.write_byte_data(self.device_address, 0x20, 0x4F) # Set full scale range, 0x00: 250dps, 0x30: 2000dps, block until lsb and msb are read - self.full_scale = 250 # This should go in a self-consistent table... + self.full_scale = 250 # This should go in a self-consistent table... self.bus.write_byte_data(self.device_address, 0x23, 0x80) time.sleep(0.5) def who_am_i(self): - """ Device identification """ + """Device identification""" id_byte = self.bus.read_byte_data(self.device_address, 0x0F) return id_byte def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" byte1 = self.bus.read_byte_data(self.device_address, 0x28) byte2 = self.bus.read_byte_data(self.device_address, 0x29) x_value = byte2 * 256 + byte1 - if x_value > (2**15)-1: + if x_value > (2**15) - 1: x_value = x_value - 2**16 x_value = 1.0 * x_value * self.full_scale / (2**15) byte1 = self.bus.read_byte_data(self.device_address, 0x2A) byte2 = self.bus.read_byte_data(self.device_address, 0x2B) y_value = byte2 * 256 + byte1 - if y_value > (2**15)-1: + if y_value > (2**15) - 1: y_value = y_value - 2**16 y_value = 1.0 * y_value * self.full_scale / (2**15) byte1 = self.bus.read_byte_data(self.device_address, 0x2C) byte2 = self.bus.read_byte_data(self.device_address, 0x2D) z_value = byte2 * 256 + byte1 - if z_value > (2**15)-1: + if z_value > (2**15) - 1: z_value = z_value - 2**16 z_value = 1.0 * z_value * self.full_scale / (2**15) - return(x_value, y_value, z_value) + return (x_value, y_value, z_value) + -if __name__ == '__main__': +if __name__ == "__main__": L3G = L3G4200D() print(bin(L3G.who_am_i())) for i in range(0, 5): diff --git a/PyExpLabSys/drivers/tdk_lambda_z_series.py b/PyExpLabSys/drivers/tdk_lambda_z_series.py index 1ff23340..894358a0 100644 --- a/PyExpLabSys/drivers/tdk_lambda_z_series.py +++ b/PyExpLabSys/drivers/tdk_lambda_z_series.py @@ -4,13 +4,13 @@ class TdkLambdaZ(object): - def __init__(self, device: str = '/dev/ttyACM0'): + def __init__(self, device: str = "/dev/ttyACM0"): self.ser = serial.Serial(device, 9600, stopbits=1, timeout=2) def _comm(self, command: str) -> str: - command = command + '\r' - self.ser.write(command.encode('ascii')) - return_string = ''.encode('ascii') + command = command + "\r" + self.ser.write(command.encode("ascii")) + return_string = "".encode("ascii") while True: next_char = self.ser.read(1) if ord(next_char) == 13: @@ -40,7 +40,7 @@ def set_current(self, current: float) -> None: self.current(current) def test_connection(self): - reply = self._comm('ADR 01') + reply = self._comm("ADR 01") print(reply) def remote_state(self, local: bool = False, remote: bool = False) -> str: @@ -53,14 +53,14 @@ def remote_state(self, local: bool = False, remote: bool = False) -> str: if local and remote: pass elif local: - command = 'RMT LOC' + command = "RMT LOC" elif remote: - command = 'RMT REM' + command = "RMT REM" # Also a Local Lockout mode exists, this is not implemented if command is not None: self._comm(command) - reply = self._comm('RMT?') + reply = self._comm("RMT?") return reply def _read_float(self, command: str) -> float: @@ -81,23 +81,23 @@ def _read_float(self, command: str) -> float: def voltage(self, value: Optional[float] = None) -> float: if value is not None: - command = 'PV {:.3f}'.format(value) + command = "PV {:.3f}".format(value) print(self._comm(command)) - actual_voltage = self._read_float('MV?') + actual_voltage = self._read_float("MV?") return actual_voltage def voltage_protection(self, value: Optional[float] = None) -> float: if value is not None: - command = 'OVP {:.3f}'.format(value) + command = "OVP {:.3f}".format(value) print(self._comm(command)) - actual_protection_voltage = self._read_float('OVP?') + actual_protection_voltage = self._read_float("OVP?") return actual_protection_voltage def current(self, value: Optional[float] = None) -> float: if value is not None: - command = 'PC {:.3f}'.format(value) + command = "PC {:.3f}".format(value) print(self._comm(command)) - actual_current = self._read_float('MC?') + actual_current = self._read_float("MC?") return actual_current def output_state(self, on: bool = False, off: bool = False) -> bool: @@ -105,23 +105,23 @@ def output_state(self, on: bool = False, off: bool = False) -> bool: if on and off: pass elif on: - command = 'OUT ON' + command = "OUT ON" elif off: - command = 'OUT OFF' + command = "OUT OFF" if command is not None: self._comm(command) - reply = self._comm('OUT?') - state = reply == 'ON' + reply = self._comm("OUT?") + state = reply == "ON" return state -if __name__ == '__main__': +if __name__ == "__main__": tdk = TdkLambdaZ() tdk.test_connection() - print('Remote state: {}'.format(tdk.remote_state(remote=True))) - print('Output state: {}'.format(tdk.output_state(on=True))) - print('Voltage proction level: {}'.format(tdk.voltage_protection(5))) + print("Remote state: {}".format(tdk.remote_state(remote=True))) + print("Output state: {}".format(tdk.output_state(on=True))) + print("Voltage proction level: {}".format(tdk.voltage_protection(5))) print() print(tdk.current(0.2)) diff --git a/PyExpLabSys/drivers/teltonika_rut.py b/PyExpLabSys/drivers/teltonika_rut.py index 66bc6b0b..f63356cb 100644 --- a/PyExpLabSys/drivers/teltonika_rut.py +++ b/PyExpLabSys/drivers/teltonika_rut.py @@ -4,96 +4,81 @@ class TeltonikaRut(object): - def __init__(self, passwd, ip_address='192.168.1.1'): + def __init__(self, passwd, ip_address="192.168.1.1"): self.ip = ip_address self.passwd = passwd - self.session = '00000000000000000000000000000000' + self.session = "00000000000000000000000000000000" self.session = self.init_session() def _comm(self, params): - payload = { - 'jsonrpc': '2.0', - 'id': 1, - 'method': 'call' - } - payload.update( - {'params': [self.session] + params} - ) - url = 'http://{}/ubus'.format(self.ip) + payload = {"jsonrpc": "2.0", "id": 1, "method": "call"} + payload.update({"params": [self.session] + params}) + url = "http://{}/ubus".format(self.ip) r = requests.post(url, data=json.dumps(payload)) reply = r.json() - result = reply['result'] + result = reply["result"] return result def init_session(self): - params = ['session', 'login', {'username': 'root', 'password': self.passwd}] + params = ["session", "login", {"username": "root", "password": self.passwd}] reply = self._comm(params) - session_key = reply[1]['ubus_rpc_session'] + session_key = reply[1]["ubus_rpc_session"] return session_key def send_sms(self, phone_number, text): """ Send SMS """ - send_command = '{} {}'.format(phone_number, text) + send_command = "{} {}".format(phone_number, text) params = [ - 'file', 'exec', { - 'command': 'gsmctl', 'params': ['-S', '--send', send_command] - } + "file", + "exec", + {"command": "gsmctl", "params": ["-S", "--send", send_command]}, ] reply = self._comm(params) - reply_text = reply[1]['stdout'] + reply_text = reply[1]["stdout"] return reply_text def rssi(self): """ Obtain signal strength """ - params = [ - 'file', 'exec', {'command': 'gsmctl', 'params': ["-q"]} - ] + params = ["file", "exec", {"command": "gsmctl", "params": ["-q"]}] reply = self._comm(params) - rssi = int(reply[1]['stdout']) + rssi = int(reply[1]["stdout"]) return rssi def cell_information(self): - params = [ - 'file', 'exec', {'command': 'gsmctl', 'params': ["--serving"]} - ] + params = ["file", "exec", {"command": "gsmctl", "params": ["--serving"]}] reply = self._comm(params) - info = reply[1]['stdout'] + info = reply[1]["stdout"] - if info.find('LTE') > 0: - pos = info.find('LTE') + 5 - data = info[pos:].split(',') + if info.find("LTE") > 0: + pos = info.find("LTE") + 5 + data = info[pos:].split(",") cell_info = { - 'mcc': data[1], - 'mnc': data[2], - 'lac': int(data[9], 16), - 'cell_id': int(data[3], 16) + "mcc": data[1], + "mnc": data[2], + "lac": int(data[9], 16), + "cell_id": int(data[3], 16), } - elif info.find('GSM') > 0: - pos = info.find('GSM') + 5 - data = info[pos:].split(',') + elif info.find("GSM") > 0: + pos = info.find("GSM") + 5 + data = info[pos:].split(",") cell_info = { - 'mcc': data[0], - 'mnc': data[1], - 'lac': int(data[2], 16), - 'cell_id': int(data[3], 16) + "mcc": data[0], + "mnc": data[1], + "lac": int(data[2], 16), + "cell_id": int(data[3], 16), } else: # Unsupported network, or no SIM at all - cell_info = { - 'mcc': 0, - 'mnc': 0, - 'lac': 0, - 'cell_id': 0 - } + cell_info = {"mcc": 0, "mnc": 0, "lac": 0, "cell_id": 0} # print('Lac: {}, Lac dec: {}'.format(lac, int(lac, 16))) return cell_info -if __name__ == '__main__': +if __name__ == "__main__": ip = sys.argv[1] pw = sys.argv[2] @@ -105,5 +90,5 @@ def cell_information(self): print(tr.cell_information()) print() - print('Sending SMS') - print(tr.send_sms('0045number', 'Text Text')) + print("Sending SMS") + print(tr.send_sms("0045number", "Text Text")) diff --git a/PyExpLabSys/drivers/tenma.py b/PyExpLabSys/drivers/tenma.py index 36d3d54d..93a9b8d5 100644 --- a/PyExpLabSys/drivers/tenma.py +++ b/PyExpLabSys/drivers/tenma.py @@ -27,6 +27,7 @@ from serial import Serial from PyExpLabSys.common.supported_versions import python2_and_3 + # Mark this module as supporting both Python 2 and 3 python2_and_3(__file__) @@ -55,8 +56,12 @@ def __init__(self, device, sleep_after_command=0.1): command, to make sure that the device is ready for another one. Defaults to 0.1, but quick tests suggest that 0.05 might be enough. """ - LOG.info('%s init on device: %s, sleep_after_command=%s', self.__class__.__name__, - device, sleep_after_command) + LOG.info( + "%s init on device: %s, sleep_after_command=%s", + self.__class__.__name__, + device, + sleep_after_command, + ) super(TenmaBase, self).__init__(device) self.sleep_after_command = sleep_after_command @@ -68,16 +73,16 @@ def com(self, command, decode_reply=True): decode_reply (bool): (Optional) Whether the reply should be utf-8 decoded to return a unicode object """ - LOG.debug('Send command: %s', command) - self.write(command.encode('utf-8')) + LOG.debug("Send command: %s", command) + self.write(command.encode("utf-8")) sleep(self.sleep_after_command) - if command.endswith('?'): + if command.endswith("?"): reply = self.read(self.in_waiting) if decode_reply: - reply = reply.decode('utf-8') # pylint: disable=redefined-variable-type - LOG.debug('Got (utf-8) decoded reply: %s', repr(reply)) + reply = reply.decode("utf-8") # pylint: disable=redefined-variable-type + LOG.debug("Got (utf-8) decoded reply: %s", repr(reply)) else: - LOG.debug('Got reply: %s', repr(reply)) + LOG.debug("Got reply: %s", repr(reply)) return reply # Spec command 1 @@ -87,8 +92,8 @@ def set_current(self, current): Args: current (float): The current to set """ - LOG.debug('set_current called with: %s', current) - self.com('ISET1:{:.3f}'.format(current)) + LOG.debug("set_current called with: %s", current) + self.com("ISET1:{:.3f}".format(current)) # Spec command 2 def get_current(self): @@ -97,8 +102,8 @@ def get_current(self): Returns: float: The current setpoint """ - LOG.debug('get_current called') - current_reply = self.com('ISET1?') + LOG.debug("get_current called") + current_reply = self.com("ISET1?") return float(current_reply.strip()) # Spec command 3 @@ -108,8 +113,8 @@ def set_voltage(self, voltage): Args: voltage (float): The voltage to set """ - LOG.debug('set_voltage called with: %s', voltage) - self.com('VSET1:{:.2f}'.format(voltage)) + LOG.debug("set_voltage called with: %s", voltage) + self.com("VSET1:{:.2f}".format(voltage)) # Spec command 4 def get_voltage(self): @@ -119,8 +124,8 @@ def get_voltage(self): Returns: float: The voltage setpoint """ - LOG.debug('get_voltage called') - voltage_reply = self.com('VSET1?') + LOG.debug("get_voltage called") + voltage_reply = self.com("VSET1?") return float(voltage_reply.strip()) # Spec command 5 @@ -130,8 +135,8 @@ def get_actual_current(self): Returns: float: The actual current """ - LOG.debug('get_actual_current called') - current_reply = self.com('IOUT1?') + LOG.debug("get_actual_current called") + current_reply = self.com("IOUT1?") return float(current_reply.strip()) # Spec command 6 @@ -141,8 +146,8 @@ def get_actual_voltage(self): Returns: float: The actual coltage """ - LOG.debug('get_actual_voltage called') - voltage_reply = self.com('VOUT1?') + LOG.debug("get_actual_voltage called") + voltage_reply = self.com("VOUT1?") return float(voltage_reply.strip()) # Spec command 7 @@ -151,8 +156,8 @@ def set_beep(self, on_off): on_off (bool): The beep status to set """ - LOG.debug('set_beep called with: %s', on_off) - self.com('BEEP' + ('1' if on_off else '0')) + LOG.debug("set_beep called with: %s", on_off) + self.com("BEEP" + ("1" if on_off else "0")) # Spec command 8 def set_output(self, on_off): @@ -160,8 +165,8 @@ def set_output(self, on_off): on_off (bool): The otput status to set """ - LOG.debug('set_output called with: %s', on_off) - self.com('OUT' + ('1' if on_off else '0')) + LOG.debug("set_output called with: %s", on_off) + self.com("OUT" + ("1" if on_off else "0")) # Spec command 9 def status(self): @@ -181,22 +186,22 @@ def status(self): Returns: dict: See fields specification above """ - LOG.debug('status called') - status_byte = ord(self.com('STATUS?', decode_reply=False)) + LOG.debug("status called") + status_byte = ord(self.com("STATUS?", decode_reply=False)) # Convert to binary representation, zeropad and reverse - status_bitstring = '{:0>8b}'.format(status_byte)[::-1] + status_bitstring = "{:0>8b}".format(status_byte)[::-1] # Form a status dict status = { - 'channel1_mode': 'CV' if status_bitstring[0] == '1' else 'CC', - 'channel2_mode': 'CV' if status_bitstring[1] == '1' else 'CC', - 'beep_on': status_bitstring[4] == '1', - 'lock_on': status_bitstring[5] == '1', - 'output_on': status_bitstring[6] == '1', + "channel1_mode": "CV" if status_bitstring[0] == "1" else "CC", + "channel2_mode": "CV" if status_bitstring[1] == "1" else "CC", + "beep_on": status_bitstring[4] == "1", + "lock_on": status_bitstring[5] == "1", + "output_on": status_bitstring[6] == "1", } - tracking_bits = status_bitstring[2: 4] - tracking_translation = {'00': 'Independent', '01': 'Series', '11': 'Parallel'} - status['tracking_status'] = tracking_translation[tracking_bits] + tracking_bits = status_bitstring[2:4] + tracking_translation = {"00": "Independent", "01": "Series", "11": "Parallel"} + status["tracking_status"] = tracking_translation[tracking_bits] return status # Spec command 10 @@ -206,8 +211,8 @@ def get_identification(self): Returns: str: E.g: 'TENMA 72-2535 V2.0' """ - LOG.debug('get_identification called') - return self.com('*IDN?') + LOG.debug("get_identification called") + return self.com("*IDN?") # Spec command 11 def recall_memory(self, memory_number): @@ -221,11 +226,11 @@ def recall_memory(self, memory_number): Raises: ValueError: On invalid memory_number """ - LOG.debug('recall_memory called with: %s', memory_number) + LOG.debug("recall_memory called with: %s", memory_number) if memory_number not in range(1, 6): - msg = 'Memory number must be int in range: {}' + msg = "Memory number must be int in range: {}" raise ValueError(msg.format(list(range(1, 6)))) - self.com('RCL{}'.format(memory_number)) + self.com("RCL{}".format(memory_number)) # Spec command 12 def save_memory(self, memory_number): @@ -241,11 +246,11 @@ def save_memory(self, memory_number): ValueError: On invalid memory_number """ - LOG.debug('save_memory called with: %s', memory_number) + LOG.debug("save_memory called with: %s", memory_number) if memory_number not in range(1, 6): - msg = 'Memory number must be int in range: {}' + msg = "Memory number must be int in range: {}" raise ValueError(msg.format(list(range(1, 6)))) - self.com('SAV{}'.format(memory_number)) + self.com("SAV{}".format(memory_number)) # Spec command 13 def set_overcurrent_protection(self, on_off): @@ -254,8 +259,8 @@ def set_overcurrent_protection(self, on_off): Args: on_off (bool): The overcurrent protection mode to set """ - LOG.debug('set_overcurrent_protection called with: %s', on_off) - self.com('OCP' + ('1' if on_off else '0')) + LOG.debug("set_overcurrent_protection called with: %s", on_off) + self.com("OCP" + ("1" if on_off else "0")) # Spec command 14 def set_overvoltage_protection(self, on_off): @@ -266,8 +271,8 @@ def set_overvoltage_protection(self, on_off): Args: on_off (bool): The overvoltage protection mode to set """ - LOG.debug('set_overvoltage_protection called with: %s', on_off) - self.com('OVP' + ('1' if on_off else '0')) + LOG.debug("set_overvoltage_protection called with: %s", on_off) + self.com("OVP" + ("1" if on_off else "0")) class Tenma722535(TenmaBase): @@ -287,26 +292,26 @@ def main(): logging.basicConfig(level=logging.INFO) from random import random - device = '/dev/serial/by-id/usb-USB_Vir_USB_Virtual_COM_NT2009101400-if00' + device = "/dev/serial/by-id/usb-USB_Vir_USB_Virtual_COM_NT2009101400-if00" tenma = Tenma722535(device) - print('ID:', tenma.get_identification()) - print('Status:', tenma.status()) + print("ID:", tenma.get_identification()) + print("Status:", tenma.status()) current = random() - print('\nSet current to:', current) + print("\nSet current to:", current) tenma.set_current(current) - print('Read current', tenma.get_current()) + print("Read current", tenma.get_current()) voltage = random() - print('\nSet voltage to:', voltage) + print("\nSet voltage to:", voltage) tenma.set_voltage(voltage) - print('Read voltage', tenma.get_voltage()) + print("Read voltage", tenma.get_voltage()) tenma.set_output(True) - print('\nActual current:', tenma.get_actual_current()) - print('Actual voltage:', tenma.get_actual_voltage()) + print("\nActual current:", tenma.get_actual_current()) + print("Actual voltage:", tenma.get_actual_voltage()) - print('\nOvercurrent and overvoltage protection, watch the LEDS switch') + print("\nOvercurrent and overvoltage protection, watch the LEDS switch") tenma.set_overcurrent_protection(True) sleep(0.5) tenma.set_overcurrent_protection(False) @@ -315,14 +320,14 @@ def main(): sleep(0.5) tenma.set_overvoltage_protection(False) - print('\nSpeed test') + print("\nSpeed test") t0 = time() for _ in range(10): value = tenma.get_voltage() now = time() - print('Voltage:', value, 'read speed', now - t0, end=' ') + print("Voltage:", value, "read speed", now - t0, end=" ") t0 = now -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/ti_ads1115.py b/PyExpLabSys/drivers/ti_ads1115.py index 51198809..13ad7150 100644 --- a/PyExpLabSys/drivers/ti_ads1115.py +++ b/PyExpLabSys/drivers/ti_ads1115.py @@ -6,9 +6,11 @@ class TI_ADS11x5(object): """ TI11x5 analog in """ + def __init__(self): self.bus = smbus.SMBus(1) self.device_address = 0x49 + # fmt: off self.pga = { 2/3: 0x0000, 1: 0x0200, @@ -17,6 +19,7 @@ def __init__(self): 8: 0x0800, 16: 0x0A00 } + # fmt: on def read_sample(self, pga=1): config = 3 # Disable comperator @@ -40,6 +43,6 @@ def read_sample(self, pga=1): return value -if __name__ == '__main__': +if __name__ == "__main__": ads = TI_ADS11x5() print(ads.read_sample(1)) diff --git a/PyExpLabSys/drivers/tsl45315.py b/PyExpLabSys/drivers/tsl45315.py index f015324e..b7734526 100644 --- a/PyExpLabSys/drivers/tsl45315.py +++ b/PyExpLabSys/drivers/tsl45315.py @@ -1,29 +1,27 @@ """ Driver for TSL45315 Digital Ambient Light Sensor """ import os -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: pass else: import smbus import time from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) class TSL45315(object): - """ Class for reading pressure and temperature from - TSL45315 Digital Ambient Light Sensor """ + """Class for reading pressure and temperature from + TSL45315 Digital Ambient Light Sensor""" def __init__(self, integration_time=0): # Integration times: # 0: 400ms # 1: 200ms # 2: 100ms - integration_times = { - 0: 0.4, - 1: 0.2, - 2: 0.1 - } + integration_times = {0: 0.4, 1: 0.2, 2: 0.1} self.integration_time = integration_times[integration_time] self.multiplier = 0.4 / self.integration_time print(self.multiplier) @@ -38,7 +36,7 @@ def read_id(self): print(value) def read_values(self): - """ Read a value from the sensor """ + """Read a value from the sensor""" time.sleep(self.integration_time) data = self.bus.read_i2c_block_data(self.device_address, 0x04 | 0x80, 2) @@ -46,7 +44,7 @@ def read_values(self): return self.multiplier * value -if __name__ == '__main__': +if __name__ == "__main__": SENSOR = TSL45315(integration_time=0) # print(SENSOR.read_id()) for i in range(0, 25): diff --git a/PyExpLabSys/drivers/vaisala_dmt143.py b/PyExpLabSys/drivers/vaisala_dmt143.py index feff55d7..774193c2 100644 --- a/PyExpLabSys/drivers/vaisala_dmt143.py +++ b/PyExpLabSys/drivers/vaisala_dmt143.py @@ -3,8 +3,9 @@ import serial -class VaisalaDMT143(): +class VaisalaDMT143: """Driver for Vaisala DMT 143""" + def __init__(self, port): self.serial = serial.Serial( port, @@ -12,14 +13,14 @@ def __init__(self, port): timeout=1, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, - bytesize=serial.EIGHTBITS + bytesize=serial.EIGHTBITS, ) def comm(self, command): """ Handle actual serial communication with instrument. """ - actual_command = (command + '\r').encode('ascii') + actual_command = (command + "\r").encode("ascii") self.serial.write(actual_command) time.sleep(1) in_waiting = self.serial.inWaiting() @@ -30,32 +31,28 @@ def device_information(self): """ Return information about the device. """ - command = '?' + command = "?" info_raw = self.comm(command) info = info_raw.strip() - info = info.split('\n') + info = info.split("\n") model = info[0].strip() - serial_nr = info[1].split(' ')[-1].strip() - pressure = info[13].split(' ')[-2].strip() + serial_nr = info[1].split(" ")[-1].strip() + pressure = info[13].split(" ")[-2].strip() # for item in info: # print(item.strip()) - info_dict = { - 'model': model, - 'serial_nr': serial_nr, - 'pressure': pressure - } + info_dict = {"model": model, "serial_nr": serial_nr, "pressure": pressure} return info_dict def current_errors(self): """ Repport current error message, empty string if no errors. """ - command = 'ERRS' + command = "ERRS" errors_raw = self.comm(command) - if 'No errors' in errors_raw: - error_list = '' + if "No errors" in errors_raw: + error_list = "" else: error_list = errors_raw return error_list @@ -64,7 +61,7 @@ def set_reference_pressure(self, pressure: float): """ Set reference pressure used for internal calculations. """ - command = 'XPRES {:.5f}'.format(pressure) + command = "XPRES {:.5f}".format(pressure) reply = self.comm(command) print(reply) # todo! @@ -72,18 +69,18 @@ def water_level(self): """ The actual measurements from the device. """ - command = 'SEND' + command = "SEND" raw_value = self.comm(command) # One could consider to use the FORMAT command # to make the output less cryptic... - split_values = raw_value.split(' ') + split_values = raw_value.split(" ") dew_point = float(split_values[2]) dew_point_atm = float(split_values[6]) vol_conc = float(split_values[9]) return_dict = { - 'dew_point': dew_point, - 'dew_point_atm': dew_point_atm, - 'vol_conc': vol_conc + "dew_point": dew_point, + "dew_point_atm": dew_point_atm, + "vol_conc": vol_conc, } return return_dict @@ -92,17 +89,17 @@ def main(): """ Main function, used only for test runs. """ - port = '/dev/ttyUSB0' + port = "/dev/ttyUSB0" dmt = VaisalaDMT143(port=port) # print(dmt.set_reference_pressure(1)) current_errors = dmt.current_errors() if current_errors: - print('Error! ' + current_errors) + print("Error! " + current_errors) # print(dmt.device_information()) print(dmt.water_level()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/vivo_technologies.py b/PyExpLabSys/drivers/vivo_technologies.py index 00fb7d70..cf3c522c 100644 --- a/PyExpLabSys/drivers/vivo_technologies.py +++ b/PyExpLabSys/drivers/vivo_technologies.py @@ -4,6 +4,7 @@ import evdev import threading import time + try: import Queue except ImportError: @@ -20,7 +21,7 @@ def detect_barcode_device(): str: The Barcode Scanner device path """ barcode_device = None - for device_string in glob.glob('/dev/input/event*'): + for device_string in glob.glob("/dev/input/event*"): try: tmp_dev = evdev.InputDevice(device_string) except OSError: @@ -29,7 +30,7 @@ def detect_barcode_device(): device_description = str(tmp_dev) tmp_dev.close() - if 'Barcode Reader' in device_description: + if "Barcode Reader" in device_description: barcode_device = device_string break @@ -45,14 +46,14 @@ def __init__(self, device_path): def read_barcode(self): """Wait for a barcode and return it""" - out = '' + out = "" for event in self.dev.read_loop(): if event.type == evdev.ecodes.EV_KEY: # Save the event temporarily to introspect it data = evdev.categorize(event) if data.keystate == 1: # Down events only - key_lookup = SCANCODES.get(data.scancode, '?') - if key_lookup == 'CRLF': + key_lookup = SCANCODES.get(data.scancode, "?") + if key_lookup == "CRLF": break else: out += key_lookup @@ -74,16 +75,16 @@ def __init__(self, device_path): def run(self): """The threaded run method""" - read_so_far = '' + read_so_far = "" for event in self.dev.read_loop(): if event.type == evdev.ecodes.EV_KEY: # Save the event temporarily to introspect it data = evdev.categorize(event) if data.keystate == 1: # Down events only - key_lookup = SCANCODES.get(data.scancode, '?') - if key_lookup == 'CRLF': + key_lookup = SCANCODES.get(data.scancode, "?") + if key_lookup == "CRLF": self._barcode_queue.put(read_so_far) - read_so_far = '' + read_so_far = "" else: read_so_far += key_lookup @@ -123,26 +124,74 @@ def close(self): SCANCODES = { # Scancode: ASCIICode - 0: None, 1: u'ESC', 2: u'1', 3: u'2', 4: u'3', 5: u'4', 6: u'5', 7: u'6', - 8: u'7', 9: u'8', 10: u'9', 11: u'0', 12: u'-', 13: u'=', 14: u'BKSP', - 15: u'TAB', 16: u'Q', 17: u'W', 18: u'E', 19: u'R', 20: u'T', 21: u'Y', - 22: u'U', 23: u'I', 24: u'O', 25: u'P', 26: u'[', 27: u']', 28: u'CRLF', - 29: u'LCTRL', 30: u'A', 31: u'S', 32: u'D', 33: u'F', 34: u'G', 35: u'H', - 36: u'J', 37: u'K', 38: u'L', 39: u';', 40: u'"', 41: u'`', 42: u'LSHFT', - 43: u'\\', 44: u'Z', 45: u'X', 46: u'C', 47: u'V', 48: u'B', 49: u'N', - 50: u'M', 51: u',', 52: u'.', 53: u'/', 54: u'RSHFT', 56: u'LALT', - 100: u'RALT' + 0: None, + 1: "ESC", + 2: "1", + 3: "2", + 4: "3", + 5: "4", + 6: "5", + 7: "6", + 8: "7", + 9: "8", + 10: "9", + 11: "0", + 12: "-", + 13: "=", + 14: "BKSP", + 15: "TAB", + 16: "Q", + 17: "W", + 18: "E", + 19: "R", + 20: "T", + 21: "Y", + 22: "U", + 23: "I", + 24: "O", + 25: "P", + 26: "[", + 27: "]", + 28: "CRLF", + 29: "LCTRL", + 30: "A", + 31: "S", + 32: "D", + 33: "F", + 34: "G", + 35: "H", + 36: "J", + 37: "K", + 38: "L", + 39: ";", + 40: '"', + 41: "`", + 42: "LSHFT", + 43: "\\", + 44: "Z", + 45: "X", + 46: "C", + 47: "V", + 48: "B", + 49: "N", + 50: "M", + 51: ",", + 52: ".", + 53: "/", + 54: "RSHFT", + 56: "LALT", + 100: "RALT", } -if __name__ == '__main__': +if __name__ == "__main__": dev_ = detect_barcode_device() print(dev_) tbs = ThreadedBarcodeReader(dev_) tbs.start() try: while True: - print('Last barcode: {}'.format(tbs.last_barcode_in_queue)) + print("Last barcode: {}".format(tbs.last_barcode_in_queue)) time.sleep(1) except KeyboardInterrupt: tbs.close() diff --git a/PyExpLabSys/drivers/vogtlin.py b/PyExpLabSys/drivers/vogtlin.py index c4fa3d19..68190ec7 100644 --- a/PyExpLabSys/drivers/vogtlin.py +++ b/PyExpLabSys/drivers/vogtlin.py @@ -21,21 +21,21 @@ from PyExpLabSys.common.supported_versions import python2_and_3 -python2_and_3(__file__) +python2_and_3(__file__) DEFAULT_COM_KWARGS = { - 'BAUDRATE': 9600, - 'BYTESIZE': 8, - 'STOPBITS': 2, - 'PARITY': serial.PARITY_NONE, + "BAUDRATE": 9600, + "BYTESIZE": 8, + "STOPBITS": 2, + "PARITY": serial.PARITY_NONE, } def process_string(value): """Strip a few non-ascii characters from string""" - return value.strip('\x00\x16') + return value.strip("\x00\x16") def convert_version(value): @@ -45,7 +45,7 @@ def convert_version(value): value = value % 256 version = value // 16 subversion = value % 16 - return '{}.{}.{}'.format(type_, version, subversion) + return "{}.{}.{}".format(type_, version, subversion) class RedFlowMeter(object): @@ -55,27 +55,26 @@ class RedFlowMeter(object): # name: (minimalmodbus_method, conversion_function), method_args...) # The command is generate from pages 1.14 and 1.15 from the manual command_map = { - 'flow': (('read_float', None), 0x00), - 'temperature': (('read_float', None), 0x02), - 'address': (('read_register', None), 0x0013), - 'serial': (('read_long', None), 0x001e), - 'hardware_version': (('read_register', convert_version), 0x0020), - 'software_version': (('read_register', convert_version), 0x0021), - 'type_code_1': (('read_string', process_string), 0x0023, 4), - 'type_code_2h': (('read_string', process_string), 0x1004, 4), - 'lut_select': (('read_register', None), 0x4139), - 'range': (('read_float', None), 0x6020), - 'fluid_name': (('read_string', process_string), 0x6042, 4), - 'unit': (('read_string', process_string), 0x6046, 4), - 'control_function': (('read_register', None), 0x000e), + "flow": (("read_float", None), 0x00), + "temperature": (("read_float", None), 0x02), + "address": (("read_register", None), 0x0013), + "serial": (("read_long", None), 0x001E), + "hardware_version": (("read_register", convert_version), 0x0020), + "software_version": (("read_register", convert_version), 0x0021), + "type_code_1": (("read_string", process_string), 0x0023, 4), + "type_code_2h": (("read_string", process_string), 0x1004, 4), + "lut_select": (("read_register", None), 0x4139), + "range": (("read_float", None), 0x6020), + "fluid_name": (("read_string", process_string), 0x6042, 4), + "unit": (("read_string", process_string), 0x6046, 4), + "control_function": (("read_register", None), 0x000E), } # The command map for set operations consists of # name: (minimalmodbus_method, conversion_function, address) command_map_set = { - 'setpoint_gas_flow': ('write_float', None, 0x0006), + "setpoint_gas_flow": ("write_float", None, 0x0006), } - def __init__(self, port, slave_address, **serial_com_kwargs): """Initialize driver @@ -100,7 +99,7 @@ def __init__(self, port, slave_address, **serial_com_kwargs): def _ensure_waittime(self): """Ensure waittime""" - waittime = 0.004 / 9600 * self.serial_com_kwargs['BAUDRATE'] + waittime = 0.004 / 9600 * self.serial_com_kwargs["BAUDRATE"] time_to_sleep = waittime - (time() - self._last_call) if time_to_sleep > 0: sleep(time_to_sleep) @@ -137,16 +136,28 @@ def read_value(self, value_name): value = conversion_function(value) break except IOError as e: - print("I/O error({}): {}. Trying to retrieve data again..".format(retry_number, e)) + print( + "I/O error({}): {}. Trying to retrieve data again..".format( + retry_number, e + ) + ) sleep(0.5) continue except ValueError as e: - print("ValueError({}): {}. Trying to retrieve data again..".format(retry_number, e)) + print( + "ValueError({}): {}. Trying to retrieve data again..".format( + retry_number, e + ) + ) sleep(0.5) continue else: - raise RuntimeError('Could not retrieve data in\ - {} retries'.format(self.number_of_retries)) + raise RuntimeError( + "Could not retrieve data in\ + {} retries".format( + self.number_of_retries + ) + ) # Set last call time self._last_call = time() @@ -192,11 +203,11 @@ def read_all(self): def read_flow(self): """Return the current flow (alias for read_value('flow')""" - return self.read_value('flow') + return self.read_value("flow") def read_temperature(self): """Return the current temperature""" - return self.read_value('temperature') + return self.read_value("temperature") def set_address(self, address): """Set the modbus address @@ -208,20 +219,21 @@ def set_address(self, address): ValueError: On invalid address """ if not (isinstance(address, int) and address in range(1, 248)): - msg = 'Invalid address: {}. Must be in range 1-247' + msg = "Invalid address: {}. Must be in range 1-247" raise ValueError(msg.format(address)) self.instrument.address = address def main(): # COM4, address 2 and 247 - flow_meter = RedFlowMeter('COM8', 42) + flow_meter = RedFlowMeter("COM8", 42) from pprint import pprint + pprint(flow_meter.read_all()) - flow_meter.write_value('setpoint_gas_flow', 0.0) - #flow_meter.set_address(247) - #pprint(flow_meter.read_all()) + flow_meter.write_value("setpoint_gas_flow", 0.0) + # flow_meter.set_address(247) + # pprint(flow_meter.read_all()) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/PyExpLabSys/drivers/weather_info.py b/PyExpLabSys/drivers/weather_info.py index 48dfada0..6e2f4e3f 100644 --- a/PyExpLabSys/drivers/weather_info.py +++ b/PyExpLabSys/drivers/weather_info.py @@ -1,4 +1,3 @@ - """Weather info driver""" import time @@ -16,7 +15,7 @@ def _virtual_temperature(temperature, dew_point, pressure): abs_temp = temperature + 273.15 - tmp1 = 10 ** (7.5*dew_point / (237.7 + dew_point)) + tmp1 = 10 ** (7.5 * dew_point / (237.7 + dew_point)) tmp2 = tmp1 / (0.01 * pressure) # Unit should be hPa, but we use Pa tv = abs_temp / (1 - (0.379 * 6.11 * tmp2)) return tv @@ -27,18 +26,21 @@ def dew_point_and_abs_humidity(temperature, humidity, pressure): Calculates dew point and absolute humidity given atmospheric conditions. """ atmos_params = { - 'RH': humidity, 'RH_unit': 'fraction', - 'T': temperature, 'T_unit': 'degC', - 'p': pressure, 'p_unit': 'Pa' + "RH": humidity, + "RH_unit": "fraction", + "T": temperature, + "T_unit": "degC", + "p": pressure, + "p_unit": "Pa", } - dew_point = atmos.calculate('Td', Td_unit='degC', **atmos_params) - absolute_humidity = atmos.calculate('AH', AH_unit='g/meter**3', **atmos_params) + dew_point = atmos.calculate("Td", Td_unit="degC", **atmos_params) + absolute_humidity = atmos.calculate("AH", AH_unit="g/meter**3", **atmos_params) # print('Calculated dew point: {:.1f}C'.format(dew_point)) # print('Calculated absolute humidity: {:.1f}g/m3'.format(absolute_humidity)) data = { - 'dew_point': dew_point, # degree C - 'absolute_humidity': absolute_humidity # g/m3 + "dew_point": dew_point, # degree C + "absolute_humidity": absolute_humidity, # g/m3 } return data @@ -51,21 +53,23 @@ def equaivalent_humidity(outside_temp, outside_hum, inside_temp, pressure): dew_and_abs = dew_point_and_abs_humidity(outside_temp, outside_hum, pressure) # Calculate inside virtual temperature if outside air was to be # heated to inside temp - tv = _virtual_temperature(inside_temp, dew_and_abs['dew_point'], pressure) + tv = _virtual_temperature(inside_temp, dew_and_abs["dew_point"], pressure) atmos_params = { - 'AH': dew_and_abs['absolute_humidity'], 'AH_unit': 'g/meter**3', - 'Tv': tv, 'Tv_unit': 'K', - 'p': pressure, 'p_unit': 'Pa', - 'debug': True + "AH": dew_and_abs["absolute_humidity"], + "AH_unit": "g/meter**3", + "Tv": tv, + "Tv_unit": "K", + "p": pressure, + "p_unit": "Pa", + "debug": True, } # Humidity is returned as percentage - indoor_humidity = atmos.calculate('RH', **atmos_params) + indoor_humidity = atmos.calculate("RH", **atmos_params) return indoor_humidity[0] class WheatherInformation(object): - def __init__(self, x_pos, y_pos, **kwargs): self.kwargs = kwargs self.x_pos = x_pos @@ -74,30 +78,30 @@ def __init__(self, x_pos, y_pos, **kwargs): def clear_data(self): self.weather_data = { - 'time': None, # Time of latest update - 'temperature': None, - 'humidity': None, - 'precepitation': None, - 'wind': None, - 'wind_gust': None, - 'wind_direction': None, # 0 North, 90 west, 180 south, 280 east - 'pressure': None, - 'sunrise': None, - 'sunset': None, - 'uv_index': None, - 'visibility': None, - 'cloud_percentage': None + "time": None, # Time of latest update + "temperature": None, + "humidity": None, + "precepitation": None, + "wind": None, + "wind_gust": None, + "wind_direction": None, # 0 North, 90 west, 180 south, 280 east + "pressure": None, + "sunrise": None, + "sunset": None, + "uv_index": None, + "visibility": None, + "cloud_percentage": None, } def dk_dmi(self): params = { - 'cmd': 'obj', - 'south': self.y_pos - 0.25, - 'north': self.y_pos + 0.25, - 'west': self.x_pos - 0.25, - 'east': self.x_pos + 0.25 + "cmd": "obj", + "south": self.y_pos - 0.25, + "north": self.y_pos + 0.25, + "west": self.x_pos - 0.25, + "east": self.x_pos + 0.25, } - url = 'https://www.dmi.dk/NinJo2DmiDk/ninjo2dmidk' + url = "https://www.dmi.dk/NinJo2DmiDk/ninjo2dmidk" error = 0 while -1 < error < 30: try: @@ -106,77 +110,73 @@ def dk_dmi(self): data = response.json() except requests.exceptions.ConnectionError: error = error + 1 - print('Connection error to: {}'.format(url)) + print("Connection error to: {}".format(url)) time.sleep(60) except json.decoder.JSONDecodeError: error = error + 1 - print('JSON decode error: {}'.format(url)) + print("JSON decode error: {}".format(url)) time.sleep(60) except socket.gaierror: error = error + 1 - print('Temporary failure in name resolution. {}'.format(url)) + print("Temporary failure in name resolution. {}".format(url)) time.sleep(60) - pri_list = self.kwargs.get('dmi_prio', {}) + pri_list = self.kwargs.get("dmi_prio", {}) if pri_list is {}: - print('Error no dmi position configured!') + print("Error no dmi position configured!") exit() # 06180: Lufthavn # 06186: Landbohøjskolen print(data) if not pri_list[0] in data: - print('Did not find priority 0 in data-list, give up!') + print("Did not find priority 0 in data-list, give up!") return - dmi_time_field = data[pri_list[0]]['time'][0:12] - self.weather_data['time'] = datetime.datetime.strptime( - dmi_time_field, '%Y%m%d%H%M') + dmi_time_field = data[pri_list[0]]["time"][0:12] + self.weather_data["time"] = datetime.datetime.strptime(dmi_time_field, "%Y%m%d%H%M") # List of genral values mapped to (key, unit-scale) value_map = { - 'temperature': ('Temperature2m', 1), - 'humidity': ('RelativeHumidity', 0.01), - 'precepitation': ('PrecAmount10min', 1), - 'wind': ('WindSpeed10m', 1), - 'wind_gust': ('WindGustLast10Min', 1), - 'wind_direction': ('WindDirection10m', 1), - 'pressure': ('PressureMSL', 1), + "temperature": ("Temperature2m", 1), + "humidity": ("RelativeHumidity", 0.01), + "precepitation": ("PrecAmount10min", 1), + "wind": ("WindSpeed10m", 1), + "wind_gust": ("WindGustLast10Min", 1), + "wind_direction": ("WindDirection10m", 1), + "pressure": ("PressureMSL", 1), } for dmi_site in sorted(pri_list.keys()): - dmi_values = data[pri_list[dmi_site]]['values'] + dmi_values = data[pri_list[dmi_site]]["values"] for global_key, dmi_key in value_map.items(): dmi_value = dmi_values.get(dmi_key[0]) - if ( - self.weather_data[global_key] is None and - dmi_value is not None - ): + if self.weather_data[global_key] is None and dmi_value is not None: self.weather_data[global_key] = dmi_value * dmi_key[1] def global_openweather(self): params = { - 'lat': self.y_pos, - 'lon': self.x_pos, - 'units': 'metric', - 'appid': self.kwargs['open_weather_appid'] + "lat": self.y_pos, + "lon": self.x_pos, + "units": "metric", + "appid": self.kwargs["open_weather_appid"], } # 'dew_point': 'dew_point' value_map = { - 'temperature': 'temp', - 'humidity': ('humidity', 0.01), + "temperature": "temp", + "humidity": ("humidity", 0.01), # 'precepitation': Not in data - 'wind': ('wind_speed', 1), + "wind": ("wind_speed", 1), # 'wind_gust': Not in data - 'wind_direction': ('wind_deg', 1), - 'pressure': ('pressure', 100), - 'sunrise': ('sunrise', 1), - 'sunset': ('sunset', 1), - 'uv_index': ('uvi', 1), - 'visibility': ('visibility', 1), - 'cloud_percentage': ('clouds', 1) + "wind_direction": ("wind_deg", 1), + "pressure": ("pressure", 100), + "sunrise": ("sunrise", 1), + "sunset": ("sunset", 1), + "uv_index": ("uvi", 1), + "visibility": ("visibility", 1), + "cloud_percentage": ("clouds", 1), } - url = 'https://api.openweathermap.org/data/2.5/onecall' + url = "https://api.openweathermap.org/data/2.5/onecall" # This code is used twice, refactor (and do it right, so # it will recover after extended loss of internet)? error = 0 @@ -187,52 +187,43 @@ def global_openweather(self): data = response.json() except requests.exceptions.ConnectionError: error = error + 1 - print('Connection error to: {}'.format(url)) + print("Connection error to: {}".format(url)) time.sleep(60) except json.decoder.JSONDecodeError: error = error + 1 - print('JSON decode error: {}'.format(url)) + print("JSON decode error: {}".format(url)) time.sleep(60) except socket.gaierror: error = error + 1 - print('Temporary failure in name resolution. {}'.format(url)) + print("Temporary failure in name resolution. {}".format(url)) time.sleep(60) # See also keys 'hourly' and 'daily' - current = data['current'] + current = data["current"] # time_field = datetime.datetime.fromtimestamp(current['dt']) for global_key, openweather_key in value_map.items(): value = current.get(openweather_key[0]) - if ( - self.weather_data[global_key] is None and - value is not None - ): + if self.weather_data[global_key] is None and value is not None: self.weather_data[global_key] = value * openweather_key[1] -if __name__ == '__main__': - dmi_prio = { - 0: '06186', - 1: '06180' - } +if __name__ == "__main__": + dmi_prio = {0: "06186", 1: "06180"} - appid = '' + appid = "" vejr = WheatherInformation( - y_pos=55.660105, - x_pos=12.589183, - dmi_prio=dmi_prio, - open_weather_appid=appid + y_pos=55.660105, x_pos=12.589183, dmi_prio=dmi_prio, open_weather_appid=appid ) vejr.dk_dmi() print(vejr.weather_data) indoor_hum = equaivalent_humidity( - outside_temp=vejr.weather_data['temperature'], - outside_hum=vejr.weather_data['humidity'], - pressure=vejr.weather_data['pressure'], - inside_temp=24 + outside_temp=vejr.weather_data["temperature"], + outside_hum=vejr.weather_data["humidity"], + pressure=vejr.weather_data["pressure"], + inside_temp=24, ) print(indoor_hum) diff --git a/PyExpLabSys/drivers/wpi_al1000.py b/PyExpLabSys/drivers/wpi_al1000.py index 84eb1db6..b7935509 100644 --- a/PyExpLabSys/drivers/wpi_al1000.py +++ b/PyExpLabSys/drivers/wpi_al1000.py @@ -1,4 +1,3 @@ - """This module implements a driver for the AL1000 syringe pump from World Precision Instruments @@ -12,24 +11,25 @@ class AL1000(object): """Driver for the AL1000 syringe pump""" - + def __init__(self, port="/dev/ttyUSB0", baudrate=19200): self.serial = serial.Serial( - port=port, baudrate=baudrate, timeout=1, + port=port, + baudrate=baudrate, + timeout=1, parity=serial.PARITY_NONE, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, - ) self.safe_mode = False def _send_command(self, command): if self.safe_mode: - encoded_command = command.encode('ascii') + encoded_command = command.encode("ascii") length = len(encoded_command) + 4 - check_sum = crc16.crc16xmodem(encoded_command).to_bytes(2, byteorder='big') - to_send = bytes([2, length]) + encoded_command + check_sum + b'\x03' - add = '{:0>2}'.format(n).encode('ascii') + check_sum = crc16.crc16xmodem(encoded_command).to_bytes(2, byteorder="big") + to_send = bytes([2, length]) + encoded_command + check_sum + b"\x03" + add = "{:0>2}".format(n).encode("ascii") to_send_add = add + to_send self.serial.write(to_send) time.sleep(0.5) @@ -37,8 +37,8 @@ def _send_command(self, command): reply = self.serial.read(waiting) # FIXME implement crc16 checksum check - - return reply.decode('ascii') + + return reply.decode("ascii") else: formatted_command = command + "\r" self.serial.write(formatted_command.encode("ascii")) @@ -50,7 +50,7 @@ def _send_command(self, command): def get_firmware(self): """Retrieves the firmware version - + Returns: str: The firmware version """ @@ -59,50 +59,49 @@ def get_firmware(self): def get_rate(self): """ Retrieves the pump rate - + Returns: - The pumping rate - + The pumping rate + """ # FIXME convert to float return self._send_command("RAT") - def set_rate(self, num, unit = False): + def set_rate(self, num, unit=False): """Sets the pump rate. - + Args: num (float): The flow rate (0 mL/min - 34.38 mL/min) unit (str): For valid values see below. - + Valid units are: UM=microL/min - MM=milliL/min - UH=microL/hr + MM=milliL/min + UH=microL/hr MH=milliL/hour - + Returns: Notihing. Printing the function yields space for success or error message - + """ if unit == False: self._send_command("FUNRAT") return "rate: " + self._send_command("RAT" + str(num)) - + if unit != False: - self._send_command("FUNRAT") - return "rate: " + self._send_command("RAT" + str(num) + unit ) + return "rate: " + self._send_command("RAT" + str(num) + unit) - def set_vol(self,num): + def set_vol(self, num): """Sets the pumped volume to the pump. The pump will pump until the given volume has been dispensed. - + Args: num (float): The volume to de dispensed (no limits) Returns: Notihing. Printing the function yields space for success or error message - + """ return self._send_command("VOL" + str(num)) @@ -113,13 +112,13 @@ def get_vol_disp(self): The dispensed volume """ return self._send_command("DIS") - - def clear_vol_disp(self, direction = "both"): - """Clear pumped volume for one or more dircetions. - + + def clear_vol_disp(self, direction="both"): + """Clear pumped volume for one or more dircetions. + Args: direction (string): The pumping direction. Valid directions are: INF=inflation, WDR=withdrawn, both=both directions. Default is both - + Returns: Notihing. Printing the function yields space for success or error message @@ -131,25 +130,25 @@ def clear_vol_disp(self, direction = "both"): if direction == "both": return self._send_command("CLDINF") return self._send_command("CLDWDR") - + def set_fun(self, phase): """Sets the program function - + Returns: Notihing. Printing the function yields space for success or error message """ return self._send_command("FUN" + phase) - def set_safe_mode(self,num): + def set_safe_mode(self, num): """Enables or disables safe mode. - + Args: If num=0 --> Safe mode disables If num>0 --> Safe mode enables with the requirement that valid communication must be received every num seconds - + Returns: - Notihing. Printing the function yields space for success or error message + Notihing. Printing the function yields space for success or error message """ return self._send_command("SAF" + str(num)) @@ -166,14 +165,14 @@ def get_direction(self): def set_direction(self, direction): """Sets the pumping direction - + Args: directoin=INF --> Pumping dirction set to infuse directoin=WDR --> Pumping dirction set to Withdraw directoin=REV --> Pumping dirction set to the reverse current pumping direction - + Returns: - Notihing. Printing the function yields space for success or error message + Notihing. Printing the function yields space for success or error message """ if direction == "INF": @@ -185,22 +184,25 @@ def set_direction(self, direction): def retract_pump(self): """Fully retracts the pump. REMEMBER TO STOP MANUALLY! - + Returns: - Notihing. Printing the function yields space for success or error message + Notihing. Printing the function yields space for success or error message """ self.set_direction("WDR") self.set_vol(9999) - self.set_rate(34.38,"MM") + self.set_rate(34.38, "MM") self.start_program() + max_rate = 34.38 + def main(): pump = AL1000() print(repr(pump.get_firmware())) - #print(repr(pump._send_command("\x02\x08SAF0\x55\x43\x03"))) + # print(repr(pump._send_command("\x02\x08SAF0\x55\x43\x03"))) + -if __name__ == "__main__" : +if __name__ == "__main__": main() diff --git a/PyExpLabSys/drivers/xgs600.py b/PyExpLabSys/drivers/xgs600.py index dab9db58..cff6ff92 100644 --- a/PyExpLabSys/drivers/xgs600.py +++ b/PyExpLabSys/drivers/xgs600.py @@ -3,12 +3,14 @@ import time import serial from PyExpLabSys.common.supported_versions import python2_and_3 + python2_and_3(__file__) -class XGS600Driver(): +class XGS600Driver: """Driver for XGS600 gauge controller""" - def __init__(self, port='/dev/ttyUSB1', timeout=2.0): + + def __init__(self, port="/dev/ttyUSB1", timeout=2.0): self.serial = serial.Serial(port) self.timeout = timeout @@ -17,7 +19,7 @@ def xgs_comm(self, command, expect_reply=True): # Write command self.serial.read(self.serial.inWaiting()) # Clear waiting characters comm = "#00" + command + "\r" # #00 is RS232 communication and #aa is RS485 - self.serial.write(comm.encode('ascii')) + self.serial.write(comm.encode("ascii")) # Read reply if requested # Expected reply is always '>'+reply+'\r' @@ -25,10 +27,10 @@ def xgs_comm(self, command, expect_reply=True): t_start_reply = time.time() time.sleep(0.25) if expect_reply: - gathered_reply = '' + gathered_reply = "" number_of_bytes = self.serial.inWaiting() gathered_reply += self.serial.read(number_of_bytes).decode() - while not gathered_reply.endswith('\r'): + while not gathered_reply.endswith("\r"): print("Waiting for rest of reply, reply so far is: ", repr(gathered_reply)) number_of_bytes = self.serial.inWaiting() gathered_reply += self.serial.read(number_of_bytes).decode() @@ -37,7 +39,7 @@ def xgs_comm(self, command, expect_reply=True): raise TimeoutError time.sleep(0.25) - return gathered_reply.replace('>', '').replace('\r', '') + return gathered_reply.replace(">", "").replace("\r", "") def read_all_pressures(self): """Read pressure from all sensors""" @@ -47,11 +49,11 @@ def read_all_pressures(self): pressure_string = self.xgs_comm("0F") if len(pressure_string) > 0: error = 0 - temp_pressure = pressure_string.replace(' ', '').split(',') + temp_pressure = pressure_string.replace(" ", "").split(",") pressures = [] for press in temp_pressure: - if press == 'OPEN': - pressures.append('OPEN') + if press == "OPEN": + pressures.append("OPEN") else: try: pressures.append((float)(press)) @@ -59,30 +61,29 @@ def read_all_pressures(self): pressures.append(-2) else: time.sleep(0.2) - error = error +1 + error = error + 1 return pressures - def list_all_gauges(self): """List all installed gauge cards""" gauge_string = self.xgs_comm("01") gauges = "" for gauge_number in range(0, len(gauge_string), 2): - gauge = gauge_string[gauge_number:gauge_number+2] + gauge = gauge_string[gauge_number : gauge_number + 2] if gauge == "10": - gauges = gauges + str(gauge_number/2) + ": Hot Filament Gauge\n" + gauges = gauges + str(gauge_number / 2) + ": Hot Filament Gauge\n" if gauge == "FE": - gauges = gauges + str(gauge_number/2) + ": Empty Slot\n" + gauges = gauges + str(gauge_number / 2) + ": Empty Slot\n" if gauge == "40": - gauges = gauges + str(gauge_number/2) + ": Convection Board\n" + gauges = gauges + str(gauge_number / 2) + ": Convection Board\n" if gauge == "3A": - gauges = gauges + str(gauge_number/2) + ": Inverted Magnetron Board\n" + gauges = gauges + str(gauge_number / 2) + ": Inverted Magnetron Board\n" return gauges def read_pressure(self, gauge_id): """Read the pressure from a specific gauge. gauge_id is represented as Uxxxxx and xxxxx is the userlabel""" - pressure = self.xgs_comm('02' + gauge_id) + pressure = self.xgs_comm("02" + gauge_id) try: val = float(pressure) except ValueError: @@ -91,27 +92,27 @@ def read_pressure(self, gauge_id): def filament_lit(self, gauge_id): """Report if the filament of a given gauge is lid""" - filament = self.xgs_comm('34' + gauge_id) + filament = self.xgs_comm("34" + gauge_id) return int(filament) def emission_status(self, gauge_id): """Read the status of the emission for a given gauge""" - status = self.xgs_comm('32' + gauge_id) - emission = status == '01' + status = self.xgs_comm("32" + gauge_id) + emission = status == "01" return emission def set_smission_off(self, gauge_id): """Turn off emission from a given gauge""" - self.xgs_comm('30' + gauge_id, expect_reply=False) + self.xgs_comm("30" + gauge_id, expect_reply=False) time.sleep(0.1) return self.emission_status(gauge_id) def set_emission_on(self, gauge_id, filament): """Turn on emission for a given gauge""" if filament == 1: - command = '31' + command = "31" if filament == 2: - command = '33' + command = "33" self.xgs_comm(command + gauge_id, expect_reply=False) return self.emission_status(gauge_id) @@ -123,7 +124,7 @@ def read_software_version(self): def read_pressure_unit(self): """Read which pressure unit is used""" gauge_string = self.xgs_comm("13") - unit = gauge_string.replace(' ', '') + unit = gauge_string.replace(" ", "") if unit == "00": unit = "Torr" if unit == "01": @@ -139,67 +140,82 @@ def read_setpoint_state(self): and 0002 corrosponds to [F,T,F,F,F,F,F,F] """ setpoint_state_string = self.xgs_comm("03") - setpoint_state = setpoint_state_string.replace(' ', '') - hex_to_binary = format(int(setpoint_state, base=16), '0>8b') # format hex value to binari with 8bit - binary_to_bool_list = [char == '1' for char in hex_to_binary] # make binary number to boolean array - states = list(reversed(binary_to_bool_list)) # Reverse boolean array to read states of valves left to right + setpoint_state = setpoint_state_string.replace(" ", "") + hex_to_binary = format( + int(setpoint_state, base=16), "0>8b" + ) # format hex value to binari with 8bit + binary_to_bool_list = [ + char == "1" for char in hex_to_binary + ] # make binary number to boolean array + states = list( + reversed(binary_to_bool_list) + ) # Reverse boolean array to read states of valves left to right return states def read_setpoint(self, channel): """Read the Setpoint OFF/ON/AUTO for channel h in [1-8]""" - setpoint_string = self.xgs_comm("5F"+str(channel)) - setpoint = setpoint_string.replace(' ', '') + setpoint_string = self.xgs_comm("5F" + str(channel)) + setpoint = setpoint_string.replace(" ", "") if str(setpoint) == "0": - status = 'OFF' + status = "OFF" elif str(setpoint) == "3": - status = 'AUTO' + status = "AUTO" elif str(setpoint) == "1": - status = 'ON' + status = "ON" else: status = None return status def set_setpoint(self, channel, state): - """"Set Setpoint OFF/ON/AUTO + """ "Set Setpoint OFF/ON/AUTO Example: #005E83 sets setpoint #8 to Auto """ if state in [0, 1, 3]: state_reply = state - elif state in ['0', '1', '3']: + elif state in ["0", "1", "3"]: state_reply = int(state) - elif state.lower() == 'auto': + elif state.lower() == "auto": state_reply = 3 - elif state.lower() == 'off': + elif state.lower() == "off": state_reply = 0 - elif state.lower() == 'on': + elif state.lower() == "on": state_reply = 1 else: return 'only (0,1,3) / ("OFF", "ON", "AUTO") is accepted' - self.xgs_comm("5E"+str(channel)+str(state_reply), expect_reply=False) + self.xgs_comm("5E" + str(channel) + str(state_reply), expect_reply=False) def set_setpoint_on(self, setpoint, sensor_code, sensor_count, pressure_on): """This sets the pressure setpoint of the valve where it will be on. hcnx.xxxE-xx, where h is setpoint 1-8, c is sensorcode, T for CNV and I for ion gauge, n is sensor count, press is pressure represented with x.xxxE-xx c could be U and and then n would be the user label""" - if sensor_code == 'user_label': - sensor_code = 'U' - self.xgs_comm("6"+str(setpoint)+str(sensor_code)+str(sensor_count)+str(pressure_on), - expect_reply=False) - print('On_string: ', "6"+str(setpoint)+str(sensor_code)+str(sensor_count)+str(pressure_on)) + if sensor_code == "user_label": + sensor_code = "U" + self.xgs_comm( + "6" + str(setpoint) + str(sensor_code) + str(sensor_count) + str(pressure_on), + expect_reply=False, + ) + print( + "On_string: ", + "6" + str(setpoint) + str(sensor_code) + str(sensor_count) + str(pressure_on), + ) def set_setpoint_off(self, setpoint, sensor_code, sensor_count, pressure_off): """This sets the pressure setpoint of the valve where it will be off. hcnx.xxxE-xx, where h is setpoint 1-8, c is sensorcode, T for CNV and I for ion gauge, U is user label, n is sensor count or the specific user label, press is pressure represented as x.xxxE-xx""" - if sensor_code == 'user_label': - sensor_code = 'U' - self.xgs_comm("7"+str(setpoint)+str(sensor_code)+str(sensor_count)+str(pressure_off), - expect_reply=False) - print('Off_string: ', - "7"+str(sensor_code)+str(sensor_count)+str(sensor_count)+str(pressure_off)) + if sensor_code == "user_label": + sensor_code = "U" + self.xgs_comm( + "7" + str(setpoint) + str(sensor_code) + str(sensor_count) + str(pressure_off), + expect_reply=False, + ) + print( + "Off_string: ", + "7" + str(sensor_code) + str(sensor_count) + str(sensor_count) + str(pressure_off), + ) def read_all_user_label(self): """Read all user defined labels for gauge id Command Entry T for TC/CNV, @@ -207,22 +223,22 @@ def read_all_user_label(self): Counting TCs or ion gauges from left to right from the front panel view.""" user_labels = {} for i in range(1, 9): - thermo_couple_string = self.xgs_comm("T"+str(i)) - ion_gauges_string = self.xgs_comm("I"+str(i)) - thermo_couple = thermo_couple_string.replace(' ', '') - ion_gauges = ion_gauges_string.replace(' ', '') + thermo_couple_string = self.xgs_comm("T" + str(i)) + ion_gauges_string = self.xgs_comm("I" + str(i)) + thermo_couple = thermo_couple_string.replace(" ", "") + ion_gauges = ion_gauges_string.replace(" ", "") if thermo_couple != "?FF": - user_labels['T'+str(i)] = thermo_couple + user_labels["T" + str(i)] = thermo_couple else: pass if ion_gauges != "?FF": - user_labels['I'+str(i)] = ion_gauges + user_labels["I" + str(i)] = ion_gauges else: pass return user_labels -if __name__ == '__main__': +if __name__ == "__main__": XGS = XGS600Driver() print(XGS.read_all_pressures()) diff --git a/PyExpLabSys/drivers/xp_power_1500_series.py b/PyExpLabSys/drivers/xp_power_1500_series.py index 6d5e4d57..d2b8c849 100644 --- a/PyExpLabSys/drivers/xp_power_1500_series.py +++ b/PyExpLabSys/drivers/xp_power_1500_series.py @@ -8,7 +8,7 @@ def __init__(self, i2c_address=0x51): def read_manufacturer(self): data = self.bus.read_i2c_block_data(self.device_address, 0x00, 16) - return_string = '' + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() @@ -16,7 +16,7 @@ def read_manufacturer(self): def read_model(self): data = self.bus.read_i2c_block_data(self.device_address, 0x10, 16) - return_string = '' + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() @@ -24,7 +24,7 @@ def read_model(self): def read_serial_nr(self): data = self.bus.read_i2c_block_data(self.device_address, 0x30, 16) - return_string = '' + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() @@ -34,73 +34,72 @@ def read_temperature(self): data = self.bus.read_i2c_block_data(self.device_address, 0x68, 1) return data[0] - def read_status(self): print() - print('Status 0x6c:') - error1 = self.bus.read_i2c_block_data(self.device_address, 0x6c, 1) + print("Status 0x6c:") + error1 = self.bus.read_i2c_block_data(self.device_address, 0x6C, 1) error_bits = bin(error1[0])[2:].zfill(8) - if error_bits[7] == '1': - print('OVP Shutdown') - if error_bits[6] == '1': - print('OLP Shutdown') - if error_bits[5] == '1': - print('OTP Shutdown') - if error_bits[4] == '1': - print('Fan Failure') - if error_bits[3] == '1': - print('AUX og SMPS Fail') - if error_bits[2] == '1': - print('HI-Temp') - if error_bits[1] == '1': - print('AC power de-rating') - if error_bits[0] == '1': - print('AC Input Failure') + if error_bits[7] == "1": + print("OVP Shutdown") + if error_bits[6] == "1": + print("OLP Shutdown") + if error_bits[5] == "1": + print("OTP Shutdown") + if error_bits[4] == "1": + print("Fan Failure") + if error_bits[3] == "1": + print("AUX og SMPS Fail") + if error_bits[2] == "1": + print("HI-Temp") + if error_bits[1] == "1": + print("AC power de-rating") + if error_bits[0] == "1": + print("AC Input Failure") print() - print('Status 0x6f:') - error2 = self.bus.read_i2c_block_data(self.device_address, 0x6f, 1) + print("Status 0x6f:") + error2 = self.bus.read_i2c_block_data(self.device_address, 0x6F, 1) error_bits = bin(error2[0])[2:].zfill(8) - if error_bits[7] == '1': - print('Inhibit by VCI / ACI or ENB') - if error_bits[6] == '1': - print('CMD Active') - assert (error_bits[5] == '0') - assert (error_bits[4] == '0') - if error_bits[3] == '1': - print('Status on') - assert (error_bits[2] == '0') - assert (error_bits[1] == '0') - if error_bits[0] == '1': - print('Remote on') + if error_bits[7] == "1": + print("Inhibit by VCI / ACI or ENB") + if error_bits[6] == "1": + print("CMD Active") + assert error_bits[5] == "0" + assert error_bits[4] == "0" + if error_bits[3] == "1": + print("Status on") + assert error_bits[2] == "0" + assert error_bits[1] == "0" + if error_bits[0] == "1": + print("Remote on") print() - print('Control:') - control = self.bus.read_i2c_block_data(self.device_address, 0x7c, 1) + print("Control:") + control = self.bus.read_i2c_block_data(self.device_address, 0x7C, 1) error_bits = bin(control[0])[2:].zfill(8) # print('Control bits: {}'.format(error_bits)) - if error_bits[7] == '1': - print('Power On') - if error_bits[5] == '1': - print('Command required') - if error_bits[4] == '1': - print('Command error') - assert (error_bits[3] == '0') - assert (error_bits[2] == '0') + if error_bits[7] == "1": + print("Power On") + if error_bits[5] == "1": + print("Command required") + if error_bits[4] == "1": + print("Command error") + assert error_bits[3] == "0" + assert error_bits[2] == "0" # Bit 1 is reserved, cannot predict what happens - if error_bits[0] == '1': - print('Control by i2c') + if error_bits[0] == "1": + print("Control by i2c") print() def read_max_values(self): data = self.bus.read_i2c_block_data(self.device_address, 0x50, 2) - print((256*data[1] + data[0]) / 100.0) + print((256 * data[1] + data[0]) / 100.0) data = self.bus.read_i2c_block_data(self.device_address, 0x52, 2) - print((256*data[1] + data[0]) / 100.0) + print((256 * data[1] + data[0]) / 100.0) data = self.bus.read_i2c_block_data(self.device_address, 0x56, 2) - print((256*data[1] + data[0]) / 100.0) + print((256 * data[1] + data[0]) / 100.0) data = self.bus.read_i2c_block_data(self.device_address, 0x54, 2) - print((256*data[1] + data[0]) / 100.0) + print((256 * data[1] + data[0]) / 100.0) def read_actual_voltage(self): data = self.bus.read_i2c_block_data(self.device_address, 0x60, 2) @@ -120,12 +119,12 @@ def read_actual_current(self): def remote_enable(self): control = 2**7 + 1 # Add one to avoid turning off the unit # control = 1 - self.bus.write_i2c_block_data(self.device_address, 0x7c, [control]) + self.bus.write_i2c_block_data(self.device_address, 0x7C, [control]) def turn_off_unit(self): control = 0 # control = 1 - self.bus.write_i2c_block_data(self.device_address, 0x7c, [control]) + self.bus.write_i2c_block_data(self.device_address, 0x7C, [control]) def set_voltage(self, voltage): # Todo, check value is within range @@ -134,10 +133,10 @@ def set_voltage(self, voltage): low_byte = decivoltage % 256 data = [low_byte, high_byte] - print('Voltage data: {}'.format(data)) + print("Voltage data: {}".format(data)) self.bus.write_i2c_block_data(self.device_address, 0x70, data) # Execute change (0x84 to abondon): - self.bus.write_i2c_block_data(self.device_address, 0x7c, [0x85]) + self.bus.write_i2c_block_data(self.device_address, 0x7C, [0x85]) def set_current(self, current): # Todo, check value is within range @@ -145,14 +144,15 @@ def set_current(self, current): high_byte = decicurrent >> 8 low_byte = decicurrent % 256 data = [low_byte, high_byte] - print('Current data: {}'.format(data)) + print("Current data: {}".format(data)) self.bus.write_i2c_block_data(self.device_address, 0x72, data) # Execute change (0x84 to abondon): - self.bus.write_i2c_block_data(self.device_address, 0x7c, [0x85]) + self.bus.write_i2c_block_data(self.device_address, 0x7C, [0x85]) -if __name__ == '__main__': +if __name__ == "__main__": import time + xp = XP_PS(0x50) # xp.remote_enable() # time.sleep(1) @@ -163,13 +163,12 @@ def set_current(self, current): # xp.read_status() # exit() - print('Manufacturer: {}'.format(xp.read_manufacturer())) - print('Model: {}'.format(xp.read_model())) - print('Serial: {}'.format(xp.read_serial_nr())) + print("Manufacturer: {}".format(xp.read_manufacturer())) + print("Model: {}".format(xp.read_model())) + print("Serial: {}".format(xp.read_serial_nr())) print() while True: time.sleep(2) xp.read_temperature() - print('Temperature: {}C'.format(xp.read_temperature())) - + print("Temperature: {}C".format(xp.read_temperature())) diff --git a/PyExpLabSys/drivers/xp_power_hpa_series.py b/PyExpLabSys/drivers/xp_power_hpa_series.py index b462e159..56895b43 100644 --- a/PyExpLabSys/drivers/xp_power_hpa_series.py +++ b/PyExpLabSys/drivers/xp_power_hpa_series.py @@ -3,30 +3,30 @@ class XP_HPA_PS(object): - def __init__(self, i2c_address=0x5f): + def __init__(self, i2c_address=0x5F): self.bus = smbus.SMBus(1) self.bus.pec = True # Enable PEC-check, is this good? self.device_address = i2c_address def read_manufacturer(self): data = self.bus.read_i2c_block_data(self.device_address, 0x99, 16) - return_string = '' + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() return return_string def read_model(self): - data = self.bus.read_i2c_block_data(self.device_address, 0x9a, 32) - return_string = '' + data = self.bus.read_i2c_block_data(self.device_address, 0x9A, 32) + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() return return_string def read_serial_nr(self): - data = self.bus.read_i2c_block_data(self.device_address, 0x9e, 16) - return_string = '' + data = self.bus.read_i2c_block_data(self.device_address, 0x9E, 16) + return_string = "" for char in data: return_string += chr(char) return_string = return_string.strip() @@ -40,11 +40,11 @@ def read_temperatures(self, read_all=True): temperature1 = 0 if read_all: # Temperature sensor 1 - secondary - data = self.bus.read_i2c_block_data(self.device_address, 0x8d, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0x8D, 2) temperature1 = 256 * data[1] + data[0] # Temperature sensor 2 - primary - data = self.bus.read_i2c_block_data(self.device_address, 0x8e, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0x8E, 2) temperature2 = 256 * data[1] + data[0] return (temperature1, temperature2) @@ -61,7 +61,7 @@ def _decode_linear(self, data): mantissa = data[0] + high_mantissa if exp > 16: exp = exp - 2**5 - value = 1.0 * mantissa * 2 ** exp + value = 1.0 * mantissa * 2**exp return value def read_fan_speeds(self): @@ -77,20 +77,17 @@ def read_input_voltage(self): fault_limit = self._decode_linear(data) data = self.bus.read_i2c_block_data(self.device_address, 0x88, 2) v_in = self._decode_linear(data) - return_value = { - 'fault_limit': fault_limit, - 'v_in': v_in - } + return_value = {"fault_limit": fault_limit, "v_in": v_in} return return_value def read_actual_voltage(self): - data = self.bus.read_i2c_block_data(self.device_address, 0x8b, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0x8B, 2) # print('Actual voltage readback: {}'.format(data)) voltage = (256 * data[1] + data[0]) / 1024.0 return voltage def read_actual_current(self): - data = self.bus.read_i2c_block_data(self.device_address, 0x8c, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0x8C, 2) current = self._decode_linear(data) return current @@ -104,7 +101,7 @@ def _common_status_read(self, address, error_list): bits = bin(data[0])[2:].zfill(8) length = len(error_list) for i in range(0, length): - if bits[length - 1 - i] == '1': + if bits[length - 1 - i] == "1": actual_errors.append(error_list[i]) return actual_errors @@ -113,51 +110,86 @@ def read_status_byte(self): Fast combined status read, will give a rough overview. """ error_values = [ - 'NONE_OF_THE_ABOVE', 'CML', 'TEMPERATURE', 'VIN_UV_FAULT', - 'IOUT_OC_FAULT', 'VOUT_OV_FAULT', 'OFF', 'BUSY' + "NONE_OF_THE_ABOVE", + "CML", + "TEMPERATURE", + "VIN_UV_FAULT", + "IOUT_OC_FAULT", + "VOUT_OV_FAULT", + "OFF", + "BUSY", ] actual_errors = self._common_status_read(0x78, error_values) return actual_errors def read_voltage_out_status(self): error_values = [ - 'Not used', 'Not used', 'Not used', 'Not used', 'VOUT_UV_FAULT', - 'VOUT_UV_WARNING', 'VOUT_OV_WARNING', 'VOUT_OV_FAULT' + "Not used", + "Not used", + "Not used", + "Not used", + "VOUT_UV_FAULT", + "VOUT_UV_WARNING", + "VOUT_OV_WARNING", + "VOUT_OV_FAULT", ] - actual_errors = self._common_status_read(0x7a, error_values) + actual_errors = self._common_status_read(0x7A, error_values) return actual_errors def read_current_out_status(self): error_values = [ - 'Not used', 'Not used', 'IN_POWER_LIMIT', 'Not used', 'Not used', - 'IOUT_OC_WARNING', 'IOUT_OC_LV_FAULT', 'OUT_OC_FAULT' + "Not used", + "Not used", + "IN_POWER_LIMIT", + "Not used", + "Not used", + "IOUT_OC_WARNING", + "IOUT_OC_LV_FAULT", + "OUT_OC_FAULT", ] - actual_errors = self._common_status_read(0x7b, error_values) + actual_errors = self._common_status_read(0x7B, error_values) return actual_errors def read_ac_input_status(self): error_values = [ - 'Not used', 'Not used', 'Not used', 'Not used', 'VIN_UV_FAULT', - 'VIN_UV_WARNING', 'VIN_OV_WARNING', 'VIN_OV_FAULT' + "Not used", + "Not used", + "Not used", + "Not used", + "VIN_UV_FAULT", + "VIN_UV_WARNING", + "VIN_OV_WARNING", + "VIN_OV_FAULT", ] - actual_errors = self._common_status_read(0x7c, error_values) + actual_errors = self._common_status_read(0x7C, error_values) return actual_errors def read_temperature_status(self): error_values = [ - 'OT_PRELOAD', 'Not used', 'Not used', 'Not used', 'Not used', - 'Not used', 'OT_WARNING', 'OT_FAULT' + "OT_PRELOAD", + "Not used", + "Not used", + "Not used", + "Not used", + "Not used", + "OT_WARNING", + "OT_FAULT", ] - actual_errors = self._common_status_read(0x7d, error_values) + actual_errors = self._common_status_read(0x7D, error_values) return actual_errors def read_communication_status(self): error_values = [ - 'MEM_LOGIC_FAULT', 'OTHER_CML_FAULT', 'Reserved - Not used', - 'MCU_FAULT', 'MEMORY_FAULT', 'PEC_FAILED', 'INVALID_DATA', - 'INVALID_COMMAND' + "MEM_LOGIC_FAULT", + "OTHER_CML_FAULT", + "Reserved - Not used", + "MCU_FAULT", + "MEMORY_FAULT", + "PEC_FAILED", + "INVALID_DATA", + "INVALID_COMMAND", ] - actual_errors = self._common_status_read(0x7e, error_values) + actual_errors = self._common_status_read(0x7E, error_values) return actual_errors # 0x81 Fans 1 and 2 @@ -165,30 +197,30 @@ def read_communication_status(self): def read_user_configuration(self): values = [ - 'CFG_CURRENT_SOFTSTART_ENABLE', - 'CFG_FAST_SOFTSTART_DISABLE', - 'CFG_PRELOAD_DISABLE', - 'CFG_FAN_OFF', - 'CFG_ACOK_SIG_LOGIC', - 'CFG_DCOK_SIG_LOGIC', - 'Reserved', - 'CFG_FAN_TEMP_OK_SIG_LOGIC', - 'CFG_SYNC_PWR_ON', - 'CFG_REMOTE_INHIBIT_LOGIC', - 'CFG_POTENTIOMETER_DISABLE', - 'CFG_POTENTIOMETER_FULL_ADJ', - 'CFG_ANALOG_PROG', - 'CFG_DISABLE_IPROG', - 'CFG_DISABLE_VPROG', - 'CFG_NO_PRELOAD_IN_SD' + "CFG_CURRENT_SOFTSTART_ENABLE", + "CFG_FAST_SOFTSTART_DISABLE", + "CFG_PRELOAD_DISABLE", + "CFG_FAN_OFF", + "CFG_ACOK_SIG_LOGIC", + "CFG_DCOK_SIG_LOGIC", + "Reserved", + "CFG_FAN_TEMP_OK_SIG_LOGIC", + "CFG_SYNC_PWR_ON", + "CFG_REMOTE_INHIBIT_LOGIC", + "CFG_POTENTIOMETER_DISABLE", + "CFG_POTENTIOMETER_FULL_ADJ", + "CFG_ANALOG_PROG", + "CFG_DISABLE_IPROG", + "CFG_DISABLE_VPROG", + "CFG_NO_PRELOAD_IN_SD", ] # Consider to extend _common_status_read() to handle this actual_settings = [] - data = self.bus.read_i2c_block_data(self.device_address, 0xd6, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0xD6, 2) data_sum = data[1] * 256 + data[0] bits = bin(data_sum)[2:].zfill(16) for i in range(0, 16): - if bits[16 - 1 - i] == '1': + if bits[16 - 1 - i] == "1": actual_settings.append(values[i]) return actual_settings @@ -202,10 +234,9 @@ def _set_bit(index, bits, wanted_state): bits += 2**index return bits - def configure_user_settings( - self, cfg_fan_off=None, cfg_remote_inhibit_logic=None): + def configure_user_settings(self, cfg_fan_off=None, cfg_remote_inhibit_logic=None): # Read current state - data = self.bus.read_i2c_block_data(self.device_address, 0xd6, 2) + data = self.bus.read_i2c_block_data(self.device_address, 0xD6, 2) data_sum = data[1] * 256 + data[0] if cfg_fan_off is not None: @@ -217,7 +248,7 @@ def configure_user_settings( high_byte = data_sum >> 8 low_byte = data_sum % 256 data = [low_byte, high_byte] - self.bus.write_i2c_block_data(self.device_address, 0xd6, data) + self.bus.write_i2c_block_data(self.device_address, 0xD6, data) def operation(self, turn_on=False, turn_off=False): """ @@ -280,38 +311,38 @@ def set_current(self, current): self.bus.write_i2c_block_data(self.device_address, 0x46, data) -if __name__ == '__main__': +if __name__ == "__main__": xp = XP_HPA_PS() xp.write_enable() # xp.store_user_all() xp.operation(turn_on=True) xp.clear_errors() - print('Comm status: {}'.format(xp.read_communication_status())) + print("Comm status: {}".format(xp.read_communication_status())) # xp.configure_user_settings(cfg_fan_off=True) # xp.configure_user_settings(cfg_remote_inhibit_logic=False) - print('User configuration: {}'.format(xp.read_user_configuration())) - print('Comm status: {}'.format(xp.read_communication_status())) - print('Voltage output status: {}'.format(xp.read_voltage_out_status())) - print('Manufacturer: {}'.format(xp.read_manufacturer())) - print('Model: {}'.format(xp.read_model())) - print('Serial: {}'.format(xp.read_serial_nr())) + print("User configuration: {}".format(xp.read_user_configuration())) + print("Comm status: {}".format(xp.read_communication_status())) + print("Voltage output status: {}".format(xp.read_voltage_out_status())) + print("Manufacturer: {}".format(xp.read_manufacturer())) + print("Model: {}".format(xp.read_model())) + print("Serial: {}".format(xp.read_serial_nr())) temps = xp.read_temperatures() - print('Primary temp sensor: {}C. Secondary: {}C'.format(temps[1], temps[0])) + print("Primary temp sensor: {}C. Secondary: {}C".format(temps[1], temps[0])) - print('Fan speeds: {}'.format(xp.read_fan_speeds())) - print('Input voltage: {}'.format(xp.read_input_voltage())) + print("Fan speeds: {}".format(xp.read_fan_speeds())) + print("Input voltage: {}".format(xp.read_input_voltage())) xp.set_voltage(1) xp.set_current(1) # xp.set_voltage(0.5) time.sleep(1) - print('PS Voltage: {}V'.format(xp.read_actual_voltage())) - print('PS Current: {}A'.format(xp.read_actual_current())) + print("PS Voltage: {}V".format(xp.read_actual_voltage())) + print("PS Current: {}A".format(xp.read_actual_current())) print() - print('Status byte: {}'.format(xp.read_status_byte())) - print('Voltage output status: {}'.format(xp.read_voltage_out_status())) - print('Current output status: {}'.format(xp.read_current_out_status())) - print('Comm status: {}'.format(xp.read_communication_status())) + print("Status byte: {}".format(xp.read_status_byte())) + print("Voltage output status: {}".format(xp.read_voltage_out_status())) + print("Current output status: {}".format(xp.read_current_out_status())) + print("Comm status: {}".format(xp.read_communication_status())) print() diff --git a/PyExpLabSys/file_parsers/avantage.py b/PyExpLabSys/file_parsers/avantage.py index 811f1baf..874e78c2 100644 --- a/PyExpLabSys/file_parsers/avantage.py +++ b/PyExpLabSys/file_parsers/avantage.py @@ -15,77 +15,83 @@ from io import BytesIO from PyExpLabSys.thirdparty import olefile from PyExpLabSys.thirdparty.cached_property import cached_property + olefile.KEEP_UNICODE_NAMES = True import numpy as np import logging + _LOG = logging.getLogger(__name__) # Make the logger follow the logging setup from the caller _LOG.addHandler(logging.NullHandler()) # Named tuples -SP_AXES = namedtuple('SpaceAxes', - 'num_points start step axis_type linear symbol unit label') -DA_AXES = namedtuple('DataAxes', 'start end numaxis unknown') +SP_AXES = namedtuple("SpaceAxes", "num_points start step axis_type linear symbol unit label") +DA_AXES = namedtuple("DataAxes", "start end numaxis unknown") # Translation of space axes type numbers to type strings SPACE_AXES_TYPES = { - 0: 'UNDEFINED', - 1: 'ENERGY', - 2: 'ANGLE', - 3: 'X', - 4: 'Y', - 5: 'LEVEL', - 6: 'ETCHLEVEL', - 10: 'POSITION', - 15: 'DISTANCE', - 16: 'RGB/COUNTS MIXED', - 17: 'PEAKS', - 20: 'PROFILE TYPE', + 0: "UNDEFINED", + 1: "ENERGY", + 2: "ANGLE", + 3: "X", + 4: "Y", + 5: "LEVEL", + 6: "ETCHLEVEL", + 10: "POSITION", + 15: "DISTANCE", + 16: "RGB/COUNTS MIXED", + 17: "PEAKS", + 20: "PROFILE TYPE", } # Defaults for different axes types SPACE_AXES_DEFAULTS = { - 'UNDEFINED': {'label': 'Undefined', 'symbol': '?', 'unit': '?'}, # 0 - 'ENERGY': {'label': 'Energy', 'symbol': 'E', 'unit': 'eV'}, # 1 - 'ANGLE': {'label': 'Angle', 'symbol': '\xd8', 'unit': '\xb0'}, # 2 - 'X': {'label': 'X', 'symbol': 'X', 'unit': '\xb5m'}, # 3 - 'Y': {'label': 'Y', 'symbol': 'Y', 'unit': '\xb5m'}, # 4 - 'LEVEL': {'symbol': 'L', 'unit': ''}, # 5 - 'ETCHLEVEL': {'label': 'Etch Level', 'symbol': 'EtchLevel', 'unit': ''}, # 6 - 'POSITION': {'label': 'Position', 'symbol': 'Pos', 'unit': ''}, # 10 - 'DISTANCE': {'label': 'Distance', 'symbol': 'd', 'unit': '\xb5m'}, # 15 - 'RGB/COUNTS MIXED': {'symbol': 'RGB', 'unit': ''}, # 16 - 'PEAKS': {'symbol': 'P'}, # 17 - 'PROFILE TYPE': {'symbol': '', 'unit': ''}, # 20 + "UNDEFINED": {"label": "Undefined", "symbol": "?", "unit": "?"}, # 0 + "ENERGY": {"label": "Energy", "symbol": "E", "unit": "eV"}, # 1 + "ANGLE": {"label": "Angle", "symbol": "\xd8", "unit": "\xb0"}, # 2 + "X": {"label": "X", "symbol": "X", "unit": "\xb5m"}, # 3 + "Y": {"label": "Y", "symbol": "Y", "unit": "\xb5m"}, # 4 + "LEVEL": {"symbol": "L", "unit": ""}, # 5 + "ETCHLEVEL": {"label": "Etch Level", "symbol": "EtchLevel", "unit": ""}, # 6 + "POSITION": {"label": "Position", "symbol": "Pos", "unit": ""}, # 10 + "DISTANCE": {"label": "Distance", "symbol": "d", "unit": "\xb5m"}, # 15 + "RGB/COUNTS MIXED": {"symbol": "RGB", "unit": ""}, # 16 + "PEAKS": {"symbol": "P"}, # 17 + "PROFILE TYPE": {"symbol": "", "unit": ""}, # 20 # Only found indirectly, so no type number - 'ETCHTIME': {'label': 'Etch Time', 'symbol': 'EtchTime', 'unit': 's'}, + "ETCHTIME": {"label": "Etch Time", "symbol": "EtchTime", "unit": "s"}, } -FIRST_CAP_RE = re.compile('(.)([A-Z][a-z]+)') -ALL_CAP_RE = re.compile('([a-z0-9])([A-Z])') +FIRST_CAP_RE = re.compile("(.)([A-Z][a-z]+)") +ALL_CAP_RE = re.compile("([a-z0-9])([A-Z])") + + def camel_to_underscore(string): - """ Convert camelcase to lowercase and underscore + """Convert camelcase to lowercase and underscore Recipy from http://stackoverflow.com/a/1176023 """ - string = FIRST_CAP_RE.sub(r'\1_\2', string) - return ALL_CAP_RE.sub(r'\1_\2', string).lower() + string = FIRST_CAP_RE.sub(r"\1_\2", string) + return ALL_CAP_RE.sub(r"\1_\2", string).lower() class UnexpectedStructure(Exception): """Exception used if a parse fails due to an unexpected structure""" + pass class UnableToParse(Exception): """Exception used if unable to parse a part""" + pass class NotXPSException(Exception): """Exception for trying to interpret non-XPS data as XPS data""" + pass @@ -95,20 +101,20 @@ class VGDFile(object): def __init__(self, filepath, debug=False): self.filepath = filepath self.olefile = olefile.OleFileIO( - filepath, raise_defects='DEFECT_INCORRECT', debug=debug + filepath, raise_defects="DEFECT_INCORRECT", debug=debug ) self._paths = { - 'summary_properties': '\x05SummaryInformation', - 'properties': '\x05Q5nw4m3lIjudbfwyAayojlptCa', + "summary_properties": "\x05SummaryInformation", + "properties": "\x05Q5nw4m3lIjudbfwyAayojlptCa", } # Raw data seems to be stored as little endian 8 byte floats - self.data_type = np.dtype('4},'.format(current_position), end=' ') + print("Known field at {: >4},".format(current_position), end=" ") name, _, type_ = knowns[current_position] - if type_ == 'x-time': - print('x-time, "{: <19}'.format(name + '"'), - unpack(ENDIAN + 'f', file_.read(4))[0] / 60000) - elif type_ == 'utf16': - print(' utf16, "{: <19}'.format(name + '"'), - parse_utf16_string(file_)) + if type_ == "x-time": + print( + 'x-time, "{: <19}'.format(name + '"'), + unpack(ENDIAN + "f", file_.read(4))[0] / 60000, + ) + elif type_ == "utf16": + print(' utf16, "{: <19}'.format(name + '"'), parse_utf16_string(file_)) else: size = struct.calcsize(type_) - print('{: >6}, "{: <19}'.format(type_, name + '"'), - unpack(type_, file_.read(size))[0]) + print( + '{: >6}, "{: <19}'.format(type_, name + '"'), + unpack(type_, file_.read(size))[0], + ) else: # We do not know about a data field at this position If we have already # collected 4 zero bytes, assume that we are done with this unkonw field, # print and reset - if unknown_bytes[-4:] == b'\x00\x00\x00\x00': - print('Unknown at', unknown_start, repr(unknown_bytes.rstrip(b'\x00'))) - unknown_bytes = b'' + if unknown_bytes[-4:] == b"\x00\x00\x00\x00": + print("Unknown at", unknown_start, repr(unknown_bytes.rstrip(b"\x00"))) + unknown_bytes = b"" unknown_start = None # Read one byte and save it one_byte = file_.read(1) - if unknown_bytes == b'': + if unknown_bytes == b"": # Only start a new collection of unknown bytes, if this byte is not a # zero byte - if one_byte != b'\x00': + if one_byte != b"\x00": unknown_bytes = one_byte unknown_start = file_.tell() - 1 else: @@ -564,10 +570,11 @@ def _parse_data(self, file_): # Read the data into a numpy array file_.seek(self.data_start) - return numpy.fromfile(file_, dtype=' 0: message = UNHANDLED_XML_COMPONENTS.format( - _reg_group_seq[0], 'region group sequence' + _reg_group_seq[0], "region group sequence" ) if EXCEPTION_ON_UNHANDLED: raise ValueError(message) @@ -245,9 +246,7 @@ def __init__(self, filepath, encoding=None): # Check that there are no unhandled XML elements in the root if len(root) > 0: - message = UNHANDLED_XML_COMPONENTS.format( - root[0], 'file' - ) + message = UNHANDLED_XML_COMPONENTS.format(root[0], "file") if EXCEPTION_ON_UNHANDLED: raise ValueError(message) _LOG.warning(message) @@ -287,15 +286,14 @@ def search_regions(self, search_term): def __repr__(self): """Returns class representation""" - return '<{}(filename=\'{}\')>'.format(self.__class__.__name__, - self.filepath) + return "<{}(filename='{}')>".format(self.__class__.__name__, self.filepath) def __str__(self): """Returns str representation""" out = self.__repr__() for region_group in self: - for line in region_group.__str__().split('\n'): - out += '\n ' + line + for line in region_group.__str__().split("\n"): + out += "\n " + line return out @property @@ -314,10 +312,10 @@ def get_analysis_method(self): """ methods = set() for region in self.regions_iter: - methods.add(region.region['analysis_method']) + methods.add(region.region["analysis_method"]) if len(methods) > 1: - message = 'More than one analysis methods is used inside this file' + message = "More than one analysis methods is used inside this file" raise ValueError(message) return methods.pop() @@ -344,20 +342,19 @@ def __init__(self, xml): super(RegionGroup, self).__init__() # Get name, find a string tag with attribute 'name' with value 'name' - self.name = xml.findtext('string[@name=\'name\']') - xml.remove(xml.find('string[@name=\'name\']')) + self.name = xml.findtext("string[@name='name']") + xml.remove(xml.find("string[@name='name']")) - _region_data_seq = xml.find('sequence[@type_name=\'RegionDataSeq\']') - for element in _region_data_seq.findall( - 'struct[@type_name=\'RegionData\']'): - _LOG.debug('Found region: {}'.format(element)) + _region_data_seq = xml.find("sequence[@type_name='RegionDataSeq']") + for element in _region_data_seq.findall("struct[@type_name='RegionData']"): + _LOG.debug("Found region: {}".format(element)) self.append(Region(element)) _region_data_seq.remove(element) # Check that there we nothing else than regions in the region data # sequence if len(_region_data_seq) > 0: message = UNHANDLED_XML_COMPONENTS.format( - _region_data_seq[0], 'region data sequence in region group' + _region_data_seq[0], "region data sequence in region group" ) if EXCEPTION_ON_UNHANDLED: raise ValueError(message) @@ -366,15 +363,13 @@ def __init__(self, xml): xml.remove(_region_data_seq) # Parse parameters - _params = xml.find('sequence[@type_name=\'ParameterSeq\']') + _params = xml.find("sequence[@type_name='ParameterSeq']") self.parameters = simple_convert(_params) xml.remove(_params) # Check if there are any unhandled XML components if len(xml) > 0: - message = UNHANDLED_XML_COMPONENTS.format( - xml[0], 'region group' - ) + message = UNHANDLED_XML_COMPONENTS.format(xml[0], "region group") if EXCEPTION_ON_UNHANDLED: raise ValueError(message) else: @@ -382,13 +377,13 @@ def __init__(self, xml): def __repr__(self): """Returns class representation""" - return '<{}(name=\'{}\')>'.format(self.__class__.__name__, self.name) + return "<{}(name='{}')>".format(self.__class__.__name__, self.name) def __str__(self): """Return the class str representation""" out = self.__repr__() for region in self: - out += '\n ' + region.__str__() + out += "\n " + region.__str__() return out @@ -408,10 +403,19 @@ class Region(object): """ - information_names = ['name', 'region', 'mcd_head', 'mcd_tail', - 'analyzer_info', 'source_info', 'remote_info', - 'cycles', 'compact_cycles', 'transmission', - 'parameters'] + information_names = [ + "name", + "region", + "mcd_head", + "mcd_tail", + "analyzer_info", + "source_info", + "remote_info", + "cycles", + "compact_cycles", + "transmission", + "parameters", + ] def __init__(self, xml): """Parse the XML and initialize internal variables @@ -423,7 +427,7 @@ def __init__(self, xml): # Parse information items self.info = {} for name in self.information_names: - element = xml.find('*[@name=\'{}\']'.format(name)) + element = xml.find("*[@name='{}']".format(name)) self.info[name] = simple_convert(element) # Dynamically create attributes for all the items setattr(self, name, self.info[name]) @@ -431,9 +435,7 @@ def __init__(self, xml): # Check if there are any unhandled XML components if len(xml) > 0: - message = UNHANDLED_XML_COMPONENTS.format( - xml[0], 'region group' - ) + message = UNHANDLED_XML_COMPONENTS.format(xml[0], "region group") if EXCEPTION_ON_UNHANDLED: raise ValueError(message) else: @@ -441,34 +443,39 @@ def __init__(self, xml): def __repr__(self): """Returns class representation""" - return '<{}(name=\'{}\')>'.format( - self.__class__.__name__, self.name, - ) + return "<{}(name='{}')>".format( + self.__class__.__name__, + self.name, + ) @cached_property def x(self): # pylint: disable=invalid-name """Returns the kinetic energy x-values as a Numpy array""" # Calculate the x-values - start = self.region['kinetic_energy'] - end = start + (self.region['values_per_curve'] - 1) *\ - self.region['scan_delta'] - data = np.linspace(start, end, self.region['values_per_curve']) - _LOG.debug('Creating x values from {} to {} in {} steps'.format( - start, end, self.region['values_per_curve'])) + start = self.region["kinetic_energy"] + end = start + (self.region["values_per_curve"] - 1) * self.region["scan_delta"] + data = np.linspace(start, end, self.region["values_per_curve"]) + _LOG.debug( + "Creating x values from {} to {} in {} steps".format( + start, end, self.region["values_per_curve"] + ) + ) return data @cached_property def x_be(self): """Returns the binding energy x-values as a Numpy array""" - if self.region['analysis_method'] != 'XPS': - message = "Analysis_method is {}".format( - self.region['analysis_method']) + if self.region["analysis_method"] != "XPS": + message = "Analysis_method is {}".format(self.region["analysis_method"]) raise NotXPSException(message) # Calculate the x binding energy values - data = self.region['excitation_energy'] - self.x - _LOG.debug('Creating x_be values from {} to {} in {} steps'.format( - data.min(), data.max(), data.size)) + data = self.region["excitation_energy"] - self.x + _LOG.debug( + "Creating x_be values from {} to {} in {} steps".format( + data.min(), data.max(), data.size + ) + ) return data @property @@ -488,7 +495,7 @@ def iter_cycles(self): or use :py:attr:`iter_scans`, which do just that. """ for cycle in self.cycles: - yield (scan['counts'] for scan in cycle['scans']) + yield (scan["counts"] for scan in cycle["scans"]) @property def iter_scans(self): @@ -506,17 +513,17 @@ def y_avg_counts(self): """Returns the average counts as a Numpy array""" vstack = np.vstack(self.iter_scans) data = vstack.mean(axis=0) - _LOG.debug('Creating {} y_avg_counts values from {} scans'.format( - data.size, vstack.shape[0] - )) + _LOG.debug( + "Creating {} y_avg_counts values from {} scans".format(data.size, vstack.shape[0]) + ) return data @cached_property def y_avg_cps(self): """Returns the average counts per second as a Numpy array""" try: - data = self.y_avg_counts / self.region['dwell_time'] - _LOG.debug('Creating {} y_avg_cps values'.format(data.size)) + data = self.y_avg_counts / self.region["dwell_time"] + _LOG.debug("Creating {} y_avg_cps values".format(data.size)) except TypeError: data = None return data @@ -525,10 +532,11 @@ def y_avg_cps(self): def unix_timestamp(self): """Returns the unix timestamp of the first cycle""" for cycle in self.cycles: - return cycle.get('time') + return cycle.get("time") return None class NotXPSException(Exception): """Exception for trying to interpret non-XPS data as XPS data""" + pass diff --git a/PyExpLabSys/file_parsers/total_chrom.py b/PyExpLabSys/file_parsers/total_chrom.py index 54519486..c41af9fa 100644 --- a/PyExpLabSys/file_parsers/total_chrom.py +++ b/PyExpLabSys/file_parsers/total_chrom.py @@ -1,5 +1,3 @@ - - """Experimental parser for total_chrom files from Perkin-Elmer GC's""" @@ -9,188 +7,190 @@ from numpy import fromfile from PyExpLabSys.common.supported_versions import python3_only + python3_only(__file__) TYPE_TRANSLATION = { - 'Uint32': '>I', - 'int32': '>i', - 'long': '>l', - 'BOOL': '>xxx?', + "Uint32": ">I", + "int32": ">i", + "long": ">l", + "BOOL": ">xxx?", } -#FILE_REFERENCE = ( +# FILE_REFERENCE = ( # FIXME -#) +# ) FILE_HEADER = ( - ('signature', 'Uint32'), - ('totalchrom_object_type', 'int32'), - ('file_revision_number', 'Uint32'), - ('technique', 'Uint32'), - ('audit_log_flag', 'BOOL'), - ('electronic_signature_enabled', 'BOOL'), - ('audit_trail_offset', 'Uint32'), - ('file_checksum', 'string'), - ('header_checksum', 'string'), + ("signature", "Uint32"), + ("totalchrom_object_type", "int32"), + ("file_revision_number", "Uint32"), + ("technique", "Uint32"), + ("audit_log_flag", "BOOL"), + ("electronic_signature_enabled", "BOOL"), + ("audit_trail_offset", "Uint32"), + ("file_checksum", "string"), + ("header_checksum", "string"), ) DATA_HEADER = ( - ('author', 'string'), - ('author_host', 'string'), - ('time_and_date_created', 'pnw_date'), - ('editor', 'string'), - ('editor_host', 'string'), - ('time_and_date_last_edited', 'pnw_date'), - ('site_id', 'string'), - ('number_of_times_edited_saved_since_creation', 'long'), - ('edit_flags', 'int32'), - ('file_description', 'string'), + ("author", "string"), + ("author_host", "string"), + ("time_and_date_created", "pnw_date"), + ("editor", "string"), + ("editor_host", "string"), + ("time_and_date_last_edited", "pnw_date"), + ("site_id", "string"), + ("number_of_times_edited_saved_since_creation", "long"), + ("edit_flags", "int32"), + ("file_description", "string"), ) RAW_DATA_HEADER = ( - ('data_header', DATA_HEADER), - ('file_completion_flag', 'int32'), - ('run_log', 'string'), + ("data_header", DATA_HEADER), + ("file_completion_flag", "int32"), + ("run_log", "string"), ) ADHEADER = ( - ('Instrument Number', 'int32'), - ('Time and Date Started', 'pnw_date'), - ('Channel Number', 'int32'), - ('Operator Initials', 'string'), - ('Sequence File Spec', 'string'), #FILE_REFERENCE), - ('Sequence Entry #', 'int32'), - ('Autosampler name', 'string'), - ('Rack Number', 'int32'), - ('Vial Number', 'int32'), - ('Actual Run Time', 'double'), - ('Raw Data Maximum', 'int32'), - ('Raw Data Minimum', 'int32'), - ('Interface serial number', 'string'), - ('AIA raw data conversion scale factor', 'double'), - ('AIA raw data conversion offset', 'double'), - ('Number of Data Points', 'int32'), - ('unknown_1', 'int32'), - ('unkonwn_2', 'int32'), + ("Instrument Number", "int32"), + ("Time and Date Started", "pnw_date"), + ("Channel Number", "int32"), + ("Operator Initials", "string"), + ("Sequence File Spec", "string"), # FILE_REFERENCE), + ("Sequence Entry #", "int32"), + ("Autosampler name", "string"), + ("Rack Number", "int32"), + ("Vial Number", "int32"), + ("Actual Run Time", "double"), + ("Raw Data Maximum", "int32"), + ("Raw Data Minimum", "int32"), + ("Interface serial number", "string"), + ("AIA raw data conversion scale factor", "double"), + ("AIA raw data conversion offset", "double"), + ("Number of Data Points", "int32"), + ("unknown_1", "int32"), + ("unkonwn_2", "int32"), ) SEQ_DESCRIPTION = ( - ('injection_site', 'string'), - ('rack_number', 'int32'), - ('vial_number', 'int32'), - ('number_of_replicates', 'int32'), - ('study_name', 'string'), - ('sample_name', 'string'), - ('sample_number', 'string'), - ('raw_data_filename', 'string'), # FILE REFERENCE - ('result_data_filename', 'string'), # FILE REFERENCE - ('modified_raw_data_filename', 'string'), # FILE REFERENCE - ('baseline_data_filename', 'string'), # FILE REFERENCE - ('instrument_method', 'string'), # FILE REFERENCE - ('process_method', 'string'), # FILE REFERENCE - ('sample_method', 'string'), # FILE REFERENCE - ('report_format_file', 'string'), # FILE REFERENCE - ('printer_device', 'string'), - ('plotter_device', 'string'), - ('actual_sample_amount', 'double'), - ('actual_istd_amount', 'double'), - ('actual_sample_volume', 'double'), - ('dilution_factor', 'double'), - ('multiplier', 'double'), - ('divisor', 'double'), - ('addend', 'double'), - ('normalization_factor', 'double'), - ('calibration_report', 'int32'), - ('calibration_level', 'string'), - ('update_retention_times', 'int32'), - ('sample_id', 'string'), - ('task_id', 'string'), - ('sequence_entry_type', 'int32'), - ('program_name', 'string'), - ('program_path', 'string'), # FILE REFERENCE - ('command_line', 'string'), - ('unknown_string', 'string'), - ('user_data_1', 'string'), - ('user_data_2', 'string'), - ('user_data_3', 'string'), - ('user_data_4', 'string'), - ('user_data_5', 'string'), - ('user_data_6', 'string'), - ('user_data_7', 'string'), - ('user_data_8', 'string'), - ('user_data_9', 'string'), - ('user_data_10', 'string'), - ('cycle_text', 'string'), + ("injection_site", "string"), + ("rack_number", "int32"), + ("vial_number", "int32"), + ("number_of_replicates", "int32"), + ("study_name", "string"), + ("sample_name", "string"), + ("sample_number", "string"), + ("raw_data_filename", "string"), # FILE REFERENCE + ("result_data_filename", "string"), # FILE REFERENCE + ("modified_raw_data_filename", "string"), # FILE REFERENCE + ("baseline_data_filename", "string"), # FILE REFERENCE + ("instrument_method", "string"), # FILE REFERENCE + ("process_method", "string"), # FILE REFERENCE + ("sample_method", "string"), # FILE REFERENCE + ("report_format_file", "string"), # FILE REFERENCE + ("printer_device", "string"), + ("plotter_device", "string"), + ("actual_sample_amount", "double"), + ("actual_istd_amount", "double"), + ("actual_sample_volume", "double"), + ("dilution_factor", "double"), + ("multiplier", "double"), + ("divisor", "double"), + ("addend", "double"), + ("normalization_factor", "double"), + ("calibration_report", "int32"), + ("calibration_level", "string"), + ("update_retention_times", "int32"), + ("sample_id", "string"), + ("task_id", "string"), + ("sequence_entry_type", "int32"), + ("program_name", "string"), + ("program_path", "string"), # FILE REFERENCE + ("command_line", "string"), + ("unknown_string", "string"), + ("user_data_1", "string"), + ("user_data_2", "string"), + ("user_data_3", "string"), + ("user_data_4", "string"), + ("user_data_5", "string"), + ("user_data_6", "string"), + ("user_data_7", "string"), + ("user_data_8", "string"), + ("user_data_9", "string"), + ("user_data_10", "string"), + ("cycle_text", "string"), ) NAIBOXINFO = ( - ('delay_time_(min.)', 'double'), - ('run_time_for_data_collection_(min.)', 'double'), - ('total_run_time_(min.)', 'double'), - ('sampling_rate_(pts/sec.)', 'double'), - ('sampling_code', 'int32'), - ('a/d_voltage_range', 'int32'), - ('data_collection_channel', 'int32'), - ('store_all_data_flag_(i.e._run_time_=_total_time)', 'BOOL'), - ('autosampler_injection_source', 'int32'), - ('use_bipolar_mode', 'BOOL'), + ("delay_time_(min.)", "double"), + ("run_time_for_data_collection_(min.)", "double"), + ("total_run_time_(min.)", "double"), + ("sampling_rate_(pts/sec.)", "double"), + ("sampling_code", "int32"), + ("a/d_voltage_range", "int32"), + ("data_collection_channel", "int32"), + ("store_all_data_flag_(i.e._run_time_=_total_time)", "BOOL"), + ("autosampler_injection_source", "int32"), + ("use_bipolar_mode", "BOOL"), ) INSTRUMENT_DATA_HEADER = ( - ('data_header', DATA_HEADER), - ('instrument_header_text', 'string'), - ('instrument_type', 'INST_TYPE'), - ('number_of_channels_for_data_collection', 'int32'), - ('instrument_text', 'string'), + ("data_header", DATA_HEADER), + ("instrument_header_text", "string"), + ("instrument_type", "INST_TYPE"), + ("number_of_channels_for_data_collection", "int32"), + ("instrument_text", "string"), ) INSTRUMENT_METHOD_STRUCTURE = ( - ('instrument_data_header', INSTRUMENT_DATA_HEADER), - #('intelligent_interface_parameters', NAIBOXINFO), - #('instrument_configuration_parameters', INST_CONFIG), - #('real-time_plot_parameters', array), - #('link_parameters_array', '), + ("instrument_data_header", INSTRUMENT_DATA_HEADER), + # ('intelligent_interface_parameters', NAIBOXINFO), + # ('instrument_configuration_parameters', INST_CONFIG), + # ('real-time_plot_parameters', array), + # ('link_parameters_array', '), ) + def parse_simple_types(specification, file_): """Parse simple types""" - #print("PARSE") + # print("PARSE") output = {} for name, type_ in specification: - #print("\nPARSE SPEC", name, type_, "AT", file_.tell()) - if type_ == 'string': - size = unpack('>i', file_.read(4))[0] - format_ = '>{}s'.format(size) + # print("\nPARSE SPEC", name, type_, "AT", file_.tell()) + if type_ == "string": + size = unpack(">i", file_.read(4))[0] + format_ = ">{}s".format(size) if size % 4 != 0: - format_ += 'x' * (4 - size % 4) + format_ += "x" * (4 - size % 4) bytes_ = file_.read(calcsize(format_)) - #print("Read {} bytes of string".format(len(bytes_))) - value = unpack(format_, bytes_)[0].decode('ascii') - elif type_ == 'double': + # print("Read {} bytes of string".format(len(bytes_))) + value = unpack(format_, bytes_)[0].decode("ascii") + elif type_ == "double": # Appearently, in this serialization, the two groups of 4 # bytes are in reversed order, so special case it first_four = file_.read(4) last_four = file_.read(4) - value = unpack('>d', last_four + first_four)[0] - elif type_ == 'pnw_date': - unix_time = unpack('>i', file_.read(4))[0] + value = unpack(">d", last_four + first_four)[0] + elif type_ == "pnw_date": + unix_time = unpack(">i", file_.read(4))[0] # We do not fully understand the time stamp, FIXME - #import datetime - #print( + # import datetime + # print( # datetime.datetime.fromtimestamp( # unix_time # ).strftime('%Y-%m-%d %H:%M:%S') - #) - timestamp = unpack('>bbhbbbb', file_.read(8)) - #timestamp = unpack('>hBBBBBB', file_.read(8)) - #print("TIMESTAMP", timestamp) - #file_.read(11) + # ) + timestamp = unpack(">bbhbbbb", file_.read(8)) + # timestamp = unpack('>hBBBBBB', file_.read(8)) + # print("TIMESTAMP", timestamp) + # file_.read(11) # Just save unix time for now value = unix_time @@ -200,7 +200,7 @@ def parse_simple_types(specification, file_): # struct parseable type format_ = TYPE_TRANSLATION[type_] value = unpack(format_, file_.read(calcsize(format_)))[0] - #print("VALUE IS", repr(value)) + # print("VALUE IS", repr(value)) output[name] = value return output @@ -215,7 +215,7 @@ def parse_array(type_, file_): Returns: numpy.array: The parsed array """ - number_of_points = unpack('>i', file_.read(4))[0] + number_of_points = unpack(">i", file_.read(4))[0] array = fromfile(file_, dtype=type_, count=number_of_points) return array @@ -225,7 +225,7 @@ class Raw: def __init__(self, filepath): self.filepath = filepath - with open(filepath, 'rb') as file_: + with open(filepath, "rb") as file_: # File starts with a file header self.file_header = parse_simple_types(FILE_HEADER, file_) @@ -235,27 +235,29 @@ def __init__(self, filepath): self.ad_header = parse_simple_types(ADHEADER, file_) # Sequence FIXME - #file_.read(8) - #print("\n\n\n##############################") + # file_.read(8) + # print("\n\n\n##############################") self.seq_description = parse_simple_types(SEQ_DESCRIPTION, file_) - #print(file_.tell()) - self.raw_data_points = parse_array('>i4', file_) + # print(file_.tell()) + self.raw_data_points = parse_array(">i4", file_) - #print("\n\n\n##############################") - #self.instrument_method_structure = parse_simple_types( + # print("\n\n\n##############################") + # self.instrument_method_structure = parse_simple_types( # INSTRUMENT_METHOD_STRUCTURE, file_, - #) - + # ) def module_demo(): """Module demon""" - filepath = ('/home/kenni/surfcat/setups/307-059-largeCO2MEA/' - 'GC_parsing/Test8_Carrier=Ar_70mLH2_26mLCO_60C_Att' - '=-2_NoRamp.raw') + filepath = ( + "/home/kenni/surfcat/setups/307-059-largeCO2MEA/" + "GC_parsing/Test8_Carrier=Ar_70mLH2_26mLCO_60C_Att" + "=-2_NoRamp.raw" + ) raw_file = Raw(filepath) print(raw_file) -if __name__ == '__main__': + +if __name__ == "__main__": module_demo() diff --git a/PyExpLabSys/settings.py b/PyExpLabSys/settings.py index 579feacb..f2de28b8 100644 --- a/PyExpLabSys/settings.py +++ b/PyExpLabSys/settings.py @@ -60,15 +60,13 @@ # User settings -if os.environ.get('READTHEDOCS') == 'True': +if os.environ.get("READTHEDOCS") == "True": # Special case for read the docs - USERSETTINGS_PATH = Path.cwd().parents[0] / 'bootstrap' / 'user_settings.yaml' + USERSETTINGS_PATH = Path.cwd().parents[0] / "bootstrap" / "user_settings.yaml" else: # Krabbe is the sole responsible person for the MAC check, if it breaks bug him - if sys.platform.lower().startswith('linux') or sys.platform.lower() == "darwin": - USERSETTINGS_PATH = ( - Path.home() / '.config' / 'PyExpLabSys' / 'user_settings.yaml' - ) + if sys.platform.lower().startswith("linux") or sys.platform.lower() == "darwin": + USERSETTINGS_PATH = Path.home() / ".config" / "PyExpLabSys" / "user_settings.yaml" else: USERSETTINGS_PATH = None @@ -76,9 +74,9 @@ def value_str(obj): """Return a object and type str or NOT_SET if obj is None""" if obj is None: - return 'NOT_SET' + return "NOT_SET" else: - return '{} ({})'.format(obj, obj.__class__.__name__) + return "{} ({})".format(obj, obj.__class__.__name__) class Settings(object): @@ -109,7 +107,7 @@ class Settings(object): _access_lock = Lock() def __init__(self): - LOG.info('Init') + LOG.info("Init") with self._access_lock: if self.settings is None: self._load_settings() @@ -120,30 +118,29 @@ def _load_settings(self): This is done when the first Settings object is instantiated """ default_settings = { - 'sql_server_host': None, - 'sql_database': None, - 'common_sql_reader_user': None, - 'common_sql_reader_password': None, - 'common_liveserver_host': None, - 'common_liveserver_port': None, - 'util_log_warning_email': None, - 'util_log_error_email': None, - 'util_log_mail_host': None, - 'util_log_max_emails_per_period': 5, - 'util_log_email_throttle_time': 86400, # 1 day - 'util_log_backlog_limit': 250 + "sql_server_host": None, + "sql_database": None, + "common_sql_reader_user": None, + "common_sql_reader_password": None, + "common_liveserver_host": None, + "common_liveserver_port": None, + "util_log_warning_email": None, + "util_log_error_email": None, + "util_log_mail_host": None, + "util_log_max_emails_per_period": 5, + "util_log_email_throttle_time": 86400, # 1 day + "util_log_backlog_limit": 250, } user_settings = {} if USERSETTINGS_PATH is not None and USERSETTINGS_PATH.exists(): try: - user_settings = yaml.load(USERSETTINGS_PATH.read_text(), - yaml.SafeLoader) + user_settings = yaml.load(USERSETTINGS_PATH.read_text(), yaml.SafeLoader) except Exception: - LOG.exception('Exception during loading of user settings') + LOG.exception("Exception during loading of user settings") # FIXME check user_settings keys else: - msg = 'No user settings found, file %s does not exist or is not readable' + msg = "No user settings found, file %s does not exist or is not readable" LOG.info(msg, USERSETTINGS_PATH) self.__class__.settings = ChainMap(user_settings, default_settings) @@ -155,7 +152,7 @@ def __setattr__(self, key, value): if key in self.settings: self.settings[key] = value else: - msg = 'Only settings that have a default can be set. They are:\n{}' + msg = "Only settings that have a default can be set. They are:\n{}" # Pretty format the list of names in the exception raise AttributeError(msg.format(pformat(self.settings_names))) @@ -165,17 +162,17 @@ def __getattr__(self, key): if key in self.settings: value = self.settings[key] else: - msg = 'Invalid settings name: {}. Available settings are:\n{}' + msg = "Invalid settings name: {}. Available settings are:\n{}" # Pretty format the list of names in the exception raise AttributeError(msg.format(key, pformat(self.settings_names))) if value is None: msg = ( 'The setting "{}" is indicated in the defaults as *requiring* a ' - 'user setting before it can be used. Fill in the value in the ' + "user setting before it can be used. Fill in the value in the " 'user settings file "{}" or instantiate a ' - 'PyExpLabSys.settings.Settings object and set the value there, ' - '*before* attempting to use it.' + "PyExpLabSys.settings.Settings object and set the value there, " + "*before* attempting to use it." ) raise AttributeError(msg.format(key, USERSETTINGS_PATH)) @@ -184,7 +181,7 @@ def __getattr__(self, key): def print_settings(self): """Pretty print of all default and user settings""" user_settings, default_settings = self.settings.maps - print_template = '{{: <{}}}: {{: <{}}} {{: <{}}}' + print_template = "{{: <{}}}: {{: <{}}} {{: <{}}}" # Calculate key length max_key_length = max(len(str(key)) for key in self.settings) @@ -205,12 +202,12 @@ def print_settings(self): ) # Printout the settings - print('Settings') - print(print_template.format('Key', 'Default', 'User')) - print('=' * len(print_template.format('', '', ''))) + print("Settings") + print(print_template.format("Key", "Default", "User")) + print("=" * len(print_template.format("", "", ""))) for key in sorted(self.settings.keys()): default = default_strs[key] - print(print_template.format(key, default, user_strs.get(key, ''))) + print(print_template.format(key, default, user_strs.get(key, ""))) def main(): @@ -227,5 +224,5 @@ def main(): settings.print_settings() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/PyExpLabSys/thirdparty/cached_property.py b/PyExpLabSys/thirdparty/cached_property.py index a8383d0e..e5750377 100644 --- a/PyExpLabSys/thirdparty/cached_property.py +++ b/PyExpLabSys/thirdparty/cached_property.py @@ -9,26 +9,26 @@ All credits goes to the original author """ -__author__ = 'Daniel Greenfeld' -__email__ = 'pydanny@gmail.com' -__version__ = '0.1.5' -__license__ = 'BSD' +__author__ = "Daniel Greenfeld" +__email__ = "pydanny@gmail.com" +__version__ = "0.1.5" +__license__ = "BSD" import threading # pylint: disable=invalid-name,too-few-public-methods class cached_property(object): - """ A property that is only computed once per instance and then replaces - itself with an ordinary attribute. Deleting the attribute resets the - property. + """A property that is only computed once per instance and then replaces + itself with an ordinary attribute. Deleting the attribute resets the + property. - Source: https://github.com/bottlepy/bottle/commit/ - fa7733e075da0d790d809aa3d2f53071897e6f76 - """ + Source: https://github.com/bottlepy/bottle/commit/ + fa7733e075da0d790d809aa3d2f53071897e6f76 + """ def __init__(self, func): - self.__doc__ = getattr(func, '__doc__') + self.__doc__ = getattr(func, "__doc__") self.func = func def __get__(self, obj, cls): @@ -40,9 +40,10 @@ def __get__(self, obj, cls): # pylint: disable=invalid-name,too-few-public-methods class threaded_cached_property(cached_property): - """ A cached_property version for use in environments where multiple - threads might concurrently try to access the property. - """ + """A cached_property version for use in environments where multiple + threads might concurrently try to access the property. + """ + def __init__(self, func): super(threaded_cached_property, self).__init__(func) self.lock = threading.RLock() diff --git a/PyExpLabSys/thirdparty/olefile.py b/PyExpLabSys/thirdparty/olefile.py index 1b5fc0b8..afcb1cfb 100644 --- a/PyExpLabSys/thirdparty/olefile.py +++ b/PyExpLabSys/thirdparty/olefile.py @@ -26,14 +26,16 @@ # This import enables print() as a function rather than a keyword # (main requirement to be compatible with Python 3.x) # The comment on the line below should be printed on Python 2.5 or older: -from __future__ import print_function # This version of olefile requires Python 2.6+ or 3.x. +from __future__ import ( + print_function, +) # This version of olefile requires Python 2.6+ or 3.x. -__author__ = "Philippe Lagadec" -__date__ = "2014-11-25" -__version__ = '0.41' +__author__ = "Philippe Lagadec" +__date__ = "2014-11-25" +__version__ = "0.41" -#--- LICENSE ------------------------------------------------------------------ +# --- LICENSE ------------------------------------------------------------------ # olefile (formerly OleFileIO_PL) is copyright (c) 2005-2014 Philippe Lagadec # (http://www.decalage.info) @@ -90,7 +92,7 @@ # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # CHANGELOG: (only olefile/OleFileIO_PL changes compared to PIL 1.1.6) # 2005-05-11 v0.10 PL: - a few fixes for Python 2.4 compatibility # (all changes flagged with [PL]) @@ -179,7 +181,7 @@ # data in a string buffer and file-like objects. # 2014-11-21 PL: - updated comments according to Pillow's commits -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # TODO (for version 1.0): # + get rid of print statements, to simplify Python 2.x and 3.x support # + add is_stream and is_storage @@ -223,7 +225,7 @@ # see issue #6 on Bitbucket: # https://bitbucket.org/decalage/olefileio_pl/issue/6/improve-olefileio_pl-to-write-ole-files -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # NOTES from PIL 1.1.6: # History: @@ -248,7 +250,7 @@ # the actual functionality of the Software represents the correct # functionality" -- Microsoft, in the OLE format specification -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ import io @@ -257,10 +259,10 @@ from io import BytesIO import struct, array, os.path, datetime -#=== COMPATIBILITY WORKAROUNDS ================================================ +# === COMPATIBILITY WORKAROUNDS ================================================ -#[PL] Define explicitly the public API to avoid private objects in pydoc: -#TODO: add more +# [PL] Define explicitly the public API to avoid private objects in pydoc: +# TODO: add more # __all__ = ['OleFileIO', 'isOleFile', 'MAGIC'] # For Python 3.x, need to redefine long as int: @@ -275,14 +277,14 @@ # no xrange, for Python 3 it was renamed as range: iterrange = range -#[PL] workaround to fix an issue with array item size on 64 bits systems: -if array.array('L').itemsize == 4: +# [PL] workaround to fix an issue with array item size on 64 bits systems: +if array.array("L").itemsize == 4: # on 32 bits platforms, long integers in an array are 32 bits: - UINT32 = 'L' -elif array.array('I').itemsize == 4: + UINT32 = "L" +elif array.array("I").itemsize == 4: # on 64 bits platforms, integers in an array are 32 bits: - UINT32 = 'I' -elif array.array('i').itemsize == 4: + UINT32 = "I" +elif array.array("i").itemsize == 4: # On 64 bit Jython, signed integers ('i') are the only way to store our 32 # bit values in an array in a *somewhat* reasonable way, as the otherwise # perfectly suited 'H' (unsigned int, 32 bits) results in a completely @@ -294,14 +296,14 @@ # 0xFFFFFFFF can be used. This way it is possible to use the same comparing # operations on all platforms / implementations. The corresponding code # lines are flagged with a 'JYTHON-WORKAROUND' tag below. - UINT32 = 'i' + UINT32 = "i" else: - raise ValueError('Need to fix a bug with 32 bit arrays, please contact author...') + raise ValueError("Need to fix a bug with 32 bit arrays, please contact author...") -#[PL] These workarounds were inspired from the Path module +# [PL] These workarounds were inspired from the Path module # (see http://www.jorendorff.com/articles/python/path/) -#TODO: test with old Python versions +# TODO: test with old Python versions # Pre-2.3 workaround for basestring. try: @@ -313,23 +315,30 @@ except NameError: basestring = str -#[PL] Experimental setting: if True, OLE filenames will be kept in Unicode +# [PL] Experimental setting: if True, OLE filenames will be kept in Unicode # if False (default PIL behaviour), all filenames are converted to Latin-1. KEEP_UNICODE_NAMES = False -#=== DEBUGGING =============================================================== +# === DEBUGGING =============================================================== -#TODO: replace this by proper logging +# TODO: replace this by proper logging -#[PL] DEBUG display mode: False by default, use set_debug_mode() or "-d" on +# [PL] DEBUG display mode: False by default, use set_debug_mode() or "-d" on # command line to change it. DEBUG_MODE = False + + def debug_print(msg): print(msg) + + def debug_pass(msg): pass + + debug = debug_pass + def set_debug_mode(debug_mode): """ Set debug mode on or off, to control display of debugging messages. @@ -343,44 +352,76 @@ def set_debug_mode(debug_mode): debug = debug_pass -#=== CONSTANTS =============================================================== +# === CONSTANTS =============================================================== # magic bytes that should be at the beginning of every OLE file: -MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' +MAGIC = b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" -#[PL]: added constants for Sector IDs (from AAF specifications) -MAXREGSECT = 0xFFFFFFFA # (-6) maximum SECT -DIFSECT = 0xFFFFFFFC # (-4) denotes a DIFAT sector in a FAT -FATSECT = 0xFFFFFFFD # (-3) denotes a FAT sector in a FAT -ENDOFCHAIN = 0xFFFFFFFE # (-2) end of a virtual stream chain -FREESECT = 0xFFFFFFFF # (-1) unallocated sector +# [PL]: added constants for Sector IDs (from AAF specifications) +MAXREGSECT = 0xFFFFFFFA # (-6) maximum SECT +DIFSECT = 0xFFFFFFFC # (-4) denotes a DIFAT sector in a FAT +FATSECT = 0xFFFFFFFD # (-3) denotes a FAT sector in a FAT +ENDOFCHAIN = 0xFFFFFFFE # (-2) end of a virtual stream chain +FREESECT = 0xFFFFFFFF # (-1) unallocated sector -#[PL]: added constants for Directory Entry IDs (from AAF specifications) -MAXREGSID = 0xFFFFFFFA # (-6) maximum directory entry ID -NOSTREAM = 0xFFFFFFFF # (-1) unallocated directory entry +# [PL]: added constants for Directory Entry IDs (from AAF specifications) +MAXREGSID = 0xFFFFFFFA # (-6) maximum directory entry ID +NOSTREAM = 0xFFFFFFFF # (-1) unallocated directory entry -#[PL] object types in storage (from AAF specifications) -STGTY_EMPTY = 0 # empty directory entry (according to OpenOffice.org doc) -STGTY_STORAGE = 1 # element is a storage object -STGTY_STREAM = 2 # element is a stream object -STGTY_LOCKBYTES = 3 # element is an ILockBytes object -STGTY_PROPERTY = 4 # element is an IPropertyStorage object -STGTY_ROOT = 5 # element is a root storage +# [PL] object types in storage (from AAF specifications) +STGTY_EMPTY = 0 # empty directory entry (according to OpenOffice.org doc) +STGTY_STORAGE = 1 # element is a storage object +STGTY_STREAM = 2 # element is a stream object +STGTY_LOCKBYTES = 3 # element is an ILockBytes object +STGTY_PROPERTY = 4 # element is an IPropertyStorage object +STGTY_ROOT = 5 # element is a root storage # # -------------------------------------------------------------------- # property types -VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6; -VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11; -VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17; -VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23; -VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28; -VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64; -VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68; -VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72; -VT_VECTOR=0x1000; +VT_EMPTY = 0 +VT_NULL = 1 +VT_I2 = 2 +VT_I4 = 3 +VT_R4 = 4 +VT_R8 = 5 +VT_CY = 6 +VT_DATE = 7 +VT_BSTR = 8 +VT_DISPATCH = 9 +VT_ERROR = 10 +VT_BOOL = 11 +VT_VARIANT = 12 +VT_UNKNOWN = 13 +VT_DECIMAL = 14 +VT_I1 = 16 +VT_UI1 = 17 +VT_UI2 = 18 +VT_UI4 = 19 +VT_I8 = 20 +VT_UI8 = 21 +VT_INT = 22 +VT_UINT = 23 +VT_VOID = 24 +VT_HRESULT = 25 +VT_PTR = 26 +VT_SAFEARRAY = 27 +VT_CARRAY = 28 +VT_USERDEFINED = 29 +VT_LPSTR = 30 +VT_LPWSTR = 31 +VT_FILETIME = 64 +VT_BLOB = 65 +VT_STREAM = 66 +VT_STORAGE = 67 +VT_STREAMED_OBJECT = 68 +VT_STORED_OBJECT = 69 +VT_BLOB_OBJECT = 70 +VT_CF = 71 +VT_CLSID = 72 +VT_VECTOR = 0x1000 # Special property identifiers DICTIONARY_PROPERTY_IDENTIFIER = 0x00000000 @@ -395,24 +436,24 @@ def set_debug_mode(debug_mode): for keyword, var in list(vars().items()): if keyword[:3] == "VT_": VT[var] = keyword - if keyword.endswith('_PROPERTY_IDENTIFIER'): + if keyword.endswith("_PROPERTY_IDENTIFIER"): SPECIAL_PROPERTY_IDENTIFIERS.append(var) # map property id for simple types to struct formats SIMPLE_TYPES = { - VT_I2: '= MINIMAL_OLEFILE_SIZE: # filename is a bytes string containing the OLE file to be parsed: - header = filename[:len(MAGIC)] + header = filename[: len(MAGIC)] else: # string-like object: filename of file on disk - header = open(filename, 'rb').read(len(MAGIC)) + header = open(filename, "rb").read(len(MAGIC)) if header == MAGIC: return True else: @@ -503,35 +544,37 @@ def isOleFile (filename): # version for Python 2.x def i8(c): return ord(c) + else: # version for Python 3.x def i8(c): return c if c.__class__ is int else c[0] -#TODO: replace i16 and i32 with more readable struct.unpack equivalent? +# TODO: replace i16 and i32 with more readable struct.unpack equivalent? + -def i16(c, o = 0): +def i16(c, o=0): """ Converts a 2-bytes (16 bits) string to an integer. :param c: string containing bytes to convert :param o: offset of bytes to convert in string """ - return i8(c[o]) | (i8(c[o+1])<<8) + return i8(c[o]) | (i8(c[o + 1]) << 8) -def i32(c, o = 0): +def i32(c, o=0): """ Converts a 4-bytes (32 bits) string to an integer. :param c: string containing bytes to convert :param o: offset of bytes to convert in string """ -## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)) -## # [PL]: added int() because "<<" gives long int since Python 2.4 + ## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)) + ## # [PL]: added int() because "<<" gives long int since Python 2.4 # copied from Pillow's _binary: - return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24) + return i8(c[o]) | (i8(c[o + 1]) << 8) | (i8(c[o + 2]) << 16) | (i8(c[o + 3]) << 24) def _clsid(clsid): @@ -545,55 +588,57 @@ def _clsid(clsid): # (PL: why not simply return the string with zeroes?) if not clsid.strip(b"\0"): return "" - return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) % - ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) + - tuple(map(i8, clsid[8:16])))) - + return ("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) % ( + (i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) + tuple(map(i8, clsid[8:16])) + ) # UNICODE support: # (necessary to handle storages/streams names which use Unicode) -def _unicode(s, errors='replace'): + +def _unicode(s, errors="replace"): """ Map unicode string to Latin 1. (Python with Unicode support) :param s: UTF-16LE unicode string to convert to Latin-1 :param errors: 'replace', 'ignore' or 'strict'. """ - #TODO: test if it OleFileIO works with Unicode strings, instead of + # TODO: test if it OleFileIO works with Unicode strings, instead of # converting to Latin-1. try: # First the string is converted to plain Unicode: # (assuming it is encoded as UTF-16 little-endian) - u = s.decode('UTF-16LE', errors) + u = s.decode("UTF-16LE", errors) if bytes is not str or KEEP_UNICODE_NAMES: return u else: # Second the unicode string is converted to Latin-1 - return u.encode('latin_1', errors) + return u.encode("latin_1", errors) except: # there was an error during Unicode to Latin-1 conversion: - raise IOError('incorrect Unicode name') + raise IOError("incorrect Unicode name") def filetime2datetime(filetime): - """ - convert FILETIME (64 bits int) to Python datetime.datetime - """ - # TODO: manage exception when microseconds is too large - # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/ - _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0) - #debug('timedelta days=%d' % (filetime//(10*1000000*3600*24))) - return _FILETIME_null_date + datetime.timedelta(microseconds=filetime//10) + """ + convert FILETIME (64 bits int) to Python datetime.datetime + """ + # TODO: manage exception when microseconds is too large + # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/ + _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0) + # debug('timedelta days=%d' % (filetime//(10*1000000*3600*24))) + return _FILETIME_null_date + datetime.timedelta(microseconds=filetime // 10) + +# === NAMED TUPLES ============================================================ -#=== NAMED TUPLES ============================================================ +UnconvertedPropertyValue = collections.namedtuple( + "UnconvertedPropertyValue", ["type", "raw_bytes"] +) -UnconvertedPropertyValue = \ - collections.namedtuple('UnconvertedPropertyValue', ['type', 'raw_bytes']) +# === CLASSES ================================================================== -#=== CLASSES ================================================================== class OleMetadata: """ @@ -630,20 +675,60 @@ class to parse and store metadata from standard properties of OLE files. # attribute names for SummaryInformation stream properties: # (ordered by property id, starting at 1) - SUMMARY_ATTRIBS = ['codepage', 'title', 'subject', 'author', 'keywords', 'comments', - 'template', 'last_saved_by', 'revision_number', 'total_edit_time', - 'last_printed', 'create_time', 'last_saved_time', 'num_pages', - 'num_words', 'num_chars', 'thumbnail', 'creating_application', - 'security'] + SUMMARY_ATTRIBS = [ + "codepage", + "title", + "subject", + "author", + "keywords", + "comments", + "template", + "last_saved_by", + "revision_number", + "total_edit_time", + "last_printed", + "create_time", + "last_saved_time", + "num_pages", + "num_words", + "num_chars", + "thumbnail", + "creating_application", + "security", + ] # attribute names for DocumentSummaryInformation stream properties: # (ordered by property id, starting at 1) - DOCSUM_ATTRIBS = ['codepage_doc', 'category', 'presentation_target', 'bytes', 'lines', 'paragraphs', - 'slides', 'notes', 'hidden_slides', 'mm_clips', - 'scale_crop', 'heading_pairs', 'titles_of_parts', 'manager', - 'company', 'links_dirty', 'chars_with_spaces', 'unused', 'shared_doc', - 'link_base', 'hlinks', 'hlinks_changed', 'version', 'dig_sig', - 'content_type', 'content_status', 'language', 'doc_version'] + DOCSUM_ATTRIBS = [ + "codepage_doc", + "category", + "presentation_target", + "bytes", + "lines", + "paragraphs", + "slides", + "notes", + "hidden_slides", + "mm_clips", + "scale_crop", + "heading_pairs", + "titles_of_parts", + "manager", + "company", + "links_dirty", + "chars_with_spaces", + "unused", + "shared_doc", + "link_base", + "hlinks", + "hlinks_changed", + "version", + "dig_sig", + "content_type", + "content_status", + "language", + "doc_version", + ] def __init__(self): """ @@ -700,7 +785,6 @@ def __init__(self): self.language = None self.doc_version = None - def parse_properties(self, olefile): """ Parse standard properties of an OLE file, from the streams @@ -710,44 +794,45 @@ def parse_properties(self, olefile): If a property is not present, its value is set to None. """ # first set all attributes to None: - for attrib in (self.SUMMARY_ATTRIBS + self.DOCSUM_ATTRIBS): + for attrib in self.SUMMARY_ATTRIBS + self.DOCSUM_ATTRIBS: setattr(self, attrib, None) if olefile.exists("\x05SummaryInformation"): # get properties from the stream: # (converting timestamps to python datetime, except total_edit_time, # which is property #10) - props = olefile.getproperties("\x05SummaryInformation", - convert_time=True, no_conversion=[10]) + props = olefile.getproperties( + "\x05SummaryInformation", convert_time=True, no_conversion=[10] + ) # store them into this object's attributes: for i in range(len(self.SUMMARY_ATTRIBS)): # ids for standards properties start at 0x01, until 0x13 - value = props.get(i+1, None) + value = props.get(i + 1, None) setattr(self, self.SUMMARY_ATTRIBS[i], value) if olefile.exists("\x05DocumentSummaryInformation"): # get properties from the stream: - props = olefile.getproperties("\x05DocumentSummaryInformation", - convert_time=True) + props = olefile.getproperties("\x05DocumentSummaryInformation", convert_time=True) # store them into this object's attributes: for i in range(len(self.DOCSUM_ATTRIBS)): # ids for standards properties start at 0x01, until 0x13 - value = props.get(i+1, None) + value = props.get(i + 1, None) setattr(self, self.DOCSUM_ATTRIBS[i], value) def dump(self): """ Dump all metadata, for debugging purposes. """ - print('Properties from SummaryInformation stream:') + print("Properties from SummaryInformation stream:") for prop in self.SUMMARY_ATTRIBS: value = getattr(self, prop) - print('- %s: %s' % (prop, repr(value))) - print('Properties from DocumentSummaryInformation stream:') + print("- %s: %s" % (prop, repr(value))) + print("Properties from DocumentSummaryInformation stream:") for prop in self.DOCSUM_ATTRIBS: value = getattr(self, prop) - print('- %s: %s' % (prop, repr(value))) + print("- %s: %s" % (prop, repr(value))) + +# --- _OleStream --------------------------------------------------------------- -#--- _OleStream --------------------------------------------------------------- class _OleStream(io.BytesIO): """ @@ -783,35 +868,37 @@ def __init__(self, fp, sect, size, offset, sectorsize, fat, filesize): :param filesize: size of OLE file (for debugging) :returns: a BytesIO instance containing the OLE stream """ - debug('_OleStream.__init__:') - debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s' - %(sect,sect,size,offset,sectorsize,len(fat), repr(fp))) - #[PL] To detect malformed documents with FAT loops, we compute the + debug("_OleStream.__init__:") + debug( + " sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s" + % (sect, sect, size, offset, sectorsize, len(fat), repr(fp)) + ) + # [PL] To detect malformed documents with FAT loops, we compute the # expected number of sectors in the stream: unknown_size = False - if size==0x7FFFFFFF: + if size == 0x7FFFFFFF: # this is the case when called from OleFileIO._open(), and stream # size is not known in advance (for example when reading the # Directory stream). Then we can only guess maximum size: - size = len(fat)*sectorsize + size = len(fat) * sectorsize # and we keep a record that size was unknown: unknown_size = True - debug(' stream with UNKNOWN SIZE') - nb_sectors = (size + (sectorsize-1)) // sectorsize - debug('nb_sectors = %d' % nb_sectors) + debug(" stream with UNKNOWN SIZE") + nb_sectors = (size + (sectorsize - 1)) // sectorsize + debug("nb_sectors = %d" % nb_sectors) # This number should (at least) be less than the total number of # sectors in the given FAT: if nb_sectors > len(fat): - raise IOError('malformed OLE document, stream too large') + raise IOError("malformed OLE document, stream too large") # optimization(?): data is first a list of strings, and join() is called # at the end to concatenate all in one string. # (this may not be really useful with recent Python versions) data = [] # if size is zero, then first sector index should be ENDOFCHAIN: if size == 0 and sect != ENDOFCHAIN: - debug('size == 0 and sect != ENDOFCHAIN:') - raise IOError('incorrect OLE sector index for empty stream') - #[PL] A fixed-length for loop is used instead of an undefined while + debug("size == 0 and sect != ENDOFCHAIN:") + raise IOError("incorrect OLE sector index for empty stream") + # [PL] A fixed-length for loop is used instead of an undefined while # loop to avoid DoS attacks: for i in range(nb_sectors): # Sector index may be ENDOFCHAIN, but only if size was unknown @@ -820,46 +907,56 @@ def __init__(self, fp, sect, size, offset, sectorsize, fat, filesize): break else: # else this means that the stream is smaller than declared: - debug('sect=ENDOFCHAIN before expected size') - raise IOError('incomplete OLE stream') + debug("sect=ENDOFCHAIN before expected size") + raise IOError("incomplete OLE stream") # sector index should be within FAT: - if sect<0 or sect>=len(fat): - debug('sect=%d (%X) / len(fat)=%d' % (sect, sect, len(fat))) - debug('i=%d / nb_sectors=%d' %(i, nb_sectors)) -## tmp_data = b"".join(data) -## f = open('test_debug.bin', 'wb') -## f.write(tmp_data) -## f.close() -## debug('data read so far: %d bytes' % len(tmp_data)) - raise IOError('incorrect OLE FAT, sector index out of range') - #TODO: merge this code with OleFileIO.getsect() ? - #TODO: check if this works with 4K sectors: + if sect < 0 or sect >= len(fat): + debug("sect=%d (%X) / len(fat)=%d" % (sect, sect, len(fat))) + debug("i=%d / nb_sectors=%d" % (i, nb_sectors)) + ## tmp_data = b"".join(data) + ## f = open('test_debug.bin', 'wb') + ## f.write(tmp_data) + ## f.close() + ## debug('data read so far: %d bytes' % len(tmp_data)) + raise IOError("incorrect OLE FAT, sector index out of range") + # TODO: merge this code with OleFileIO.getsect() ? + # TODO: check if this works with 4K sectors: try: fp.seek(offset + sectorsize * sect) except: - debug('sect=%d, seek=%d, filesize=%d' % - (sect, offset+sectorsize*sect, filesize)) - raise IOError('OLE sector index out of range') + debug( + "sect=%d, seek=%d, filesize=%d" + % (sect, offset + sectorsize * sect, filesize) + ) + raise IOError("OLE sector index out of range") sector_data = fp.read(sectorsize) # [PL] check if there was enough data: # Note: if sector is the last of the file, sometimes it is not a # complete sector (of 512 or 4K), so we may read less than # sectorsize. - if len(sector_data)!=sectorsize and sect!=(len(fat)-1): - debug('sect=%d / len(fat)=%d, seek=%d / filesize=%d, len read=%d' % - (sect, len(fat), offset+sectorsize*sect, filesize, len(sector_data))) - debug('seek+len(read)=%d' % (offset+sectorsize*sect+len(sector_data))) - raise IOError('incomplete OLE sector') + if len(sector_data) != sectorsize and sect != (len(fat) - 1): + debug( + "sect=%d / len(fat)=%d, seek=%d / filesize=%d, len read=%d" + % ( + sect, + len(fat), + offset + sectorsize * sect, + filesize, + len(sector_data), + ) + ) + debug("seek+len(read)=%d" % (offset + sectorsize * sect + len(sector_data))) + raise IOError("incomplete OLE sector") data.append(sector_data) # jump to next sector in the FAT: try: sect = fat[sect] & 0xFFFFFFFF # JYTHON-WORKAROUND except IndexError: # [PL] if pointer is out of the FAT an exception is raised - raise IOError('incorrect OLE FAT, sector index out of range') - #[PL] Last sector should be a "end of chain" marker: + raise IOError("incorrect OLE FAT, sector index out of range") + # [PL] Last sector should be a "end of chain" marker: if sect != ENDOFCHAIN: - raise IOError('incorrect last sector index in OLE stream') + raise IOError("incorrect last sector index in OLE stream") data = b"".join(data) # Data is truncated to the actual stream size: if len(data) >= size: @@ -872,21 +969,23 @@ def __init__(self, fp, sect, size, offset, sectorsize, fat, filesize): self.size = len(data) else: # read data is less than expected: - debug('len(data)=%d, size=%d' % (len(data), size)) - raise IOError('OLE stream size is less than declared') + debug("len(data)=%d, size=%d" % (len(data), size)) + raise IOError("OLE stream size is less than declared") # when all data is read in memory, BytesIO constructor is called io.BytesIO.__init__(self, data) # Then the _OleStream object can be used as a read-only file object. -#--- _OleDirectoryEntry ------------------------------------------------------- +# --- _OleDirectoryEntry ------------------------------------------------------- + class _OleDirectoryEntry: """ OLE2 Directory Entry """ - #[PL] parsing code moved from OleFileIO.loaddirectory + + # [PL] parsing code moved from OleFileIO.loaddirectory # struct to parse directory entries: # <: little-endian byte order, standard sizes @@ -906,12 +1005,11 @@ class _OleDirectoryEntry: # of stream containing ministreams if root entry, 0 otherwise # I: uint32, total stream size in bytes if stream (low 32 bits), 0 otherwise # I: uint32, total stream size in bytes if stream (high 32 bits), 0 otherwise - STRUCT_DIRENTRY = '<64sHBBIII16sIQQIII' + STRUCT_DIRENTRY = "<64sHBBIII16sIQQIII" # size of a directory entry: 128 bytes DIRENTRY_SIZE = 128 assert struct.calcsize(STRUCT_DIRENTRY) == DIRENTRY_SIZE - def __init__(self, entry, sid, olefile): """ Constructor for an _OleDirectoryEntry object. @@ -949,63 +1047,71 @@ def __init__(self, entry, sid, olefile): self.modifyTime, self.isectStart, sizeLow, - sizeHigh + sizeHigh, ) = struct.unpack(_OleDirectoryEntry.STRUCT_DIRENTRY, entry) - if self.entry_type not in [STGTY_ROOT, STGTY_STORAGE, STGTY_STREAM, STGTY_EMPTY]: - olefile._raise_defect(DEFECT_INCORRECT, 'unhandled OLE storage type') + if self.entry_type not in [ + STGTY_ROOT, + STGTY_STORAGE, + STGTY_STREAM, + STGTY_EMPTY, + ]: + olefile._raise_defect(DEFECT_INCORRECT, "unhandled OLE storage type") # only first directory entry can (and should) be root: if self.entry_type == STGTY_ROOT and sid != 0: - olefile._raise_defect(DEFECT_INCORRECT, 'duplicate OLE root entry') + olefile._raise_defect(DEFECT_INCORRECT, "duplicate OLE root entry") if sid == 0 and self.entry_type != STGTY_ROOT: - olefile._raise_defect(DEFECT_INCORRECT, 'incorrect OLE root entry') - #debug (struct.unpack(fmt_entry, entry[:len_entry])) + olefile._raise_defect(DEFECT_INCORRECT, "incorrect OLE root entry") + # debug (struct.unpack(fmt_entry, entry[:len_entry])) # name should be at most 31 unicode characters + null character, # so 64 bytes in total (31*2 + 2): - if namelength>64: - olefile._raise_defect(DEFECT_INCORRECT, 'incorrect DirEntry name length') + if namelength > 64: + olefile._raise_defect(DEFECT_INCORRECT, "incorrect DirEntry name length") # if exception not raised, namelength is set to the maximum value: namelength = 64 # only characters without ending null char are kept: - name = name[:(namelength-2)] + name = name[: (namelength - 2)] # name is converted from unicode to Latin-1: self.name = _unicode(name) - debug('DirEntry SID=%d: %s' % (self.sid, repr(self.name))) - debug(' - type: %d' % self.entry_type) - debug(' - sect: %d' % self.isectStart) - debug(' - SID left: %d, right: %d, child: %d' % (self.sid_left, - self.sid_right, self.sid_child)) + debug("DirEntry SID=%d: %s" % (self.sid, repr(self.name))) + debug(" - type: %d" % self.entry_type) + debug(" - sect: %d" % self.isectStart) + debug( + " - SID left: %d, right: %d, child: %d" + % (self.sid_left, self.sid_right, self.sid_child) + ) # sizeHigh is only used for 4K sectors, it should be zero for 512 bytes # sectors, BUT apparently some implementations set it as 0xFFFFFFFF, 1 # or some other value so it cannot be raised as a defect in general: if olefile.sectorsize == 512: if sizeHigh != 0 and sizeHigh != 0xFFFFFFFF: - debug('sectorsize=%d, sizeLow=%d, sizeHigh=%d (%X)' % - (olefile.sectorsize, sizeLow, sizeHigh, sizeHigh)) - olefile._raise_defect(DEFECT_UNSURE, 'incorrect OLE stream size') + debug( + "sectorsize=%d, sizeLow=%d, sizeHigh=%d (%X)" + % (olefile.sectorsize, sizeLow, sizeHigh, sizeHigh) + ) + olefile._raise_defect(DEFECT_UNSURE, "incorrect OLE stream size") self.size = sizeLow else: - self.size = sizeLow + (long(sizeHigh)<<32) - debug(' - size: %d (sizeLow=%d, sizeHigh=%d)' % (self.size, sizeLow, sizeHigh)) + self.size = sizeLow + (long(sizeHigh) << 32) + debug(" - size: %d (sizeLow=%d, sizeHigh=%d)" % (self.size, sizeLow, sizeHigh)) self.clsid = _clsid(clsid) # a storage should have a null size, BUT some implementations such as # Word 8 for Mac seem to allow non-null values => Potential defect: if self.entry_type == STGTY_STORAGE and self.size != 0: - olefile._raise_defect(DEFECT_POTENTIAL, 'OLE storage with size>0') + olefile._raise_defect(DEFECT_POTENTIAL, "OLE storage with size>0") # check if stream is not already referenced elsewhere: - if self.entry_type in (STGTY_ROOT, STGTY_STREAM) and self.size>0: - if self.size < olefile.minisectorcutoff \ - and self.entry_type==STGTY_STREAM: # only streams can be in MiniFAT + if self.entry_type in (STGTY_ROOT, STGTY_STREAM) and self.size > 0: + if ( + self.size < olefile.minisectorcutoff and self.entry_type == STGTY_STREAM + ): # only streams can be in MiniFAT # ministream object minifat = True else: minifat = False olefile._check_duplicate_stream(self.isectStart, minifat) - - def build_storage_tree(self): """ Read and build the red-black tree attached to this _OleDirectoryEntry @@ -1013,8 +1119,10 @@ def build_storage_tree(self): Note that this method builds a tree of all subentries, so it should only be called for the root object once. """ - debug('build_storage_tree: SID=%d - %s - sid_child=%d' - % (self.sid, repr(self.name), self.sid_child)) + debug( + "build_storage_tree: SID=%d - %s - sid_child=%d" + % (self.sid, repr(self.name), self.sid_child) + ) if self.sid_child != NOSTREAM: # if child SID is not NOSTREAM, then this entry is a storage. # Let's walk through the tree of children to fill the kids list: @@ -1029,7 +1137,6 @@ def build_storage_tree(self): # (see rich comparison methods in this class) self.kids.sort() - def append_kids(self, child_sid): """ Walk through red-black tree of children of this directory entry to add @@ -1038,18 +1145,26 @@ def append_kids(self, child_sid): :param child_sid : index of child directory entry to use, or None when called first time for the root. (only used during recursion) """ - #[PL] this method was added to use simple recursion instead of a complex + # [PL] this method was added to use simple recursion instead of a complex # algorithm. # if this is not a storage or a leaf of the tree, nothing to do: if child_sid == NOSTREAM: return # check if child SID is in the proper range: - if child_sid<0 or child_sid>=len(self.olefile.direntries): - self.olefile._raise_defect(DEFECT_FATAL, 'OLE DirEntry index out of range') + if child_sid < 0 or child_sid >= len(self.olefile.direntries): + self.olefile._raise_defect(DEFECT_FATAL, "OLE DirEntry index out of range") # get child direntry: - child = self.olefile._load_direntry(child_sid) #direntries[child_sid] - debug('append_kids: child_sid=%d - %s - sid_left=%d, sid_right=%d, sid_child=%d' - % (child.sid, repr(child.name), child.sid_left, child.sid_right, child.sid_child)) + child = self.olefile._load_direntry(child_sid) # direntries[child_sid] + debug( + "append_kids: child_sid=%d - %s - sid_left=%d, sid_right=%d, sid_child=%d" + % ( + child.sid, + repr(child.name), + child.sid_left, + child.sid_right, + child.sid_child, + ) + ) # the directory entries are organized as a red-black tree. # (cf. Wikipedia for details) # First walk through left side of the tree: @@ -1057,23 +1172,20 @@ def append_kids(self, child_sid): # Check if its name is not already used (case-insensitive): name_lower = child.name.lower() if name_lower in self.kids_dict: - self.olefile._raise_defect(DEFECT_INCORRECT, - "Duplicate filename in OLE storage") + self.olefile._raise_defect(DEFECT_INCORRECT, "Duplicate filename in OLE storage") # Then the child_sid _OleDirectoryEntry object is appended to the # kids list and dictionary: self.kids.append(child) self.kids_dict[name_lower] = child # Check if kid was not already referenced in a storage: if child.used: - self.olefile._raise_defect(DEFECT_INCORRECT, - 'OLE Entry referenced more than once') + self.olefile._raise_defect(DEFECT_INCORRECT, "OLE Entry referenced more than once") child.used = True # Finally walk through right side of the tree: self.append_kids(child.sid_right) # Afterwards build kid's own tree if it's also a storage: child.build_storage_tree() - def __eq__(self, other): "Compare entries by name" return self.name == other.name @@ -1090,25 +1202,29 @@ def __le__(self, other): # Reflected __lt__() and __le__() will be used for __gt__() and __ge__() - #TODO: replace by the same function as MS implementation ? + # TODO: replace by the same function as MS implementation ? # (order by name length first, then case-insensitive order) - - def dump(self, tab = 0): + def dump(self, tab=0): "Dump this entry, and all its subentries (for debug purposes only)" - TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)", - "(property)", "(root)"] - print(" "*tab + repr(self.name), TYPES[self.entry_type], end=' ') + TYPES = [ + "(invalid)", + "(storage)", + "(stream)", + "(lockbytes)", + "(property)", + "(root)", + ] + print(" " * tab + repr(self.name), TYPES[self.entry_type], end=" ") if self.entry_type in (STGTY_STREAM, STGTY_ROOT): - print(self.size, "bytes", end=' ') + print(self.size, "bytes", end=" ") print() if self.entry_type in (STGTY_STORAGE, STGTY_ROOT) and self.clsid: - print(" "*tab + "{%s}" % self.clsid) + print(" " * tab + "{%s}" % self.clsid) for kid in self.kids: kid.dump(tab + 2) - def getmtime(self): """ Return modification time of a directory entry. @@ -1122,7 +1238,6 @@ def getmtime(self): return None return filetime2datetime(self.modifyTime) - def getctime(self): """ Return creation time of a directory entry. @@ -1137,7 +1252,8 @@ def getctime(self): return filetime2datetime(self.createTime) -#--- OleFileIO ---------------------------------------------------------------- +# --- OleFileIO ---------------------------------------------------------------- + class OleFileIO: """ @@ -1168,8 +1284,9 @@ class OleFileIO: TIFF files). """ - def __init__(self, filename=None, raise_defects=DEFECT_FATAL, - write_mode=False, debug=False): + def __init__( + self, filename=None, raise_defects=DEFECT_FATAL, write_mode=False, debug=False + ): """ Constructor for the OleFileIO class. @@ -1203,7 +1320,6 @@ def __init__(self, filename=None, raise_defects=DEFECT_FATAL, if filename: self.open(filename, write_mode=write_mode) - def _raise_defect(self, defect_level, message, exception_type=IOError): """ This method should be called for any defect found during file parsing. @@ -1227,7 +1343,6 @@ def _raise_defect(self, defect_level, message, exception_type=IOError): # just record the issue, no exception raised: self.parsing_issues.append((exception_type, message)) - def open(self, filename, write_mode=False): """ Open an OLE2 file in read-only or read/write mode. @@ -1246,10 +1361,10 @@ def open(self, filename, write_mode=False): of read-only by default. (ignored if filename is not a path) """ self.write_mode = write_mode - #[PL] check if filename is a string-like or file-like object: + # [PL] check if filename is a string-like or file-like object: # (it is better to check for a read() method) - if hasattr(filename, 'read'): - #TODO: also check seek and tell methods? + if hasattr(filename, "read"): + # TODO: also check seek and tell methods? # file-like object: use it directly self.fp = filename elif isinstance(filename, bytes) and len(filename) >= MINIMAL_OLEFILE_SIZE: @@ -1262,16 +1377,16 @@ def open(self, filename, write_mode=False): # open file in mode 'read with update, binary' # According to https://docs.python.org/2/library/functions.html#open # 'w' would truncate the file, 'a' may only append on some Unixes - mode = 'r+b' + mode = "r+b" else: # read-only mode by default - mode = 'rb' + mode = "rb" self.fp = open(filename, mode) # obtain the filesize by using seek and tell, which should work on most # file-like objects: - #TODO: do it above, using getsize with filename when possible? - #TODO: fix code to fail with clear exception when filesize cannot be obtained - filesize=0 + # TODO: do it above, using getsize with filename when possible? + # TODO: fix code to fail with clear exception when filesize cannot be obtained + filesize = 0 self.fp.seek(0, os.SEEK_END) try: filesize = self.fp.tell() @@ -1326,9 +1441,9 @@ def open(self, filename, write_mode=False): # [PL] header decoding: # '<' indicates little-endian byte ordering for Intel (cf. struct module help) - fmt_header = '<8s16sHHHHHHLLLLLLLLLL' + fmt_header = "<8s16sHHHHHHLLLLLLLLLL" header_size = struct.calcsize(fmt_header) - debug( "fmt_header size = %d, +FAT = %d" % (header_size, header_size + 109*4) ) + debug("fmt_header size = %d, +FAT = %d" % (header_size, header_size + 109 * 4)) header1 = header[:header_size] ( self.Sig, @@ -1338,7 +1453,8 @@ def open(self, filename, write_mode=False): self.ByteOrder, self.SectorShift, self.MiniSectorShift, - self.Reserved, self.Reserved1, + self.Reserved, + self.Reserved1, self.csectDir, self.csectFat, self.sectDirStart, @@ -1347,9 +1463,9 @@ def open(self, filename, write_mode=False): self.MiniFatStart, self.csectMiniFat, self.sectDifStart, - self.csectDif + self.csectDif, ) = struct.unpack(fmt_header, header1) - debug( struct.unpack(fmt_header, header1)) + debug(struct.unpack(fmt_header, header1)) if self.Sig != MAGIC: # OLE signature should always be present @@ -1357,47 +1473,52 @@ def open(self, filename, write_mode=False): if self.clsid != bytearray(16): # according to AAF specs, CLSID should always be zero self._raise_defect(DEFECT_INCORRECT, "incorrect CLSID in OLE header") - debug( "MinorVersion = %d" % self.MinorVersion ) - debug( "DllVersion = %d" % self.DllVersion ) + debug("MinorVersion = %d" % self.MinorVersion) + debug("DllVersion = %d" % self.DllVersion) if self.DllVersion not in [3, 4]: # version 3: usual format, 512 bytes per sector # version 4: large format, 4K per sector self._raise_defect(DEFECT_INCORRECT, "incorrect DllVersion in OLE header") - debug( "ByteOrder = %X" % self.ByteOrder ) + debug("ByteOrder = %X" % self.ByteOrder) if self.ByteOrder != 0xFFFE: # For now only common little-endian documents are handled correctly self._raise_defect(DEFECT_FATAL, "incorrect ByteOrder in OLE header") # TODO: add big-endian support for documents created on Mac ? # But according to [MS-CFB] ? v20140502, ByteOrder MUST be 0xFFFE. self.SectorSize = 2**self.SectorShift - debug( "SectorSize = %d" % self.SectorSize ) + debug("SectorSize = %d" % self.SectorSize) if self.SectorSize not in [512, 4096]: self._raise_defect(DEFECT_INCORRECT, "incorrect SectorSize in OLE header") - if (self.DllVersion==3 and self.SectorSize!=512) \ - or (self.DllVersion==4 and self.SectorSize!=4096): - self._raise_defect(DEFECT_INCORRECT, "SectorSize does not match DllVersion in OLE header") + if (self.DllVersion == 3 and self.SectorSize != 512) or ( + self.DllVersion == 4 and self.SectorSize != 4096 + ): + self._raise_defect( + DEFECT_INCORRECT, "SectorSize does not match DllVersion in OLE header" + ) self.MiniSectorSize = 2**self.MiniSectorShift - debug( "MiniSectorSize = %d" % self.MiniSectorSize ) + debug("MiniSectorSize = %d" % self.MiniSectorSize) if self.MiniSectorSize not in [64]: self._raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorSize in OLE header") if self.Reserved != 0 or self.Reserved1 != 0: - self._raise_defect(DEFECT_INCORRECT, "incorrect OLE header (non-null reserved bytes)") - debug( "csectDir = %d" % self.csectDir ) + self._raise_defect( + DEFECT_INCORRECT, "incorrect OLE header (non-null reserved bytes)" + ) + debug("csectDir = %d" % self.csectDir) # Number of directory sectors (only allowed if DllVersion != 3) - if self.SectorSize==512 and self.csectDir!=0: + if self.SectorSize == 512 and self.csectDir != 0: self._raise_defect(DEFECT_INCORRECT, "incorrect csectDir in OLE header") - debug( "csectFat = %d" % self.csectFat ) + debug("csectFat = %d" % self.csectFat) # csectFat = number of FAT sectors in the file - debug( "sectDirStart = %X" % self.sectDirStart ) + debug("sectDirStart = %X" % self.sectDirStart) # sectDirStart = 1st sector containing the directory - debug( "signature = %d" % self.signature ) + debug("signature = %d" % self.signature) # Signature should be zero, BUT some implementations do not follow this # rule => only a potential defect: # (according to MS-CFB, may be != 0 for applications supporting file # transactions) if self.signature != 0: self._raise_defect(DEFECT_POTENTIAL, "incorrect OLE header (signature>0)") - debug( "MiniSectorCutoff = %d" % self.MiniSectorCutoff ) + debug("MiniSectorCutoff = %d" % self.MiniSectorCutoff) # MS-CFB: This integer field MUST be set to 0x00001000. This field # specifies the maximum size of a user-defined data stream allocated # from the mini FAT and mini stream, and that cutoff is 4096 bytes. @@ -1405,25 +1526,25 @@ def open(self, filename, write_mode=False): # must be allocated as normal sectors from the FAT. if self.MiniSectorCutoff != 0x1000: self._raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorCutoff in OLE header") - debug( "MiniFatStart = %X" % self.MiniFatStart ) - debug( "csectMiniFat = %d" % self.csectMiniFat ) - debug( "sectDifStart = %X" % self.sectDifStart ) - debug( "csectDif = %d" % self.csectDif ) + debug("MiniFatStart = %X" % self.MiniFatStart) + debug("csectMiniFat = %d" % self.csectMiniFat) + debug("sectDifStart = %X" % self.sectDifStart) + debug("csectDif = %d" % self.csectDif) # calculate the number of sectors in the file # (-1 because header doesn't count) - self.nb_sect = ( (filesize + self.SectorSize-1) // self.SectorSize) - 1 - debug( "Number of sectors in the file: %d" % self.nb_sect ) - #TODO: change this test, because an OLE file MAY contain other data + self.nb_sect = ((filesize + self.SectorSize - 1) // self.SectorSize) - 1 + debug("Number of sectors in the file: %d" % self.nb_sect) + # TODO: change this test, because an OLE file MAY contain other data # after the last sector. # file clsid self.clsid = _clsid(header[8:24]) - #TODO: remove redundant attributes, and fix the code which uses them? - self.sectorsize = self.SectorSize #1 << i16(header, 30) - self.minisectorsize = self.MiniSectorSize #1 << i16(header, 32) - self.minisectorcutoff = self.MiniSectorCutoff # i32(header, 56) + # TODO: remove redundant attributes, and fix the code which uses them? + self.sectorsize = self.SectorSize # 1 << i16(header, 30) + self.minisectorsize = self.MiniSectorSize # 1 << i16(header, 32) + self.minisectorcutoff = self.MiniSectorCutoff # i32(header, 56) # check known streams for duplicate references (these are always in FAT, # never in MiniFAT): @@ -1439,10 +1560,9 @@ def open(self, filename, write_mode=False): self.loadfat(header) # Load direcory. This sets both the direntries list (ordered by sid) # and the root (ordered by hierarchy) members. - self.loaddirectory(self.sectDirStart)#i32(header, 48)) + self.loaddirectory(self.sectDirStart) # i32(header, 48)) self.ministream = None - self.minifatsect = self.MiniFatStart #i32(header, 60) - + self.minifatsect = self.MiniFatStart # i32(header, 60) def close(self): """ @@ -1450,7 +1570,6 @@ def close(self): """ self.fp.close() - def _check_duplicate_stream(self, first_sect, minifat=False): """ Checks if a stream has not been already referenced elsewhere. @@ -1461,79 +1580,77 @@ def _check_duplicate_stream(self, first_sect, minifat=False): :param minifat: bool, if True, stream is located in the MiniFAT, else in the FAT """ if minifat: - debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect) + debug("_check_duplicate_stream: sect=%d in MiniFAT" % first_sect) used_streams = self._used_streams_minifat else: - debug('_check_duplicate_stream: sect=%d in FAT' % first_sect) + debug("_check_duplicate_stream: sect=%d in FAT" % first_sect) # some values can be safely ignored (not a real stream): - if first_sect in (DIFSECT,FATSECT,ENDOFCHAIN,FREESECT): + if first_sect in (DIFSECT, FATSECT, ENDOFCHAIN, FREESECT): return used_streams = self._used_streams_fat - #TODO: would it be more efficient using a dict or hash values, instead + # TODO: would it be more efficient using a dict or hash values, instead # of a list of long ? if first_sect in used_streams: - self._raise_defect(DEFECT_INCORRECT, 'Stream referenced twice') + self._raise_defect(DEFECT_INCORRECT, "Stream referenced twice") else: used_streams.append(first_sect) - def dumpfat(self, fat, firstindex=0): "Displays a part of FAT in human-readable form for debugging purpose" # [PL] added only for debug if not DEBUG_MODE: return # dictionary to convert special FAT values in human-readable strings - VPL = 8 # values per line (8+1 * 8+1 = 81) + VPL = 8 # values per line (8+1 * 8+1 = 81) fatnames = { - FREESECT: "..free..", + FREESECT: "..free..", ENDOFCHAIN: "[ END. ]", - FATSECT: "FATSECT ", - DIFSECT: "DIFSECT " - } + FATSECT: "FATSECT ", + DIFSECT: "DIFSECT ", + } nbsect = len(fat) - nlines = (nbsect+VPL-1)//VPL + nlines = (nbsect + VPL - 1) // VPL print("index", end=" ") for i in range(VPL): print("%8X" % i, end=" ") print() for l in range(nlines): - index = l*VPL - print("%8X:" % (firstindex+index), end=" ") - for i in range(index, index+VPL): - if i>=nbsect: + index = l * VPL + print("%8X:" % (firstindex + index), end=" ") + for i in range(index, index + VPL): + if i >= nbsect: break sect = fat[i] aux = sect & 0xFFFFFFFF # JYTHON-WORKAROUND if aux in fatnames: name = fatnames[aux] else: - if sect == i+1: + if sect == i + 1: name = " --->" else: name = "%8X" % sect print(name, end=" ") print() - def dumpsect(self, sector, firstindex=0): "Displays a sector in a human-readable form, for debugging purpose." if not DEBUG_MODE: return - VPL=8 # number of values per line (8+1 * 8+1 = 81) + VPL = 8 # number of values per line (8+1 * 8+1 = 81) tab = array.array(UINT32, sector) - if sys.byteorder == 'big': + if sys.byteorder == "big": tab.byteswap() nbsect = len(tab) - nlines = (nbsect+VPL-1)//VPL + nlines = (nbsect + VPL - 1) // VPL print("index", end=" ") for i in range(VPL): print("%8X" % i, end=" ") print() for l in range(nlines): - index = l*VPL - print("%8X:" % (firstindex+index), end=" ") - for i in range(index, index+VPL): - if i>=nbsect: + index = l * VPL + print("%8X:" % (firstindex + index), end=" ") + for i in range(index, index + VPL): + if i >= nbsect: break sect = tab[i] name = "%8X" % sect @@ -1547,11 +1664,10 @@ def sect2array(self, sect): """ a = array.array(UINT32, sect) # if CPU is big endian, swap bytes: - if sys.byteorder == 'big': + if sys.byteorder == "big": a.byteswap() return a - def loadfat_sect(self, sect): """ Adds the indexes of the given sector to the FAT @@ -1583,7 +1699,6 @@ def loadfat_sect(self, sect): self.fat = self.fat + nextfat return isect - def loadfat(self, header): """ Load the FAT table. @@ -1594,71 +1709,70 @@ def loadfat(self, header): # Additional sectors are described by DIF blocks sect = header[76:512] - debug( "len(sect)=%d, so %d integers" % (len(sect), len(sect)//4) ) - #fat = [] + debug("len(sect)=%d, so %d integers" % (len(sect), len(sect) // 4)) + # fat = [] # [PL] FAT is an array of 32 bits unsigned ints, it's more effective # to use an array than a list in Python. # It's initialized as empty first: self.fat = array.array(UINT32) self.loadfat_sect(sect) - #self.dumpfat(self.fat) -## for i in range(0, len(sect), 4): -## ix = i32(sect, i) -## #[PL] if ix == -2 or ix == -1: # ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: -## if ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: -## break -## s = self.getsect(ix) -## #fat = fat + [i32(s, i) for i in range(0, len(s), 4)] -## fat = fat + array.array(UINT32, s) + # self.dumpfat(self.fat) + ## for i in range(0, len(sect), 4): + ## ix = i32(sect, i) + ## #[PL] if ix == -2 or ix == -1: # ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: + ## if ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: + ## break + ## s = self.getsect(ix) + ## #fat = fat + [i32(s, i) for i in range(0, len(s), 4)] + ## fat = fat + array.array(UINT32, s) if self.csectDif != 0: # [PL] There's a DIFAT because file is larger than 6.8MB # some checks just in case: if self.csectFat <= 109: # there must be at least 109 blocks in header and the rest in # DIFAT, so number of sectors must be >109. - self._raise_defect(DEFECT_INCORRECT, 'incorrect DIFAT, not enough sectors') + self._raise_defect(DEFECT_INCORRECT, "incorrect DIFAT, not enough sectors") if self.sectDifStart >= self.nb_sect: # initial DIFAT block index must be valid - self._raise_defect(DEFECT_FATAL, 'incorrect DIFAT, first index out of range') - debug( "DIFAT analysis..." ) + self._raise_defect(DEFECT_FATAL, "incorrect DIFAT, first index out of range") + debug("DIFAT analysis...") # We compute the necessary number of DIFAT sectors : # Number of pointers per DIFAT sector = (sectorsize/4)-1 # (-1 because the last pointer is the next DIFAT sector number) - nb_difat_sectors = (self.sectorsize//4)-1 + nb_difat_sectors = (self.sectorsize // 4) - 1 # (if 512 bytes: each DIFAT sector = 127 pointers + 1 towards next DIFAT sector) - nb_difat = (self.csectFat-109 + nb_difat_sectors-1)//nb_difat_sectors - debug( "nb_difat = %d" % nb_difat ) + nb_difat = (self.csectFat - 109 + nb_difat_sectors - 1) // nb_difat_sectors + debug("nb_difat = %d" % nb_difat) if self.csectDif != nb_difat: - raise IOError('incorrect DIFAT') + raise IOError("incorrect DIFAT") isect_difat = self.sectDifStart for i in iterrange(nb_difat): - debug( "DIFAT block %d, sector %X" % (i, isect_difat) ) - #TODO: check if corresponding FAT SID = DIFSECT + debug("DIFAT block %d, sector %X" % (i, isect_difat)) + # TODO: check if corresponding FAT SID = DIFSECT sector_difat = self.getsect(isect_difat) difat = self.sect2array(sector_difat) self.dumpsect(sector_difat) self.loadfat_sect(difat[:nb_difat_sectors]) # last DIFAT pointer is next DIFAT sector: isect_difat = difat[nb_difat_sectors] - debug( "next DIFAT sector: %X" % isect_difat ) + debug("next DIFAT sector: %X" % isect_difat) # checks: if isect_difat not in [ENDOFCHAIN, FREESECT]: # last DIFAT pointer value must be ENDOFCHAIN or FREESECT - raise IOError('incorrect end of DIFAT') -## if len(self.fat) != self.csectFat: -## # FAT should contain csectFat blocks -## print("FAT length: %d instead of %d" % (len(self.fat), self.csectFat)) -## raise IOError('incorrect DIFAT') + raise IOError("incorrect end of DIFAT") + ## if len(self.fat) != self.csectFat: + ## # FAT should contain csectFat blocks + ## print("FAT length: %d instead of %d" % (len(self.fat), self.csectFat)) + ## raise IOError('incorrect DIFAT') # since FAT is read from fixed-size sectors, it may contain more values # than the actual number of sectors in the file. # Keep only the relevant sector indexes: if len(self.fat) > self.nb_sect: - debug('len(fat)=%d, shrunk to nb_sect=%d' % (len(self.fat), self.nb_sect)) - self.fat = self.fat[:self.nb_sect] - debug('\nFAT:') + debug("len(fat)=%d, shrunk to nb_sect=%d" % (len(self.fat), self.nb_sect)) + self.fat = self.fat[: self.nb_sect] + debug("\nFAT:") self.dumpfat(self.fat) - def loadminifat(self): """ Load the MiniFAT table. @@ -1674,23 +1788,31 @@ def loadminifat(self): # 2) Actually used size is calculated by dividing the MiniStream size # (given by root entry size) by the size of mini sectors, *4 for # 32 bits indexes: - nb_minisectors = (self.root.size + self.MiniSectorSize-1) // self.MiniSectorSize + nb_minisectors = (self.root.size + self.MiniSectorSize - 1) // self.MiniSectorSize used_size = nb_minisectors * 4 - debug('loadminifat(): minifatsect=%d, nb FAT sectors=%d, used_size=%d, stream_size=%d, nb MiniSectors=%d' % - (self.minifatsect, self.csectMiniFat, used_size, stream_size, nb_minisectors)) + debug( + "loadminifat(): minifatsect=%d, nb FAT sectors=%d, used_size=%d, stream_size=%d, nb MiniSectors=%d" + % ( + self.minifatsect, + self.csectMiniFat, + used_size, + stream_size, + nb_minisectors, + ) + ) if used_size > stream_size: # This is not really a problem, but may indicate a wrong implementation: - self._raise_defect(DEFECT_INCORRECT, 'OLE MiniStream is larger than MiniFAT') + self._raise_defect(DEFECT_INCORRECT, "OLE MiniStream is larger than MiniFAT") # In any case, first read stream_size: s = self._open(self.minifatsect, stream_size, force_FAT=True).read() - #[PL] Old code replaced by an array: - #self.minifat = [i32(s, i) for i in range(0, len(s), 4)] + # [PL] Old code replaced by an array: + # self.minifat = [i32(s, i) for i in range(0, len(s), 4)] self.minifat = self.sect2array(s) # Then shrink the array to used size, to avoid indexes out of MiniStream: - debug('MiniFAT shrunk from %d to %d sectors' % (len(self.minifat), nb_minisectors)) + debug("MiniFAT shrunk from %d to %d sectors" % (len(self.minifat), nb_minisectors)) self.minifat = self.minifat[:nb_minisectors] - debug('loadminifat(): len=%d' % len(self.minifat)) - debug('\nMiniFAT:') + debug("loadminifat(): len=%d" % len(self.minifat)) + debug("\nMiniFAT:") self.dumpfat(self.minifat) def getsect(self, sect): @@ -1708,24 +1830,27 @@ def getsect(self, sect): # [PL] the original code in PIL was wrong when sectors are 4KB instead of # 512 bytes: - #self.fp.seek(512 + self.sectorsize * sect) - #[PL]: added safety checks: - #print("getsect(%X)" % sect) + # self.fp.seek(512 + self.sectorsize * sect) + # [PL]: added safety checks: + # print("getsect(%X)" % sect) try: - self.fp.seek(self.sectorsize * (sect+1)) + self.fp.seek(self.sectorsize * (sect + 1)) except: - debug('getsect(): sect=%X, seek=%d, filesize=%d' % - (sect, self.sectorsize*(sect+1), self._filesize)) - self._raise_defect(DEFECT_FATAL, 'OLE sector index out of range') + debug( + "getsect(): sect=%X, seek=%d, filesize=%d" + % (sect, self.sectorsize * (sect + 1), self._filesize) + ) + self._raise_defect(DEFECT_FATAL, "OLE sector index out of range") sector = self.fp.read(self.sectorsize) if len(sector) != self.sectorsize: - debug('getsect(): sect=%X, read=%d, sectorsize=%d' % - (sect, len(sector), self.sectorsize)) - self._raise_defect(DEFECT_FATAL, 'incomplete OLE sector') + debug( + "getsect(): sect=%X, read=%d, sectorsize=%d" + % (sect, len(sector), self.sectorsize) + ) + self._raise_defect(DEFECT_FATAL, "incomplete OLE sector") return sector - - def write_sect(self, sect, data, padding=b'\x00'): + def write_sect(self, sect, data, padding=b"\x00"): """ Write given sector to file on disk. @@ -1735,15 +1860,17 @@ def write_sect(self, sect, data, padding=b'\x00'): """ if not isinstance(data, bytes): raise TypeError("write_sect: data must be a bytes string") - if not isinstance(padding, bytes) or len(padding)!=1: + if not isinstance(padding, bytes) or len(padding) != 1: raise TypeError("write_sect: padding must be a bytes string of 1 char") - #TODO: we could allow padding=None for no padding at all + # TODO: we could allow padding=None for no padding at all try: - self.fp.seek(self.sectorsize * (sect+1)) + self.fp.seek(self.sectorsize * (sect + 1)) except: - debug('write_sect(): sect=%X, seek=%d, filesize=%d' % - (sect, self.sectorsize*(sect+1), self._filesize)) - self._raise_defect(DEFECT_FATAL, 'OLE sector index out of range') + debug( + "write_sect(): sect=%X, seek=%d, filesize=%d" + % (sect, self.sectorsize * (sect + 1), self._filesize) + ) + self._raise_defect(DEFECT_FATAL, "OLE sector index out of range") if len(data) < self.sectorsize: # add padding data += padding * (self.sectorsize - len(data)) @@ -1751,7 +1878,6 @@ def write_sect(self, sect, data, padding=b'\x00'): raise ValueError("Data is larger than sector size") self.fp.write(data) - def loaddirectory(self, sect): """ Load the directory. @@ -1765,21 +1891,20 @@ def loaddirectory(self, sect): # (stream size is not known in advance) self.directory_fp = self._open(sect) - #[PL] to detect malformed documents and avoid DoS attacks, the maximum + # [PL] to detect malformed documents and avoid DoS attacks, the maximum # number of directory entries can be calculated: max_entries = self.directory_fp.size // 128 - debug('loaddirectory: size=%d, max_entries=%d' % - (self.directory_fp.size, max_entries)) + debug("loaddirectory: size=%d, max_entries=%d" % (self.directory_fp.size, max_entries)) # Create list of directory entries - #self.direntries = [] + # self.direntries = [] # We start with a list of "None" object self.direntries = [None] * max_entries -## for sid in iterrange(max_entries): -## entry = fp.read(128) -## if not entry: -## break -## self.direntries.append(_OleDirectoryEntry(entry, sid, self)) + ## for sid in iterrange(max_entries): + ## entry = fp.read(128) + ## if not entry: + ## break + ## self.direntries.append(_OleDirectoryEntry(entry, sid, self)) # load root entry: root_entry = self._load_direntry(0) # Root entry is the first entry: @@ -1787,8 +1912,7 @@ def loaddirectory(self, sect): # read and build all storage trees, starting from the root: self.root.build_storage_tree() - - def _load_direntry (self, sid): + def _load_direntry(self, sid): """ Load a directory entry from the directory. This method should only be called once for each storage/stream when @@ -1800,12 +1924,11 @@ def _load_direntry (self, sid): :exception IOError: if the entry has always been referenced. """ # check if SID is OK: - if sid<0 or sid>=len(self.direntries): + if sid < 0 or sid >= len(self.direntries): self._raise_defect(DEFECT_FATAL, "OLE directory index out of range") # check if entry was already referenced: if self.direntries[sid] is not None: - self._raise_defect(DEFECT_INCORRECT, - "double reference for OLE stream/storage") + self._raise_defect(DEFECT_INCORRECT, "double reference for OLE stream/storage") # if exception not raised, return the object return self.direntries[sid] self.directory_fp.seek(sid * 128) @@ -1813,15 +1936,13 @@ def _load_direntry (self, sid): self.direntries[sid] = _OleDirectoryEntry(entry, sid, self) return self.direntries[sid] - def dumpdirectory(self): """ Dump directory (for debugging only) """ self.root.dump() - - def _open(self, start, size = 0x7FFFFFFF, force_FAT=False): + def _open(self, start, size=0x7FFFFFFF, force_FAT=False): """ Open a stream, either in FAT or MiniFAT according to its size. (openstream helper) @@ -1831,8 +1952,9 @@ def _open(self, start, size = 0x7FFFFFFF, force_FAT=False): :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT according to size. If True, it will always be opened in FAT. """ - debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' % - (start, size, str(force_FAT))) + debug( + "OleFileIO.open(): sect=%d, size=%d, force_FAT=%s" % (start, size, str(force_FAT)) + ) # stream size is compared to the MiniSectorCutoff threshold: if size < self.minisectorcutoff and not force_FAT: # ministream object @@ -1842,20 +1964,33 @@ def _open(self, start, size = 0x7FFFFFFF, force_FAT=False): # The first sector index of the miniFAT stream is stored in the # root directory entry: size_ministream = self.root.size - debug('Opening MiniStream: sect=%d, size=%d' % - (self.root.isectStart, size_ministream)) - self.ministream = self._open(self.root.isectStart, - size_ministream, force_FAT=True) - return _OleStream(fp=self.ministream, sect=start, size=size, - offset=0, sectorsize=self.minisectorsize, - fat=self.minifat, filesize=self.ministream.size) + debug( + "Opening MiniStream: sect=%d, size=%d" + % (self.root.isectStart, size_ministream) + ) + self.ministream = self._open( + self.root.isectStart, size_ministream, force_FAT=True + ) + return _OleStream( + fp=self.ministream, + sect=start, + size=size, + offset=0, + sectorsize=self.minisectorsize, + fat=self.minifat, + filesize=self.ministream.size, + ) else: # standard stream - return _OleStream(fp=self.fp, sect=start, size=size, - offset=self.sectorsize, - sectorsize=self.sectorsize, fat=self.fat, - filesize=self._filesize) - + return _OleStream( + fp=self.fp, + sect=start, + size=size, + offset=self.sectorsize, + sectorsize=self.sectorsize, + fat=self.fat, + filesize=self._filesize, + ) def _list(self, files, prefix, node, streams=True, storages=False): """ @@ -1883,7 +2018,6 @@ def _list(self, files, prefix, node, streams=True, storages=False): # add it to the list files.append(prefix[1:] + [entry.name]) - def listdir(self, streams=True, storages=False): """ Return a list of streams and/or storages stored in this file @@ -1897,7 +2031,6 @@ def listdir(self, streams=True, storages=False): self._list(files, [], self.root, streams, storages) return files - def _find(self, filename): """ Returns directory entry of given filename. (openstream helper) @@ -1917,7 +2050,7 @@ def _find(self, filename): # if filename is a string instead of a list, split it on slashes to # convert to a list: if isinstance(filename, basestring): - filename = filename.split('/') + filename = filename.split("/") # walk across storage tree, following given path: node = self.root for name in filename: @@ -1929,7 +2062,6 @@ def _find(self, filename): node = kid return node.sid - def openstream(self, filename): """ Open a stream as a read-only file object (BytesIO). @@ -1951,7 +2083,6 @@ def openstream(self, filename): raise IOError("this file is not a stream") return self._open(entry.isectStart, entry.size) - def write_stream(self, stream_name, data): """ Write a stream to disk. For now, it is only possible to replace an @@ -1980,38 +2111,39 @@ def write_stream(self, stream_name, data): raise NotImplementedError("Writing a stream in MiniFAT is not implemented yet") sect = entry.isectStart # number of sectors to write - nb_sectors = (size + (self.sectorsize-1)) // self.sectorsize - debug('nb_sectors = %d' % nb_sectors) + nb_sectors = (size + (self.sectorsize - 1)) // self.sectorsize + debug("nb_sectors = %d" % nb_sectors) for i in range(nb_sectors): -## try: -## self.fp.seek(offset + self.sectorsize * sect) -## except: -## debug('sect=%d, seek=%d' % -## (sect, offset+self.sectorsize*sect)) -## raise IOError('OLE sector index out of range') + ## try: + ## self.fp.seek(offset + self.sectorsize * sect) + ## except: + ## debug('sect=%d, seek=%d' % + ## (sect, offset+self.sectorsize*sect)) + ## raise IOError('OLE sector index out of range') # extract one sector from data, the last one being smaller: - if i<(nb_sectors-1): - data_sector = data [i*self.sectorsize : (i+1)*self.sectorsize] - #TODO: comment this if it works - assert(len(data_sector)==self.sectorsize) + if i < (nb_sectors - 1): + data_sector = data[i * self.sectorsize : (i + 1) * self.sectorsize] + # TODO: comment this if it works + assert len(data_sector) == self.sectorsize else: - data_sector = data [i*self.sectorsize:] - #TODO: comment this if it works - debug('write_stream: size=%d sectorsize=%d data_sector=%d size%%sectorsize=%d' - % (size, self.sectorsize, len(data_sector), size % self.sectorsize)) - assert(len(data_sector) % self.sectorsize==size % self.sectorsize) + data_sector = data[i * self.sectorsize :] + # TODO: comment this if it works + debug( + "write_stream: size=%d sectorsize=%d data_sector=%d size%%sectorsize=%d" + % (size, self.sectorsize, len(data_sector), size % self.sectorsize) + ) + assert len(data_sector) % self.sectorsize == size % self.sectorsize self.write_sect(sect, data_sector) -## self.fp.write(data_sector) + ## self.fp.write(data_sector) # jump to next sector in the FAT: try: sect = self.fat[sect] except IndexError: # [PL] if pointer is out of the FAT an exception is raised - raise IOError('incorrect OLE FAT, sector index out of range') - #[PL] Last sector should be a "end of chain" marker: + raise IOError("incorrect OLE FAT, sector index out of range") + # [PL] Last sector should be a "end of chain" marker: if sect != ENDOFCHAIN: - raise IOError('incorrect last sector index in OLE stream') - + raise IOError("incorrect last sector index in OLE stream") def get_type(self, filename): """ @@ -2032,7 +2164,6 @@ def get_type(self, filename): except: return False - def getmtime(self, filename): """ Return modification time of a stream/storage. @@ -2048,7 +2179,6 @@ def getmtime(self, filename): entry = self.direntries[sid] return entry.getmtime() - def getctime(self, filename): """ Return creation time of a stream/storage. @@ -2064,7 +2194,6 @@ def getctime(self, filename): entry = self.direntries[sid] return entry.getctime() - def exists(self, filename): """ Test if given filename exists as a stream or a storage in the OLE @@ -2080,7 +2209,6 @@ def exists(self, filename): except: return False - def get_size(self, filename): """ Return size of a stream in the OLE container, in bytes. @@ -2093,11 +2221,10 @@ def get_size(self, filename): sid = self._find(filename) entry = self.direntries[sid] if entry.entry_type != STGTY_STREAM: - #TODO: Should it return zero instead of raising an exception ? - raise TypeError('object is not an OLE stream') + # TODO: Should it return zero instead of raising an exception ? + raise TypeError("object is not an OLE stream") return entry.size - def get_rootentry_name(self): """ Return root entry name. Should usually be 'Root Entry' or 'R' in most @@ -2105,7 +2232,6 @@ def get_rootentry_name(self): """ return self.root.name - def getproperties(self, filename, convert_time=False, no_conversion=None): """ Return properties described in substream. @@ -2123,7 +2249,7 @@ def getproperties(self, filename, convert_time=False, no_conversion=None): # stream path as a string to report exceptions: streampath = filename if not isinstance(streampath, str): - streampath = '/'.join(streampath) + streampath = "/".join(streampath) fp = self.openstream(filename) @@ -2151,14 +2277,16 @@ def getproperties(self, filename, convert_time=False, no_conversion=None): # catch exception while parsing property header, and only raise # a DEFECT_INCORRECT then return an empty dict, because this is not # a fatal error when parsing the whole file - msg = 'Error while parsing properties header in stream %s: %s' % ( - repr(streampath), exc) + msg = "Error while parsing properties header in stream %s: %s" % ( + repr(streampath), + exc, + ) self._raise_defect(DEFECT_INCORRECT, msg, type(exc)) return data # Parse PropertyIdentifierAndOffset's (uint32, uint32) ps.seek(8) - id_offsets = [unpack(' [file2 ...] @@ -2344,95 +2485,125 @@ def get_metadata(self): -c : check all streams (for debugging purposes) For more information, see http://www.decalage.info/olefile -""") +""" + ) sys.exit() check_streams = False for filename in sys.argv[1:]: -## try: - # OPTIONS: - if filename == '-d': - # option to switch debug mode on: - set_debug_mode(True) - continue - if filename == '-c': - # option to switch check streams mode on: - check_streams = True - continue - - ole = OleFileIO(filename)#, raise_defects=DEFECT_INCORRECT) - print("-" * 68) - print(filename) - print("-" * 68) - ole.dumpdirectory() + ## try: + # OPTIONS: + if filename == "-d": + # option to switch debug mode on: + set_debug_mode(True) + continue + if filename == "-c": + # option to switch check streams mode on: + check_streams = True + continue + + ole = OleFileIO(filename) # , raise_defects=DEFECT_INCORRECT) + print("-" * 68) + print(filename) + print("-" * 68) + ole.dumpdirectory() + for streamname in ole.listdir(): + if streamname[-1][0] == "\005": + print(streamname, ": properties") + props = ole.getproperties(streamname, convert_time=True) + props = sorted(props.items()) + for k, v in props: + # [PL]: avoid to display too large or binary values: + if isinstance(v, (basestring, bytes)): + if len(v) > 50: + v = v[:50] + if isinstance(v, bytes): + # quick and dirty binary check: + for c in ( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 11, + 12, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + ): + if c in bytearray(v): + v = "(binary data)" + break + print(" ", k, v) + + if check_streams: + # Read all streams to check if there are errors: + print("\nChecking streams...") for streamname in ole.listdir(): - if streamname[-1][0] == "\005": - print(streamname, ": properties") - props = ole.getproperties(streamname, convert_time=True) - props = sorted(props.items()) - for k, v in props: - #[PL]: avoid to display too large or binary values: - if isinstance(v, (basestring, bytes)): - if len(v) > 50: - v = v[:50] - if isinstance(v, bytes): - # quick and dirty binary check: - for c in (1,2,3,4,5,6,7,11,12,14,15,16,17,18,19,20, - 21,22,23,24,25,26,27,28,29,30,31): - if c in bytearray(v): - v = '(binary data)' - break - print(" ", k, v) - - if check_streams: - # Read all streams to check if there are errors: - print('\nChecking streams...') - for streamname in ole.listdir(): - # print name using repr() to convert binary chars to \xNN: - print('-', repr('/'.join(streamname)),'-', end=' ') - st_type = ole.get_type(streamname) - if st_type == STGTY_STREAM: - print('size %d' % ole.get_size(streamname)) - # just try to read stream in memory: - ole.openstream(streamname) - else: - print('NOT a stream : type=%d' % st_type) - print() - -## for streamname in ole.listdir(): -## # print name using repr() to convert binary chars to \xNN: -## print('-', repr('/'.join(streamname)),'-', end=' ') -## print(ole.getmtime(streamname)) -## print() - - print('Modification/Creation times of all directory entries:') - for entry in ole.direntries: - if entry is not None: - print('- %s: mtime=%s ctime=%s' % (entry.name, - entry.getmtime(), entry.getctime())) + # print name using repr() to convert binary chars to \xNN: + print("-", repr("/".join(streamname)), "-", end=" ") + st_type = ole.get_type(streamname) + if st_type == STGTY_STREAM: + print("size %d" % ole.get_size(streamname)) + # just try to read stream in memory: + ole.openstream(streamname) + else: + print("NOT a stream : type=%d" % st_type) print() - # parse and display metadata: - meta = ole.get_metadata() - meta.dump() - print() - #[PL] Test a few new methods: - root = ole.get_rootentry_name() - print('Root entry name: "%s"' % root) - if ole.exists('worddocument'): - print("This is a Word document.") - print("type of stream 'WordDocument':", ole.get_type('worddocument')) - print("size :", ole.get_size('worddocument')) - if ole.exists('macros/vba'): - print("This document may contain VBA macros.") - - # print parsing issues: - print('\nNon-fatal issues raised during parsing:') - if ole.parsing_issues: - for exctype, msg in ole.parsing_issues: - print('- %s: %s' % (exctype.__name__, msg)) - else: - print('None') + ## for streamname in ole.listdir(): + ## # print name using repr() to convert binary chars to \xNN: + ## print('-', repr('/'.join(streamname)),'-', end=' ') + ## print(ole.getmtime(streamname)) + ## print() + + print("Modification/Creation times of all directory entries:") + for entry in ole.direntries: + if entry is not None: + print( + "- %s: mtime=%s ctime=%s" + % (entry.name, entry.getmtime(), entry.getctime()) + ) + print() + + # parse and display metadata: + meta = ole.get_metadata() + meta.dump() + print() + # [PL] Test a few new methods: + root = ole.get_rootentry_name() + print('Root entry name: "%s"' % root) + if ole.exists("worddocument"): + print("This is a Word document.") + print("type of stream 'WordDocument':", ole.get_type("worddocument")) + print("size :", ole.get_size("worddocument")) + if ole.exists("macros/vba"): + print("This document may contain VBA macros.") + + # print parsing issues: + print("\nNon-fatal issues raised during parsing:") + if ole.parsing_issues: + for exctype, msg in ole.parsing_issues: + print("- %s: %s" % (exctype.__name__, msg)) + else: + print("None") ## except IOError as v: ## print("***", "cannot read", file, "-", v) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..a81647a5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 95 +target-version = ['py37'] +# 'extend-exclude' excludes files or directories in addition to the defaults +extend-exclude = ''' +# A regex preceded with ^/ will apply only to files and directories +# in the root of the project. +( + PyExpLabSys/common/massspec/test.py + | PyExpLabSys/apps/picture_logbook.py + | PyExpLabSys/apps/rampreader.py +) +''' diff --git a/requirements-dev.txt b/requirements-dev.txt index 8511f0b3..c1b9f561 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,5 @@ invoke pylint pytest +black +rich diff --git a/setup.py b/setup.py index 8ec3f50d..8aea89b5 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,6 @@ """Setup file for PyExpLabSys""" -# 19:20 - 20:10 -# 20:40 - 21:20 -# 21:15 - 21:55 - import codecs import os import re diff --git a/tasks.py b/tasks.py index d88a203e..fb78dffa 100644 --- a/tasks.py +++ b/tasks.py @@ -4,6 +4,10 @@ from pathlib import Path from shutil import rmtree, which from invoke import task +try: + from rich import print as rprint +except ImportError: + rprint = print THIS_DIR = Path(__file__).parent @@ -38,7 +42,6 @@ def open_docs(context): @task( - aliases=["c"], help={ "dryrun": ( "Only display the files and folders that would be deleted by the " @@ -77,11 +80,53 @@ def clean(context, dryrun=False): def lint(context): """Run linting tool on all of PyExpLabSys""" with context.cd(THIS_DIR): - context.run("pylint PyExpLabSys") + result = context.run("pylint PyExpLabSys") + if result.return_code == 0: + rprint("[bold green]Files linted. No errors.") + return result.return_code @task(aliases=["pytest", "t"]) def test(context): """Run non-equipment dependent tests""" with context.cd(THIS_DIR): - context.run("pytest --color yes tests/unittests/ tests/functional_test/") + result = context.run("pytest --color yes tests/unittests/ tests/functional_test/") + if result.return_code == 0: + rprint("[bold green]All tests passed") + return result.return_code + + +@task(aliases=["deps", "d"]) +def dependencies(context): + """Install normal and development dependencies""" + context.run("python -m pip install --upgrade pip") + context.run("pip install --upgrade -r requirements.txt") + context.run("pip install --upgrade -r requirements-dev.txt") + + +@task(aliases=["black", "f", "b"]) +def format(context): + """Run all source code through the code formatter""" + with context.cd(THIS_DIR): + context.run("black PyExpLabSys") + +@task(aliases=["check_black", "cf", "cb"]) +def check_format(context): + """Check that the code has already been run through the code formatter""" + with context.cd(THIS_DIR): + result = context.run("black --check PyExpLabSys") + if result.return_code == 0: + rprint("[bold green]Code format checked. No issues.") + return result.return_code + +@task(aliases=["check", "c"]) +def checks(context): + """Check the code with flake8 and mypy""" + combined_return_code = check_format(context) + combined_return_code += lint(context) + combined_return_code += test(context) + if combined_return_code == 0: + print() + print(r"+----------+") + print(r"| All good |") + print(r"+----------+") \ No newline at end of file