diff --git a/src/helmholtz_cage_device.py b/src/helmholtz_cage_device.py index ae74022..7a16ba5 100644 --- a/src/helmholtz_cage_device.py +++ b/src/helmholtz_cage_device.py @@ -29,6 +29,12 @@ class HelmholtzCageDevice: # Indicates all the threads should be joined self._stop_flag = Event() + # --- POLLING SUBSCRIBERS --- + # This is a list of object callbacks interested in receiving device status updates. + # This will primarily include the front-end which wants to update its display data. + # The callback functions should accept a dict as an argument of the form {'arduino':, 'axes':[{}, {}, {}]} + self._subscribers = [] + # --- COMMAND QUEUEING --- self.command_lock = RLock() # Indicates to the command executing thread that a new command has arrived for execution @@ -94,7 +100,7 @@ class HelmholtzCageDevice: # Loop over axes for i in range(3): axis_dict = {} - for key in ["coil_const", "ambient_field", "resistance", "max_volts", "max_amps"] + for key in ["coil_const", "ambient_field", "resistance", "max_volts", "max_amps"]: axis_dict[key] = float(config.read_from_config(g.AXIS_NAMES[i], key, config.CONFIG_OBJECT)) self.axes.append(axis_dict) @@ -125,6 +131,11 @@ class HelmholtzCageDevice: self.proxy_id = None # Otherwise do nothing, this case requires no behaviour + def subscribe_status_updates(self, callback): + # List containing all interested subscribers. + # We won't check if a callback is added twice. Not our responsibility + self._subscribers.append(callback) + def queue_command(self, proxy_obj, command): """ Queues a dict for immediate execution containing the command for the cage as a whole. Since the newest command should always be run, it is not a real queue (just a variable)""" @@ -172,8 +183,19 @@ class HelmholtzCageDevice: if stop_flag_set: return + status_data = {'axes': []} with self.hardware_lock: - pass + # This polls all three axes at once + for i in range(3): + # Helper function to find correct psu and channel to talk to + psu, channel = self._get_psu_for_axis(i) + # This is a slow operation, watch out! + status = psu.poll_channel_state(channel) + status_data['axes'].append(status) + + # Distribute status data to all interested subscribers + for subscriber in self._subscribers: + subscriber(status_data) def _set_field_raw(self, arg): currents = [] @@ -217,33 +239,40 @@ class HelmholtzCageDevice: # min. 8V, max. max_volts, in-between as needed with current value (+margin to not limit current) voltage_limit = min(max(1.3 * abs(safe_current) * self.axes[i]['resistance'], 8), self.axes[i]['max_volts']) # limit voltage - # TODO: This kind of stuff belongs in the config and should not be hardcoded - # Determine which channel of which psu is required - if i == 0 or i == 1: - psu = self.psu1 - channel = psu.valid_channels[i] - else: - psu = self.psu2 - channel = psu.valid_channels[0] + + # Helper function to find correct psu and channel to talk to + psu, channel = self._get_psu_for_axis(i) # Set voltages and currents. Outputs should already be active from initializer. psu.set_current(channel, safe_current) psu.set_voltage(channel, voltage_limit) + def _get_psu_for_axis(self, axis_index): + """Determine which channel of which psu is required""" + # TODO: This kind of stuff belongs in the config and should not be hardcoded + if axis_index == 0 or axis_index == 1: + psu = self.psu1 + channel = psu.valid_channels[axis_index] + else: + psu = self.psu2 + channel = psu.valid_channels[0] + return psu, channel + def shutdown(self): """ Shuts down the hardware. This special command overrides the currently active proxy. The object cannot be recovered from this state, but may be re-instantiated.""" # Send signals to kill threads: + # TODO: Handle timeout behaviour self._stop_flag.set() # _cmd_exec_thread: with self.command_lock: self.command = None self.new_command_flag.set() # Causes the thread to unblock self._cmd_exec_thread.join(timeout=2) - # TODO: Handle timeout behaviour #_hw_poll_thread: - + # This thread is stopped just by setting the _stop_flag + self._hw_poll_thread.join(timeout=2) # This waiting period is not easily removed without resulting in unexpected behaviour with self.hardware_lock: