|
| | __init__ (self, device_id, log_level, callback=None) |
| | Instantiated by Controller.connect_new(), if-and-only-if a WasatchBus reports a DeviceID which has not already connected to the GUI.
|
| |
| | acquire_data (self, mode=None) |
| | This method is called by the Controller.
|
| |
| | acquire_status_message (self) |
| | Similar to acquire_data, this method is called by the Controller in MainProcess to dequeue a StatusMessage from the spectrometer child thread, if one is available.
|
| |
| | change_setting (self, setting, value=None) |
| | Add the specified setting and value to the local control queue.
|
| |
| | connect (self) |
| | Create a low level device object with the specified identifier, kick off the child thread to attempt to read from it.
|
| |
| | disconnect (self) |
| |
| | get_final_item (self, keep_averaged=False) |
| | Read from the response queue until empty (or we find an averaged item)
|
| |
| | poll_settings (self) |
| |
| | reset (self) |
| |
| | send_alert (self, setting, value) |
| |
| | wait_for_settings (self) |
| |
Wrap WasatchDevice in a non-blocking interface run in a separate thread, using multiprocess.pipes to exchange data (SpectrometerSettings, Readings and StatusMessages) for multiprocessing-safe device communications and acquisition under Windows and Qt.
- Todo
- This document is full of references to continuous_poll(). That method no longer exists, and has been replaced by WrapperWorker.run()
- Lifecycle
From ENLIGHTEN's standpoint (the original Wasatch.PY caller), here's what's going on:
- MainProcess (enlighten.Controller.setup_bus_listener) instantiates a wasatch.WasatchBus (bus) which will be persistent through the application lifetime. setup_bus_listener also creates a QTimer (bus_timer) which will check the USB bus for newly-connected ("hotplug") devices every second or so.
- Controller.tick_bus_listener
- does nothing (silently reschedules itself) if any new spectrometers are actively in the process of connecting, because things get hairy if we're trying to enumerate and configure several spectrometers at once
- calls bus.update(), which will internally instantiate and use a wasatch.DeviceFinderUSB to scan the current USB bus and update its internal list of all Wasatch spectrometers (whether already connected or otherwise)
- then calls Controller.connect_new() to process the updated device list (including determining whether any new devices are visible, and if so what to do about them)
- Controller.connect_new()
- if there is at least one new spectrometer on the device list, pull off that ONE device for connection (don't iterate over multiple new devices...we'll get them on a subsequent bus tick).
- instantiates a WasatchDeviceWrapper and then calls connect() on it
- WasatchDeviceWrapper.connect()
- spawns a thread running the continuous_poll() method of the same WasatchDeviceWrapper instance
- the WDW in the MainProcess then waits (blocks) while waiting for a single SpectrometerSettings object to be returned via a pipe from the child thread. This doesn't block the GUI, because this whole sequence is occuring in a background tick event on the bus_timer QTimer.
- the WDW in the child threadis running continuous_poll()
- instantiates a WasatchDevice to access the actual hardware spectrometer over USB (this object will only ever be referenced within this thread)
- then calls WasatchDevice.connect()
- continuous_poll() sends back the single SpectrometerSettings object to the blocked MainProcess by way of confirmation that a new spectrometer has successfully connected
Controller.connect_new() then completes the initialization of the spectrometer in the GUI by calling Controller.initialize_new_device(), which adds the spectrometer to Multispec, updates the EEPROMEditor, defaults the TEC controls and so on.
- In the background, WasatchDeviceWrapper.continuous_poll() continues running, acting as a "free-running mode driver" of the spectrometer, passing down new commands from the enlighten.Controller, and feeding back Readings or StatusMessages when they appear.
- Shutdown
The whole thing can be shutdown in various ways, using the concept of "poison pill" messages which can be flowed either upstream or downstream:
- if a hardware error occurs in the spectrometer thread, it sends a poison pill upstream (a False value where a Reading is expected), then self-destructs (Controller is expected to drop the spectrometer from the GUI)
- if the GUI is closing, poison-pills (None values where ControlObjects are expected) are sent downstream to each spectrometer thread, telling them to terminate themselves
- Throughput Considerations
It is important to recognize that continuous_poll() updates the command/response pipes at a relatively leisurely interval (currently 20Hz, set in POLLER_WAIT_SEC). No matter how short the integration time is (1ms), you're not going to get spectra faster than 20 per second through this (as ENLIGHTEN was designed as a real-time visualization tool, not a high-speed data collection tool).
Now, if you set ACQUISITION_MODE_KEEP_ALL, then you should still get every spectrum (whatever the spectrometer achieved through its scan-rate, potentially 220/sec or so) – but you'll get them in chunks (e.g., scan rate of 220/sec polled at 20Hz = 20 chunks of 11 ea).
- Responsiveness
With regard to the "immediacy" of commands like laser_enable, note that spectrometer threads are internally single-threaded: the continuous_poll() function BLOCKS on WasatchDevice.acquire_data(), meaning that even if new laser_enable commands get pushed into the downstream command pipe, continuous_poll won't check for them until the end of the current acquisition.
What we'd really like is two threads running in the child, one handling acquisitions and most commands, and another handling high-priority events like laser_enable. I'm not sure the pipes are designed for multiple readers, but a SEPARATE pipe (or queue) could be setup for high-priority commands, and EXCLUSIVELY read by the secondary thread. That just leaves open the question of synchronization on WasatchDevice's USBDevice.
| wasatch.WasatchDeviceWrapper.WasatchDeviceWrapper.connect |
( |
| self | ) |
|
Create a low level device object with the specified identifier, kick off the child thread to attempt to read from it.
Called by Controller.connect_new() immediately after instantiation
Spawn a child thread running the continuous_poll() method on THIS object instance.
The two threads are coupled via the 3 queues:
- settings_queue
Lets the child (WasatchDevice) send a single, one-time copy of a populated SpectrometerSettings object back through the Wrapper to the calling Controller. This is the primary way the Controller knows what kind of spectrometer it has connected to, what hardware features and EEPROM settings are applied etc.
Thereafter both WasatchDevice and Controller will maintain their own copies of SpectrometerSettings, and they are not automatically synchronized (is there a clever way to do this?). They may well drift out-of-sync regarding state, although the command_queue helps keep them somewhat in sync.
- command_queue
The Controller will send ControlObject instances (basically (name, value) pairs) through the Wrapper to the WasatchDevice to set attributes or commands on the WasatchDevice spectrometer. These may be volatile hardware settings (laser power, integration time), meta-commands to the WasatchDevice class (scan averaging), or EEPROM updates.
- response_queue
The WasatchDevice will stream a continuous series of Reading instances back through the Wrapper to the Controller. These each contain a newly read spectrum, as well as metadata about the spectrometer at the time the spectrum was taken (integration time, laser power), plus additional readings from the spectrometer (detector and laser temperature, secondary ADC).