devlib initial commit.
diff --git a/devlib/instrument/daq.py b/devlib/instrument/daq.py
new file mode 100644
index 0000000..cbee7b8
--- /dev/null
+++ b/devlib/instrument/daq.py
@@ -0,0 +1,138 @@
+import os
+import csv
+import tempfile
+from itertools import chain
+
+from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS
+from devlib.exception import HostError
+from devlib.utils.misc import unique
+
+try:
+ from daqpower.client import execute_command, Status
+ from daqpower.config import DeviceConfiguration, ServerConfiguration
+except ImportError, e:
+ execute_command, Status = None, None
+ DeviceConfiguration, ServerConfiguration, ConfigurationError = None, None, None
+ import_error_mesg = e.message
+
+
+class DaqInstrument(Instrument):
+
+ mode = CONTINUOUS
+
+ def __init__(self, target, resistor_values, # pylint: disable=R0914
+ labels=None,
+ host='localhost',
+ port=45677,
+ device_id='Dev1',
+ v_range=2.5,
+ dv_range=0.2,
+ sampling_rate=10000,
+ channel_map=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23),
+ ):
+ # pylint: disable=no-member
+ super(DaqInstrument, self).__init__(target)
+ self._need_reset = True
+ if execute_command is None:
+ raise HostError('Could not import "daqpower": {}'.format(import_error_mesg))
+ if labels is None:
+ labels = ['PORT_{}'.format(i) for i in xrange(len(resistor_values))]
+ if len(labels) != len(resistor_values):
+ raise ValueError('"labels" and "resistor_values" must be of the same length')
+ self.server_config = ServerConfiguration(host=host,
+ port=port)
+ result = self.execute('list_devices')
+ if result.status == Status.OK:
+ if device_id not in result.data:
+ raise ValueError('Device "{}" is not found on the DAQ server.'.format(device_id))
+ elif result.status != Status.OKISH:
+ raise HostError('Problem querying DAQ server: {}'.format(result.message))
+
+ self.device_config = DeviceConfiguration(device_id=device_id,
+ v_range=v_range,
+ dv_range=dv_range,
+ sampling_rate=sampling_rate,
+ resistor_values=resistor_values,
+ channel_map=channel_map,
+ labels=labels)
+
+ for label in labels:
+ for kind in ['power', 'voltage']:
+ self.add_channel(label, kind)
+
+ def reset(self, sites=None, kinds=None):
+ super(DaqInstrument, self).reset(sites, kinds)
+ self.execute('close')
+ result = self.execute('configure', config=self.device_config)
+ if not result.status == Status.OK: # pylint: disable=no-member
+ raise HostError(result.message)
+ self._need_reset = False
+
+ def start(self):
+ if self._need_reset:
+ self.reset()
+ self.execute('start')
+
+ def stop(self):
+ self.execute('stop')
+ self._need_reset = True
+
+ def get_data(self, outfile): # pylint: disable=R0914
+ tempdir = tempfile.mkdtemp(prefix='daq-raw-')
+ self.execute('get_data', output_directory=tempdir)
+ raw_file_map = {}
+ for entry in os.listdir(tempdir):
+ site = os.path.splitext(entry)[0]
+ path = os.path.join(tempdir, entry)
+ raw_file_map[site] = path
+
+ active_sites = unique([c.site for c in self.active_channels])
+ file_handles = []
+ try:
+ site_readers = {}
+ for site in active_sites:
+ try:
+ site_file = raw_file_map[site]
+ fh = open(site_file, 'rb')
+ site_readers[site] = csv.reader(fh)
+ file_handles.append(fh)
+ except KeyError:
+ message = 'Could not get DAQ trace for {}; Obtained traces are in {}'
+ raise HostError(message.format(site, tempdir))
+
+ # The first row is the headers
+ channel_order = []
+ for site, reader in site_readers.iteritems():
+ channel_order.extend(['{}_{}'.format(site, kind)
+ for kind in reader.next()])
+
+ def _read_next_rows():
+ parts = []
+ for reader in site_readers.itervalues():
+ try:
+ parts.extend(reader.next())
+ except StopIteration:
+ parts.extend([None, None])
+ return list(chain(parts))
+
+ with open(outfile, 'wb') as wfh:
+ field_names = [c.label for c in self.active_channels]
+ writer = csv.writer(wfh)
+ writer.writerow(field_names)
+ raw_row = _read_next_rows()
+ while any(raw_row):
+ row = [raw_row[channel_order.index(f)] for f in field_names]
+ writer.writerow(row)
+ raw_row = _read_next_rows()
+
+ return MeasurementsCsv(outfile, self.active_channels)
+ finally:
+ for fh in file_handles:
+ fh.close()
+
+ def teardown(self):
+ self.execute('close')
+
+ def execute(self, command, **kwargs):
+ return execute_command(self.server_config, command, **kwargs)
+