Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | import os |
| 2 | import csv |
| 3 | import tempfile |
| 4 | from itertools import chain |
| 5 | |
| 6 | from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS |
| 7 | from devlib.exception import HostError |
| 8 | from devlib.utils.misc import unique |
| 9 | |
| 10 | try: |
| 11 | from daqpower.client import execute_command, Status |
| 12 | from daqpower.config import DeviceConfiguration, ServerConfiguration |
| 13 | except ImportError, e: |
| 14 | execute_command, Status = None, None |
| 15 | DeviceConfiguration, ServerConfiguration, ConfigurationError = None, None, None |
| 16 | import_error_mesg = e.message |
| 17 | |
| 18 | |
| 19 | class DaqInstrument(Instrument): |
| 20 | |
| 21 | mode = CONTINUOUS |
| 22 | |
| 23 | def __init__(self, target, resistor_values, # pylint: disable=R0914 |
| 24 | labels=None, |
| 25 | host='localhost', |
| 26 | port=45677, |
| 27 | device_id='Dev1', |
| 28 | v_range=2.5, |
| 29 | dv_range=0.2, |
Brendan Jackman | 0d61ee5 | 2017-04-26 16:17:43 +0100 | [diff] [blame] | 30 | sample_rate_hz=10000, |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 31 | channel_map=(0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23), |
| 32 | ): |
| 33 | # pylint: disable=no-member |
| 34 | super(DaqInstrument, self).__init__(target) |
| 35 | self._need_reset = True |
Sergei Trofimov | 823ce71 | 2017-08-30 14:55:42 +0100 | [diff] [blame^] | 36 | self._raw_files = [] |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 37 | if execute_command is None: |
| 38 | raise HostError('Could not import "daqpower": {}'.format(import_error_mesg)) |
| 39 | if labels is None: |
| 40 | labels = ['PORT_{}'.format(i) for i in xrange(len(resistor_values))] |
| 41 | if len(labels) != len(resistor_values): |
| 42 | raise ValueError('"labels" and "resistor_values" must be of the same length') |
| 43 | self.server_config = ServerConfiguration(host=host, |
| 44 | port=port) |
| 45 | result = self.execute('list_devices') |
| 46 | if result.status == Status.OK: |
| 47 | if device_id not in result.data: |
| 48 | raise ValueError('Device "{}" is not found on the DAQ server.'.format(device_id)) |
| 49 | elif result.status != Status.OKISH: |
| 50 | raise HostError('Problem querying DAQ server: {}'.format(result.message)) |
| 51 | |
| 52 | self.device_config = DeviceConfiguration(device_id=device_id, |
| 53 | v_range=v_range, |
| 54 | dv_range=dv_range, |
Brendan Jackman | 0d61ee5 | 2017-04-26 16:17:43 +0100 | [diff] [blame] | 55 | sampling_rate=sample_rate_hz, |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 56 | resistor_values=resistor_values, |
| 57 | channel_map=channel_map, |
| 58 | labels=labels) |
Brendan Jackman | 0d61ee5 | 2017-04-26 16:17:43 +0100 | [diff] [blame] | 59 | self.sample_rate_hz = sample_rate_hz |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 60 | |
| 61 | for label in labels: |
| 62 | for kind in ['power', 'voltage']: |
| 63 | self.add_channel(label, kind) |
| 64 | |
Sergei Trofimov | 390a544 | 2016-09-02 14:03:33 +0100 | [diff] [blame] | 65 | def reset(self, sites=None, kinds=None, channels=None): |
| 66 | super(DaqInstrument, self).reset(sites, kinds, channels) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 67 | self.execute('close') |
| 68 | result = self.execute('configure', config=self.device_config) |
| 69 | if not result.status == Status.OK: # pylint: disable=no-member |
| 70 | raise HostError(result.message) |
| 71 | self._need_reset = False |
Sergei Trofimov | 823ce71 | 2017-08-30 14:55:42 +0100 | [diff] [blame^] | 72 | self._raw_files = [] |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 73 | |
| 74 | def start(self): |
| 75 | if self._need_reset: |
| 76 | self.reset() |
| 77 | self.execute('start') |
| 78 | |
| 79 | def stop(self): |
| 80 | self.execute('stop') |
| 81 | self._need_reset = True |
| 82 | |
| 83 | def get_data(self, outfile): # pylint: disable=R0914 |
| 84 | tempdir = tempfile.mkdtemp(prefix='daq-raw-') |
| 85 | self.execute('get_data', output_directory=tempdir) |
| 86 | raw_file_map = {} |
| 87 | for entry in os.listdir(tempdir): |
| 88 | site = os.path.splitext(entry)[0] |
| 89 | path = os.path.join(tempdir, entry) |
| 90 | raw_file_map[site] = path |
Sergei Trofimov | 823ce71 | 2017-08-30 14:55:42 +0100 | [diff] [blame^] | 91 | self._raw_files.append(path) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 92 | |
| 93 | active_sites = unique([c.site for c in self.active_channels]) |
| 94 | file_handles = [] |
| 95 | try: |
| 96 | site_readers = {} |
| 97 | for site in active_sites: |
| 98 | try: |
| 99 | site_file = raw_file_map[site] |
| 100 | fh = open(site_file, 'rb') |
| 101 | site_readers[site] = csv.reader(fh) |
| 102 | file_handles.append(fh) |
| 103 | except KeyError: |
| 104 | message = 'Could not get DAQ trace for {}; Obtained traces are in {}' |
| 105 | raise HostError(message.format(site, tempdir)) |
| 106 | |
| 107 | # The first row is the headers |
| 108 | channel_order = [] |
| 109 | for site, reader in site_readers.iteritems(): |
| 110 | channel_order.extend(['{}_{}'.format(site, kind) |
| 111 | for kind in reader.next()]) |
| 112 | |
| 113 | def _read_next_rows(): |
| 114 | parts = [] |
| 115 | for reader in site_readers.itervalues(): |
| 116 | try: |
| 117 | parts.extend(reader.next()) |
| 118 | except StopIteration: |
| 119 | parts.extend([None, None]) |
| 120 | return list(chain(parts)) |
| 121 | |
| 122 | with open(outfile, 'wb') as wfh: |
| 123 | field_names = [c.label for c in self.active_channels] |
| 124 | writer = csv.writer(wfh) |
| 125 | writer.writerow(field_names) |
| 126 | raw_row = _read_next_rows() |
| 127 | while any(raw_row): |
| 128 | row = [raw_row[channel_order.index(f)] for f in field_names] |
| 129 | writer.writerow(row) |
| 130 | raw_row = _read_next_rows() |
| 131 | |
Marc Bonnici | 049b275 | 2017-08-03 16:43:32 +0100 | [diff] [blame] | 132 | return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 133 | finally: |
| 134 | for fh in file_handles: |
| 135 | fh.close() |
| 136 | |
Sergei Trofimov | 823ce71 | 2017-08-30 14:55:42 +0100 | [diff] [blame^] | 137 | def get_raw(self): |
| 138 | return self._raw_files |
| 139 | |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 140 | def teardown(self): |
| 141 | self.execute('close') |
| 142 | |
| 143 | def execute(self, command, **kwargs): |
| 144 | return execute_command(self.server_config, command, **kwargs) |
| 145 | |