class MyProgram(Thread): # pylint: disable=too-many-instance-attributes """My fancy program""" def __init__(self, args): super(MyProgram, self).__init__() # Form channel_id e.g: A1 self.channel_id = args.power_supply + args.output ### Required by the stepped program runner # Accepted capabilities are: can_edit, can_play, # can_stop, can_quit self.capabilities = ('can_stop', 'can_start', 'can_edit') # Status fields (in order) self.status_fields = ( # Status {'codename': 'status_field', 'title': 'Status'}, # Voltage {'codename': self.channel_id + '_voltage', 'title': 'Voltage', 'formatter': '{:.3f}', 'unit': 'V'}, # Voltage setpoint {'codename': self.channel_id + '_voltage_setpoint', 'title': 'Voltage Setpoint', 'formatter': '{:.3f}', 'unit': 'V'}, # Current {'codename': self.channel_id + '_current', 'title': 'Current', 'formatter': '{:.3f}', 'unit': 'A'}, # Current limit {'codename': self.channel_id + '_current_limit', 'title': 'Current limit', 'formatter': '{:.3f}', 'unit': 'A'}, # Charge {'codename': self.channel_id + '_accum_charge', 'title': 'Accumulated charge', 'formatter': '{:.3f}', 'unit': 'C'}, # Time elapsed (step) {'codename': 'elapsed', 'title': 'Time elapsed (step)', 'formatter': '{:.1f}', 'unit': 's'}, # Time remaining (step) {'codename': 'remaining', 'title': 'Time remaining (step)', 'formatter': '{:.1f}', 'unit': 's'}, # Time elapsed (total) {'codename': 'elapsed_total', 'title': 'Time elapsed (total)', 'formatter': '{:.1f}', 'unit': 's'}, # Time remaining (total) {'codename': 'remaining_total', 'title': 'Time remaining (total)', 'formatter': '{:.1f}', 'unit': 's'}, # Iteration time {'codename': 'iteration_time', 'title': 'Iteration time', 'formatter': '{:.2f}', 'unit': 's'}, ) # Queue for GUI updates self.message_queue = Queue() # The GUI also looks in self.config, see below ### Normal program # Setup my program with open(path.join(THIS_DIR, args.program_file)) as file__: self.config, self.steps = parse_ramp(file__) # The GUI will look for keys: program_title in config self.say('Using power supply channel: ' + self.channel_id) self.say('Loaded with config:\n' + pformat(self.config)) self.active_step = 0 self.send_steps() # Add completions for the edits self._completion_additions = [] for number, step in enumerate(self.steps): base = 'edit {} '.format(number) self._completion_additions.append(base) for field in sorted(step.fields): self._completion_additions.append('{}{}='.format(base, field)) # Base for the status self.status = {'status_field': 'Initialized'} # General variables self.stop = False self.ok_to_start = False # Setup power supply # Create a partial function with the output substitued in self.send_command = partial(_send_command, args.output, args.power_supply) self.power_supply_on_off(True, self.config['maxcurrent_start']) # Power supply commands, must match order with self.codenames self.power_supply_commands = ( 'read_actual_current', 'read_actual_voltage', 'read_set_voltage', 'read_current_limit' ) # Setup dataset saver and live socket self.codenames = [self.channel_id + id_ for id_ in ('_current', '_voltage', '_voltage_setpoint', '_current_limit')] self.live_socket = LiveSocket( 'H2O2_proactive_' + self.channel_id, self.codenames + [self.channel_id + '_accum_charge'], no_internal_data_pull_socket=True ) self.live_socket.reset(self.codenames) self.live_socket.start() self.data_set_saver = DataSetSaver( credentials.measurements, credentials.xy_values, username=credentials.username, password=credentials.password ) self.data_set_saver.start() # Done with init, send status self.send_status() def command(self, command, args_str): """Process commands from the GUI""" if command == 'stop': # stop is sent on quit self.stop = True elif command == 'start': self.ok_to_start = True elif command == 'edit': # Parse the edit line, start by splitting up in step_num, field and value try: num_step, field_value = [arg.strip() for arg in args_str.split(' ')] field, value = [arg.strip() for arg in field_value.split('=')] except ValueError: message = ('Bad edit command, must be on the form:\n' 'edit step_num field=value') self.say(message, message_type='error') return # Try to get the correct step try: step = self.steps[int(num_step)] except (ValueError, IndexError): message = 'Unable to convert step number {} to integer or nu such step '\ 'exists' self.say(message.format(num_step), message_type='error') return # Edit the value try: step.edit_value(field, value) except (ValueError, AttributeError) as exception: self.say(str(exception.args[0]), message_type='error') return # Finally send the new steps to the GUI self.send_steps() def send_status(self, update_dict=None): """Send the status to the GUI""" if update_dict: self.status.update(update_dict) self.message_queue.put(('status', self.status.copy())) def send_steps(self): """Send the steps list to the GUI""" steps = [(index == self.active_step, str(step)) for index, step in enumerate(self.steps)] self.message_queue.put(('steps', steps)) def say(self, text, message_type='message'): """Send a ordinary text message to the gui""" self.message_queue.put((message_type, text)) def run(self): """The MAIN run method""" # Wait for start while not self.ok_to_start: if self.stop: self.send_status({'status_field': 'Stopping'}) self.power_supply_on_off(False) self.send_status({'status_field': 'Stopped'}) return sleep(0.1) # Start self.send_status({'status_field': 'Starting'}) self.setup_data_set_saver() # Run the MAIN measurement loop # (This is where most of the time is spent) self.main_measure() # Shutdown powersupply, livesocket and possibly server self.send_status({'status_field': 'Stopping'}) self.stop_everything() self.send_status({'status_field': 'Stopped'}) sleep(0.1) self.say("I have stopped") def setup_data_set_saver(self): """Setup the data set saver""" sql_time = CustomColumn(time(), 'FROM_UNIXTIME(%s)') for codename in self.codenames: metadata = { 'time': sql_time, 'comment': self.config['comment'], 'label': codename[3:], 'type': 1, 'power_supply_channel': self.channel_id, } self.data_set_saver.add_measurement(codename, metadata) def main_measure(self): # pylint: disable=too-many-locals """The main measurement loop""" self.send_status({'status_field': 'Running'}) # Initial setup last_set_voltage = None last_set_max_current = None last_time = time() iteration_time = 'N/A' self.status['elapsed'] = 0.0 accum_charge_codename = self.channel_id + '_accum_charge' self.status[accum_charge_codename] = 0.0 current_id = self.channel_id + '_current' last_measured_current = 0.0 self.say('I started on step 0') for self.active_step, current_step in enumerate(self.steps): self.send_status({'status_field': 'Running step {}'.format(self.active_step)}) # Also give the step an instance name (for steps list) if self.active_step > 0: self.say('Switched to step: {}'.format(self.active_step)) self.send_steps() current_step.start() # While the step hasn't completed yet while current_step.elapsed() < current_step.duration: # Check if we should stop if self.stop: self.say('I have been asked to stop') return iteration_start = now = time() # Calculate the time for one iteration and update times in status iteration_time = now - last_time last_time = now self.status.update({ 'elapsed': current_step.elapsed(), 'remaining': current_step.remaining(), 'iteration_time': iteration_time, 'elapsed_total': sum(step.elapsed() for step in self.steps), 'remaining_total': sum(step.remaining() for step in self.steps), }) # Ask the power supply to set a new voltage if needed required_voltage, required_max_current = current_step.values() if required_max_current != last_set_max_current: self.send_command('set_current_limit', required_max_current) last_set_max_current = required_max_current if required_voltage != last_set_voltage: self.send_command('set_voltage', required_voltage) last_set_voltage = required_voltage # Read value from the power supply self._read_values_from_power_supply() # Calculate, set and send accumulated charge charge_addition = \ (last_measured_current + self.status[current_id])\ / 2 * iteration_time last_measured_current = self.status[current_id] self.status[accum_charge_codename] += charge_addition point = (self.status['elapsed_total'], self.status[accum_charge_codename]) self.live_socket.set_point(accum_charge_codename, point) # Send the new status self.send_status() # Calculate time to sleep to use the proper probe interval time_to_sleep = current_step.probe_interval - (time() - iteration_start) if time_to_sleep > 0: sleep(time_to_sleep) # Stop the step(s own time keeping) current_step.stop() # For loop over steps ended self.send_status({'status_field': 'Program Complete'}) self.say('Stepped program completed') def _read_values_from_power_supply(self): """Read all required values from the power supply (used only from run)""" for command, codename in zip(self.power_supply_commands, self.codenames): # Get a value for the current command value = self.send_command(command) # Set/save it on the live_socket, database and in the GUI point = (self.status['elapsed_total'], value) self.live_socket.set_point(codename, point) self.data_set_saver.save_point(codename, point) self.status[codename] = value def stop_everything(self): """Stop power supply and live socket""" self.power_supply_on_off(False) self.live_socket.stop() self.data_set_saver.stop() def power_supply_on_off(self, state, current_limit=0.0): """Set power supply on off""" # Set voltage to 0 LOG.debug('Stopping everything. Set voltage to 0.0') self.send_command('set_voltage', 0.0) start = time() # Anything < 1.0 Volt is OK while self.send_command('read_actual_voltage') > 1.0: if time() - start > 60: LOG.error('Unable to set voltage to 0') if state: raise RuntimeError('Unable to set voltage to 0') else: self.say('Unable to set voltage to 0') break sleep(1) # Set current limit self.send_command('set_current_limit', current_limit) read_current_limit = self.send_command('read_current_limit') if not isclose(read_current_limit, current_limit): raise RuntimeError('Unable to set current limit') # Set state self.send_command('output_status', state) read_state = self.send_command('read_output_status').strip() == '1' if not read_state is state: raise RuntimeError('Could not set output state')
count = 0 while True: count += 1 now = time() # Set sines data = { 'sine1': sin(now), 'sine2': sin(now + pi), } ls.set_batch_now(data) # Set cosines if time() - start > 6.28: start = time() ls.reset(['cosine1', 'cosine2']) x = time() - start data = { 'cosine1': [x, cos(x)], 'cosine2': [x, cos(x + pi)], } ls.set_batch(data) if count % 2 == 0: ls.set_point_now('status', 'OK') else: ls.set_point_now('status', 'even better') #if count % 10 == 0: # print(sock.recv(1024)) sleep(0.1)
class VoltageCurrentProgram(Thread): # pylint: disable=too-many-instance-attributes """The Voltage Current Program This program uses the generic stepped program runner as a GUI and serves information to that """ def __init__(self, args): super(VoltageCurrentProgram, self).__init__() # Form channel_id e.g: EA1 self.channel_id = args.power_supply + args.output ### Required by the stepped program runner # Accepted capabilities are: can_edit, can_play, # can_stop, can_quit self.capabilities = ('can_stop', 'can_start', 'can_edit') # Status fields (in order) self.status_fields = ( # Status { 'codename': 'status_field', 'title': 'Status' }, # Voltage { 'codename': self.channel_id + '_voltage', 'title': 'Voltage', 'formatter': '{:.3f}', 'unit': 'V' }, # Voltage setpoint { 'codename': self.channel_id + '_voltage_setpoint', 'title': 'Voltage Setpoint', 'formatter': '{:.3f}', 'unit': 'V' }, # Current { 'codename': self.channel_id + '_current', 'title': 'Current', 'formatter': '{:.3f}', 'unit': 'A' }, # Current limit { 'codename': self.channel_id + '_current_limit', 'title': 'Current limit', 'formatter': '{:.3f}', 'unit': 'A' }, # Charge { 'codename': self.channel_id + '_accum_charge', 'title': 'Accumulated charge', 'formatter': '{:.3f}', 'unit': 'C' }, # Time elapsed (step) { 'codename': 'elapsed', 'title': 'Time elapsed (step)', 'formatter': '{:.1f}', 'unit': 's' }, # Time remaining (step) { 'codename': 'remaining', 'title': 'Time remaining (step)', 'formatter': '{:.1f}', 'unit': 's' }, # Time elapsed (total) { 'codename': 'elapsed_total', 'title': 'Time elapsed (total)', 'formatter': '{:.1f}', 'unit': 's' }, # Time remaining (total) { 'codename': 'remaining_total', 'title': 'Time remaining (total)', 'formatter': '{:.1f}', 'unit': 's' }, # Iteration time { 'codename': 'iteration_time', 'title': 'Iteration time', 'formatter': '{:.2f}', 'unit': 's' }, ) self.extra_capabilities = { 'psuchannel': { 'help_text': ('Used for simple PSU control when not on\n' 'a ramp. Possibly usages are:\n' ' psuchannel voltage=1.23\n' 'which will set the voltage and\n' ' psuchannel off\n' 'which will set the output off'), 'completions': [ 'psuchannel', 'psuchannel voltage=', 'psuchannel off', ] } } # Queue for GUI updates self.message_queue = Queue() # The GUI also looks in self.config, see below ### Normal program # Setup my program with open(path.join(THIS_DIR, args.program_file)) as file__: self.config, self.steps = parse_ramp(file__) # The GUI will look for keys: program_title in config self.say('Using power supply channel: ' + self.channel_id) self.say('Loaded with config:\n' + pformat(self.config)) self.active_step = 0 self.send_steps() # Add completions for the edits self._completion_additions = [] for number, step in enumerate(self.steps): base = 'edit {} '.format(number) self._completion_additions.append(base) for field in sorted(step.fields): self._completion_additions.append('{}{}='.format(base, field)) # Base for the status self.status = {'status_field': 'Initialized'} # General variables self.stop = False self.ok_to_start = False # Create a partial function with the output substitued in self.send_command = partial( _send_command, args.output, args.power_supply, ) # Setup power supply self.power_supply_on_off(True, self.config['maxcurrent_start']) # Power supply commands, must match order with self.codenames self.power_supply_commands = ('read_actual_current', 'read_actual_voltage', 'read_set_voltage', 'read_current_limit') # Setup dataset saver and live socket self.codenames = [ self.channel_id + id_ for id_ in ('_current', '_voltage', '_voltage_setpoint', '_current_limit') ] self.live_socket = LiveSocket('H2O2_proactive_' + self.channel_id, self.codenames + [self.channel_id + '_accum_charge'], no_internal_data_pull_socket=True) self.live_socket.reset(self.codenames) self.live_socket.start() self.data_set_saver = DataSetSaver(credentials.measurements, credentials.xy_values, username=credentials.username, password=credentials.password) self.data_set_saver.start() # Done with init, send status self.send_status() def command(self, command, args_str): """Process commands from the GUI""" if command == 'stop': # stop is sent on quit self.stop = True elif command == 'start': self.ok_to_start = True elif command == 'edit': # Parse the edit line, start by splitting up in step_num, field and value try: num_step, field_value = [ arg.strip() for arg in args_str.split(' ') ] field, value = [arg.strip() for arg in field_value.split('=')] except ValueError: message = ('Bad edit command, must be on the form:\n' 'edit step_num field=value') self.say(message, message_type='error') return # Try to get the correct step try: step = self.steps[int(num_step)] except (ValueError, IndexError): message = 'Unable to convert step number {} to integer or nu such step '\ 'exists' self.say(message.format(num_step), message_type='error') return # Edit the value try: step.edit_value(field, value) except (ValueError, AttributeError) as exception: self.say(str(exception.args[0]), message_type='error') return # Finally send the new steps to the GUI self.send_steps() elif command == "psuchannel": if self.ok_to_start and not self.stop: message = "Using psuchannel during ramp not allowed" self.say(message, message_type='error') return if args_str.startswith('voltage='): voltage_str = args_str.replace('voltage=', '') try: voltage = float(voltage_str) except ValueError: message = 'Invalid voltage for psuchannel {}'.format( voltage_str) self.say(message, message_type='error') return self.send_command('set_voltage', voltage) self.say('PSU channel set to {}.\n' 'NOTE: The GUI will not update to show\n' 'this, only the actual PSU.'.format(voltage)) elif args_str == 'off': self.power_supply_on_off(False) self.say('PSU channel set to off') else: message = 'Invalid argument. psuchannel can do "voltage=" and "off"' self.say(message, message_type='error') return def send_status(self, update_dict=None): """Send the status to the GUI""" if update_dict: self.status.update(update_dict) self.message_queue.put(('status', self.status.copy())) def send_steps(self): """Send the steps list to the GUI""" steps = [(index == self.active_step, str(step)) for index, step in enumerate(self.steps)] self.message_queue.put(('steps', steps)) def say(self, text, message_type='message'): """Send a ordinary text message to the gui""" self.message_queue.put((message_type, text)) def run(self): """The MAIN run method""" # Wait for start while not self.ok_to_start: if self.stop: self.send_status({'status_field': 'Stopped'}) return sleep(0.1) # Start self.send_status({'status_field': 'Starting'}) self.setup_data_set_saver() # Run the MAIN measurement loop # (This is where most of the time is spent) self.main_measure() # Shutdown powersupply, livesocket and possibly server self.send_status({'status_field': 'Stopping'}) self.stop_everything() self.send_status({'status_field': 'Stopped'}) sleep(0.1) self.say("I have stopped") def setup_data_set_saver(self): """Setup the data set saver""" sql_time = CustomColumn(time(), 'FROM_UNIXTIME(%s)') for codename in self.codenames: metadata = { 'time': sql_time, 'comment': self.config['comment'], 'label': codename[3:], 'type': 1, 'power_supply_channel': self.channel_id, } self.data_set_saver.add_measurement(codename, metadata) def main_measure(self): # pylint: disable=too-many-locals """The main measurement loop""" self.send_status({'status_field': 'Running'}) # Initial setup last_set_voltage = None last_set_max_current = None last_time = time() iteration_time = 'N/A' self.status['elapsed'] = 0.0 accum_charge_codename = self.channel_id + '_accum_charge' self.status[accum_charge_codename] = 0.0 current_id = self.channel_id + '_current' last_measured_current = 0.0 self.say('I started on step 0') for self.active_step, current_step in enumerate(self.steps): self.send_status( {'status_field': 'Running step {}'.format(self.active_step)}) # Also give the step an instance name (for steps list) if self.active_step > 0: self.say('Switched to step: {}'.format(self.active_step)) self.send_steps() current_step.start() # While the step hasn't completed yet while current_step.elapsed() < current_step.duration: # Check if we should stop if self.stop: self.say('I have been asked to stop') return iteration_start = now = time() # Calculate the time for one iteration and update times in status iteration_time = now - last_time last_time = now self.status.update({ 'elapsed': current_step.elapsed(), 'remaining': current_step.remaining(), 'iteration_time': iteration_time, 'elapsed_total': sum(step.elapsed() for step in self.steps), 'remaining_total': sum(step.remaining() for step in self.steps), }) # Ask the power supply to set a new voltage if needed required_voltage, required_max_current = current_step.values() if required_max_current != last_set_max_current: self.send_command('set_current_limit', required_max_current) last_set_max_current = required_max_current if required_voltage != last_set_voltage: self.send_command('set_voltage', required_voltage) last_set_voltage = required_voltage # Read value from the power supply self._read_values_from_power_supply() # Calculate, set and send accumulated charge charge_addition = \ (last_measured_current + self.status[current_id])\ / 2 * iteration_time last_measured_current = self.status[current_id] self.status[accum_charge_codename] += charge_addition point = (self.status['elapsed_total'], self.status[accum_charge_codename]) self.live_socket.set_point(accum_charge_codename, point) # Send the new status self.send_status() # Calculate time to sleep to use the proper probe interval time_to_sleep = current_step.probe_interval - (time() - iteration_start) if time_to_sleep > 0: sleep(time_to_sleep) # Stop the step(s own time keeping) current_step.stop() # For loop over steps ended self.send_status({'status_field': 'Program Complete'}) self.say('Stepped program completed') def _read_values_from_power_supply(self): """Read all required values from the power supply (used only from run)""" for command, codename in zip(self.power_supply_commands, self.codenames): # Get a value for the current command value = self.send_command(command) # Set/save it on the live_socket, database and in the GUI point = (self.status['elapsed_total'], value) self.live_socket.set_point(codename, point) self.data_set_saver.save_point(codename, point) self.status[codename] = value def stop_everything(self): """Stop power supply and live socket""" self.live_socket.stop() self.data_set_saver.stop() def power_supply_on_off(self, state, current_limit=None): """Set power supply on off""" LOG.debug('Stop power supply') # Set current limit if current_limit is not None: self.send_command('set_current_limit', current_limit) read_current_limit = self.send_command('read_current_limit') if not isclose(read_current_limit, current_limit): raise RuntimeError('Unable to set current limit') # Set state self.send_command('output_status', state) read_state = self.send_command('read_output_status').strip() == '1' if not read_state is state: raise RuntimeError('Could not set output state')