Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 1 | #pylint: disable=attribute-defined-outside-init |
| 2 | from __future__ import division |
| 3 | import csv |
| 4 | import os |
| 5 | import time |
| 6 | import tempfile |
| 7 | from fcntl import fcntl, F_GETFL, F_SETFL |
| 8 | from string import Template |
| 9 | from subprocess import Popen, PIPE, STDOUT |
| 10 | |
| 11 | from devlib import Instrument, CONTINUOUS, MeasurementsCsv |
| 12 | from devlib.exception import HostError |
| 13 | from devlib.utils.misc import which |
| 14 | |
| 15 | OUTPUT_CAPTURE_FILE = 'acme-cape.csv' |
| 16 | IIOCAP_CMD_TEMPLATE = Template(""" |
| 17 | ${iio_capture} -n ${host} -b ${buffer_size} -c -f ${outfile} ${iio_device} |
| 18 | """) |
| 19 | |
| 20 | def _read_nonblock(pipe, size=1024): |
| 21 | fd = pipe.fileno() |
| 22 | flags = fcntl(fd, F_GETFL) |
| 23 | flags |= os.O_NONBLOCK |
| 24 | fcntl(fd, F_SETFL, flags) |
| 25 | |
| 26 | output = '' |
| 27 | try: |
| 28 | while True: |
| 29 | output += pipe.read(size) |
| 30 | except IOError: |
| 31 | pass |
| 32 | return output |
| 33 | |
| 34 | |
| 35 | class AcmeCapeInstrument(Instrument): |
| 36 | |
| 37 | mode = CONTINUOUS |
| 38 | |
| 39 | def __init__(self, target, |
Brendan Jackman | 0b04ffc | 2017-10-04 17:12:27 +0100 | [diff] [blame] | 40 | iio_capture=which('iio-capture'), |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 41 | host='baylibre-acme.local', |
| 42 | iio_device='iio:device0', |
| 43 | buffer_size=256): |
| 44 | super(AcmeCapeInstrument, self).__init__(target) |
| 45 | self.iio_capture = iio_capture |
| 46 | self.host = host |
| 47 | self.iio_device = iio_device |
| 48 | self.buffer_size = buffer_size |
Marc Bonnici | 7dd934a | 2017-08-03 16:41:10 +0100 | [diff] [blame] | 49 | self.sample_rate_hz = 100 |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 50 | if self.iio_capture is None: |
| 51 | raise HostError('Missing iio-capture binary') |
| 52 | self.command = None |
| 53 | self.process = None |
| 54 | |
| 55 | self.add_channel('shunt', 'voltage') |
| 56 | self.add_channel('bus', 'voltage') |
| 57 | self.add_channel('device', 'power') |
| 58 | self.add_channel('device', 'current') |
| 59 | self.add_channel('timestamp', 'time_ms') |
| 60 | |
Patrick Bellasi | 4a6aace | 2017-10-12 14:51:19 +0100 | [diff] [blame] | 61 | def __del__(self): |
| 62 | if self.process and self.process.pid: |
| 63 | self.logger.warning('killing iio-capture process [%d]...', |
| 64 | self.process.pid) |
| 65 | self.process.kill() |
| 66 | |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 67 | def reset(self, sites=None, kinds=None, channels=None): |
| 68 | super(AcmeCapeInstrument, self).reset(sites, kinds, channels) |
| 69 | self.raw_data_file = tempfile.mkstemp('.csv')[1] |
| 70 | params = dict( |
| 71 | iio_capture=self.iio_capture, |
| 72 | host=self.host, |
| 73 | buffer_size=self.buffer_size, |
| 74 | iio_device=self.iio_device, |
| 75 | outfile=self.raw_data_file |
| 76 | ) |
| 77 | self.command = IIOCAP_CMD_TEMPLATE.substitute(**params) |
| 78 | self.logger.debug('ACME cape command: {}'.format(self.command)) |
| 79 | |
| 80 | def start(self): |
| 81 | self.process = Popen(self.command.split(), stdout=PIPE, stderr=STDOUT) |
| 82 | |
| 83 | def stop(self): |
| 84 | self.process.terminate() |
| 85 | timeout_secs = 10 |
Brendan Jackman | dbe568f | 2017-10-04 17:32:32 +0100 | [diff] [blame] | 86 | output = '' |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 87 | for _ in xrange(timeout_secs): |
| 88 | if self.process.poll() is not None: |
| 89 | break |
| 90 | time.sleep(1) |
| 91 | else: |
Brendan Jackman | dbe568f | 2017-10-04 17:32:32 +0100 | [diff] [blame] | 92 | output += _read_nonblock(self.process.stdout) |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 93 | self.process.kill() |
| 94 | self.logger.error('iio-capture did not terminate gracefully') |
| 95 | if self.process.poll() is None: |
| 96 | msg = 'Could not terminate iio-capture:\n{}' |
| 97 | raise HostError(msg.format(output)) |
Brendan Jackman | dbe568f | 2017-10-04 17:32:32 +0100 | [diff] [blame] | 98 | if self.process.returncode != 15: # iio-capture exits with 15 when killed |
| 99 | output += self.process.stdout.read() |
Brendan Jackman | 7dd7811 | 2017-10-09 15:28:57 +0100 | [diff] [blame] | 100 | self.logger.info('ACME instrument encountered an error, ' |
| 101 | 'you may want to try rebooting the ACME device:\n' |
| 102 | ' ssh root@{} reboot'.format(self.host)) |
Brendan Jackman | dbe568f | 2017-10-04 17:32:32 +0100 | [diff] [blame] | 103 | raise HostError('iio-capture exited with an error ({}), output:\n{}' |
| 104 | .format(self.process.returncode, output)) |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 105 | if not os.path.isfile(self.raw_data_file): |
| 106 | raise HostError('Output CSV not generated.') |
Patrick Bellasi | 4a6aace | 2017-10-12 14:51:19 +0100 | [diff] [blame] | 107 | self.process = None |
Marc Bonnici | 30fdfc2 | 2017-07-24 15:52:34 +0100 | [diff] [blame] | 108 | |
| 109 | def get_data(self, outfile): |
| 110 | if os.stat(self.raw_data_file).st_size == 0: |
| 111 | self.logger.warning('"{}" appears to be empty'.format(self.raw_data_file)) |
| 112 | return |
| 113 | |
| 114 | all_channels = [c.label for c in self.list_channels()] |
| 115 | active_channels = [c.label for c in self.active_channels] |
| 116 | active_indexes = [all_channels.index(ac) for ac in active_channels] |
| 117 | |
| 118 | with open(self.raw_data_file, 'rb') as fh: |
| 119 | with open(outfile, 'wb') as wfh: |
| 120 | writer = csv.writer(wfh) |
| 121 | writer.writerow(active_channels) |
| 122 | |
| 123 | reader = csv.reader(fh, skipinitialspace=True) |
| 124 | header = reader.next() |
| 125 | ts_index = header.index('timestamp ms') |
| 126 | |
| 127 | |
| 128 | for row in reader: |
| 129 | output_row = [] |
| 130 | for i in active_indexes: |
| 131 | if i == ts_index: |
| 132 | # Leave time in ms |
| 133 | output_row.append(float(row[i])) |
| 134 | else: |
| 135 | # Convert rest into standard units. |
| 136 | output_row.append(float(row[i])/1000) |
| 137 | writer.writerow(output_row) |
Marc Bonnici | 049b275 | 2017-08-03 16:43:32 +0100 | [diff] [blame] | 138 | return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz) |
Sergei Trofimov | 823ce71 | 2017-08-30 14:55:42 +0100 | [diff] [blame] | 139 | |
| 140 | def get_raw(self): |
| 141 | return [self.raw_data_file] |