1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-04-07 02:10:03 +01:00
devlib/devlib/instrument/acmecape.py
Marc Bonnici 0d6c6883dd instruments: Add keep_raw parameter to control teardown deletion
Add a `keep_raw` parameter that prevents raw files from being deleted
during teardown in case they are still required.
2019-10-03 11:36:11 +01:00

169 lines
6.1 KiB
Python

# Copyright 2018 ARM Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#pylint: disable=attribute-defined-outside-init
from __future__ import division
import os
import sys
import time
import tempfile
import shlex
from fcntl import fcntl, F_GETFL, F_SETFL
from string import Template
from subprocess import Popen, PIPE, STDOUT
from pipes import quote
from devlib import Instrument, CONTINUOUS, MeasurementsCsv
from devlib.exception import HostError
from devlib.utils.csvutil import csvreader, csvwriter
from devlib.utils.misc import which
OUTPUT_CAPTURE_FILE = 'acme-cape.csv'
IIOCAP_CMD_TEMPLATE = Template("""
${iio_capture} -n ${host} -b ${buffer_size} -c -f ${outfile} ${iio_device}
""")
def _read_nonblock(pipe, size=1024):
fd = pipe.fileno()
flags = fcntl(fd, F_GETFL)
flags |= os.O_NONBLOCK
fcntl(fd, F_SETFL, flags)
output = ''
try:
while True:
output += pipe.read(size)
except IOError:
pass
return output
class AcmeCapeInstrument(Instrument):
mode = CONTINUOUS
def __init__(self, target,
iio_capture=which('iio-capture'),
host='baylibre-acme.local',
iio_device='iio:device0',
buffer_size=256,
keep_raw=False):
super(AcmeCapeInstrument, self).__init__(target)
self.iio_capture = iio_capture
self.host = host
self.iio_device = iio_device
self.buffer_size = buffer_size
self.keep_raw = keep_raw
self.sample_rate_hz = 100
if self.iio_capture is None:
raise HostError('Missing iio-capture binary')
self.command = None
self.process = None
self.add_channel('shunt', 'voltage')
self.add_channel('bus', 'voltage')
self.add_channel('device', 'power')
self.add_channel('device', 'current')
self.add_channel('timestamp', 'time_ms')
def __del__(self):
if self.process and self.process.pid:
self.logger.warning('killing iio-capture process [{}]...'.format(self.process.pid))
self.process.kill()
def reset(self, sites=None, kinds=None, channels=None):
super(AcmeCapeInstrument, self).reset(sites, kinds, channels)
self.raw_data_file = tempfile.mkstemp('.csv')[1]
params = dict(
iio_capture=self.iio_capture,
host=self.host,
# This must be a string for quote()
buffer_size=str(self.buffer_size),
iio_device=self.iio_device,
outfile=self.raw_data_file
)
params = {k: quote(v) for k, v in params.items()}
self.command = IIOCAP_CMD_TEMPLATE.substitute(**params)
self.logger.debug('ACME cape command: {}'.format(self.command))
def start(self):
self.process = Popen(shlex.split(self.command), stdout=PIPE, stderr=STDOUT)
def stop(self):
self.process.terminate()
timeout_secs = 10
output = ''
for _ in range(timeout_secs):
if self.process.poll() is not None:
break
time.sleep(1)
else:
output += _read_nonblock(self.process.stdout)
self.process.kill()
self.logger.error('iio-capture did not terminate gracefully')
if self.process.poll() is None:
msg = 'Could not terminate iio-capture:\n{}'
raise HostError(msg.format(output))
if self.process.returncode != 15: # iio-capture exits with 15 when killed
if sys.version_info[0] == 3:
output += self.process.stdout.read().decode(sys.stdout.encoding or 'utf-8', 'replace')
else:
output += self.process.stdout.read()
self.logger.info('ACME instrument encountered an error, '
'you may want to try rebooting the ACME device:\n'
' ssh root@{} reboot'.format(self.host))
raise HostError('iio-capture exited with an error ({}), output:\n{}'
.format(self.process.returncode, output))
if not os.path.isfile(self.raw_data_file):
raise HostError('Output CSV not generated.')
self.process = None
def get_data(self, outfile):
if os.stat(self.raw_data_file).st_size == 0:
self.logger.warning('"{}" appears to be empty'.format(self.raw_data_file))
return
all_channels = [c.label for c in self.list_channels()]
active_channels = [c.label for c in self.active_channels]
active_indexes = [all_channels.index(ac) for ac in active_channels]
with csvreader(self.raw_data_file, skipinitialspace=True) as reader:
with csvwriter(outfile) as writer:
writer.writerow(active_channels)
header = next(reader)
ts_index = header.index('timestamp ms')
for row in reader:
output_row = []
for i in active_indexes:
if i == ts_index:
# Leave time in ms
output_row.append(float(row[i]))
else:
# Convert rest into standard units.
output_row.append(float(row[i])/1000)
writer.writerow(output_row)
return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)
def get_raw(self):
return [self.raw_data_file]
def teardown(self):
if not self.keep_raw:
if os.path.isfile(self.raw_data_file):
os.remove(self.raw_data_file)