Skip to content

Commit

Permalink
Merge pull request #126 from Emantor/topic/sigrok
Browse files Browse the repository at this point in the history
sigrok support
  • Loading branch information
jluebbe committed Feb 16, 2018
2 parents 17dd6e8 + 4139300 commit d779cc8
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 1 deletion.
49 changes: 48 additions & 1 deletion doc/configuration.rst
Expand Up @@ -206,6 +206,23 @@ A USBMassStorage resource describes a USB memory stick or similar device.
Used by:
- `USBStorageDriver`_

SigrokDevice
~~~~~~~~~~~~~~~~
A SigrokDevice resource describes a sigrok device. To select a specific device
from all connected supported devices use the `SigrokUSBDevice`_.

.. code-block:: yaml
SigrokUSBDevice:
driver: fx2lafw
channel: "D0=CLK,D1=DATA"
- driver (str): name of the sigrok driver to use
- channel (str): channel mapping as described in the sigrok-cli man page

Used by:
- `SigrokDriver`_

IMXUSBLoader
~~~~~~~~~~~~
An IMXUSBLoader resource describes a USB device in the imx loader state.
Expand Down Expand Up @@ -281,7 +298,7 @@ An AlteraUSBBlaster resource describes an Altera USB blaster.
match:
'ID_PATH': 'pci-0000:06:00.0-usb-0:1.3.2:1.0'
- match (str): key and value for a udev match, see `udev Matching`_
- match (dict): key and value for a udev match, see `udev Matching`_

Used by:
- `OpenOCDDriver`_
Expand All @@ -300,6 +317,25 @@ accessible via SNMP.
- switch (str): host name of the Ethernet switch
- interface (str): interface name

SigrokUSBDevice
~~~~~~~~~~~~~~~~
A SigrokUSBDevice resource describes a sigrok USB device.

.. code-block:: yaml
SigrokUSBDevice:
driver: fx2lafw
channel: "D0=CLK,D1=DATA"
match:
'ID_PATH': 'pci-0000:06:00.0-usb-0:1.3.2:1.0'
- driver (str): name of the sigrok driver to use
- channel (str): channel mapping as described in the sigrok-cli man page
- match (str): key and value for a udev match, see `udev Matching`_

Used by:
- `SigrokDriver`_

RemotePlace
~~~~~~~~~~~
A RemotePlace describes a set of resources attached to a labgrid remote place.
Expand Down Expand Up @@ -904,6 +940,17 @@ The qemudriver also requires the specification of:
specify the build device tree
- a path key, this is the path to the rootfs

SigrokDriver
~~~~~~~~~~~~
The SigrokDriver uses a SigrokDriver Resource to record samples and provides
them during test runs.

Implements:
- None yet

The driver can be used in test cases by calling the `capture`, `stop` and
`analyze` functions.

Strategies
~~~~~~~~~~
Strategies are used to ensure that the device is in a certain state during a test.
Expand Down
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Expand Up @@ -15,3 +15,4 @@
from .common import Driver
from .qemudriver import QEMUDriver
from .modbusdriver import ModbusCoilDriver
from .sigrokdriver import SigrokDriver
239 changes: 239 additions & 0 deletions labgrid/driver/sigrokdriver.py
@@ -0,0 +1,239 @@
# pylint: disable=no-member
import logging
import os.path
import re
import subprocess
import shutil
import signal
import tempfile
import uuid
import csv

from time import sleep

import attr

from ..factory import target_factory
from ..resource.remote import NetworkSigrokUSBDevice
from ..resource.udev import SigrokUSBDevice
from ..resource.sigrok import SigrokDevice
from ..step import step
from .common import Driver, check_file


@target_factory.reg_driver
@attr.s(cmp=False)
class SigrokDriver(Driver):
"""The SigrokDriver uses sigrok-cli to record samples and expose them as python dictionaries.
Args:
bindings (dict): driver to use with sigrok
"""
bindings = {
"sigrok": {SigrokUSBDevice, NetworkSigrokUSBDevice, SigrokDevice},
}

def __attrs_post_init__(self):
super().__attrs_post_init__()
# FIXME make sure we always have an environment or config
if self.target.env:
self.tool = self.target.env.config.get_tool(
'sigrok-cli'
) or 'sigrok-cli'
else:
self.tool = 'sigrok-cli'
self.log = logging.getLogger("SigrokDriver")
self._running = False

def _get_sigrok_prefix(self):
if isinstance(self.sigrok, (NetworkSigrokUSBDevice, SigrokUSBDevice)):
prefix = [
self.tool, "-d", "{}:conn={}.{}".format(
self.sigrok.driver, self.sigrok.busnum, self.sigrok.devnum
), "-C", self.sigrok.channels
]
else:
prefix = [
self.tool, "-d", self.sigrok.driver, "-C", self.sigrok.channels
]
return self.sigrok.command_prefix + prefix

def _create_tmpdir(self):
if isinstance(self.sigrok, NetworkSigrokUSBDevice):
self._tmpdir = '/tmp/labgrid-sigrok-{}'.format(uuid.uuid1())
command = self.sigrok.command_prefix + [
'mkdir', '-p', self._tmpdir
]
self.log.debug("Tmpdir command: %s", command)
subprocess.call(
command,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
self.log.debug("Created tmpdir: %s", self._tmpdir)
self._local_tmpdir = tempfile.mkdtemp(prefix="labgrid-sigrok-")
self.log.debug("Created local tmpdir: %s", self._local_tmpdir)
else:
self._tmpdir = tempfile.mkdtemp(prefix="labgrid-sigrok-")
self.log.debug("created tmpdir: %s", self._tmpdir)

def _delete_tmpdir(self):
if isinstance(self.sigrok, NetworkSigrokUSBDevice):
command = self.sigrok.command_prefix + [
'rm', '-r', self._tmpdir
]
self.log.debug("Tmpdir command: %s", command)
subprocess.call(
command,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
shutil.rmtree(self._local_tmpdir)
else:
shutil.rmtree(self._tmpdir)


def on_activate(self):
self._create_tmpdir()

def on_deactivate(self):
self._delete_tmpdir()

@Driver.check_active
@step(title='call', args=['args'])
def _call_with_driver(self, *args):
combined = self._get_sigrok_prefix() + list(args)
self.log.debug("Combined command: %s", " ".join(combined))
self._process = subprocess.Popen(
combined,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE
)

@Driver.check_active
@step(title='call', args=['args'])
def _call(self, *args):
combined = self.sigrok.command_prefix + [
self.tool, "-C", self.sigrok.channels
] + list(args)
self.log.debug("Combined command: %s", combined)
self._process = subprocess.Popen(
combined,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE
)

@Driver.check_active
def capture(self, filename, samplerate="200k"):
self._filename = filename
self._basename = os.path.basename(self._filename)
self.log.debug(
"Saving to: %s with basename: %s", self._filename, self._basename
)
cmd = [
"-l", "4", "--config", "samplerate={}".format(samplerate),
"--continuous", "-o"
]
filename = os.path.join(self._tmpdir, self._basename)
cmd.append(filename)
self._call_with_driver(*cmd)
while subprocess.call(
self.sigrok.command_prefix + [
'test',
'-e',
filename,
]
):
sleep(0.1)

self._running = True

@Driver.check_active
def stop(self):
assert self._running == True
self._running = False
fnames = ['time']
fnames.extend(self.sigrok.channels.split(','))
csv_filename = '{}.csv'.format(os.path.splitext(self._basename)[0])

self._process.send_signal(signal.SIGINT)
stdout, stderr = self._process.communicate()
self._process.wait()
self.log.debug("stdout:\n %s\n ----- \n stderr:\n %s", stdout, stderr)

# Convert from .sr to .csv
cmd = [
'-i',
os.path.join(self._tmpdir, self._basename), '-O', 'csv', '-o',
os.path.join(self._tmpdir, csv_filename)
]
self._call(*cmd)
self._process.wait()
stdout, stderr = self._process.communicate()
self.log.debug("stdout:\n %s\n ----- \n stderr:\n %s", stdout, stderr)
if isinstance(self.sigrok, NetworkSigrokUSBDevice):
subprocess.call([
'scp', '{}:{}'.format(
self.sigrok.host,
os.path.join(self._tmpdir, self._basename)
),
os.path.join(self._local_tmpdir, self._filename)
],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
# get csv from remote host
subprocess.call([
'scp', '{}:{}'.format(
self.sigrok.host, os.path.join(self._tmpdir, csv_filename)
),
os.path.join(self._local_tmpdir, csv_filename)
],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
with open(os.path.join(self._local_tmpdir,
csv_filename)) as csv_file:
# skip first 5 lines of the csv output, contains metadata and fieldnames
for _ in range(0, 5):
next(csv_file)
return [x for x in csv.DictReader(csv_file, fieldnames=fnames)]
else:
shutil.copyfile(
os.path.join(self._tmpdir, self._basename), self._filename
)
with open(os.path.join(self._tmpdir, csv_filename)) as csv_file:
# skip first 5 lines of the csv output, contains metadata and fieldnames
for _ in range(0, 5):
next(csv_file)
return [x for x in csv.DictReader(csv_file, fieldnames=fnames)]

@Driver.check_active
def analyze(self, args, filename=None):
annotation_regex = re.compile(
r'(?P<startnum>\d+)-(?P<endnum>\d+) (?P<decoder>[\w\-]+): (?P<annotation>[\w\-]+): (?P<data>".*)'
)
if not filename and self._filename:
filename = self._filename
else:
filename = os.path.abspath(filename)
check_file(filename, command_prefix=self.sigrok.command_prefix)
args.insert(0, filename)
if isinstance(args, str):
args = args.split(" ")
args.insert(0, '-i')
args.append("--protocol-decoder-samplenum")
args.append("-l")
args.append("4")
combined = self._get_sigrok_prefix() + list(args)
output = subprocess.check_output(
self._get_sigrok_prefix() + list(args),
)
return [
match.groupdict()
for match in re.finditer(annotation_regex, output.decode("utf-8"))
]
21 changes: 21 additions & 0 deletions labgrid/remote/exporter.py
Expand Up @@ -180,11 +180,32 @@ def _get_params(self):
'model_id': self.local.model_id,
}

@attr.s(cmp=False)
class USBSigrokExport(USBGenericExport):
"""ResourceExport for USB devices accessed directly from userspace"""

def __attrs_post_init__(self):
super().__attrs_post_init__()

def _get_params(self):
"""Helper function to return parameters"""
return {
'host': gethostname(),
'busnum': self.local.busnum,
'devnum': self.local.devnum,
'path': self.local.path,
'vendor_id': self.local.vendor_id,
'model_id': self.local.model_id,
'driver': self.local.driver,
'channels': self.local.channels
}


exports["AndroidFastboot"] = USBGenericExport
exports["IMXUSBLoader"] = USBGenericExport
exports["MXSUSBLoader"] = USBGenericExport
exports["AlteraUSBBlaster"] = USBGenericExport
exports["SigrokUSBDevice"] = USBSigrokExport


@attr.s
Expand Down
10 changes: 10 additions & 0 deletions labgrid/resource/remote.py
Expand Up @@ -137,3 +137,13 @@ class NetworkAlteraUSBBlaster(RemoteUSBResource):
def __attrs_post_init__(self):
self.timeout = 10.0
super().__attrs_post_init__()

@target_factory.reg_resource
@attr.s(cmp=False)
class NetworkSigrokUSBDevice(RemoteUSBResource):
"""The NetworkSigrokUSBDevice describes a remotely accessible sigrok USB device"""
driver = attr.ib(default=None, validator=attr.validators.instance_of(str))
channels = attr.ib(default=None, validator=attr.validators.instance_of(str))
def __attrs_post_init__(self):
self.timeout = 10.0
super().__attrs_post_init__()
17 changes: 17 additions & 0 deletions labgrid/resource/sigrok.py
@@ -0,0 +1,17 @@
import attr

from ..factory import target_factory
from .common import Resource

@target_factory.reg_resource
@attr.s(cmp=False)
class SigrokDevice(Resource):
"""The SigrokDevice describes an attached sigrok device with driver and
channel mapping
Args:
driver (str): driver to use with sigrok
channels (str): a sigrok channel mapping as desribed in the sigrok-cli man page
"""
driver = attr.ib(default="demo")
channels = attr.ib(default=None, validator=attr.validators.instance_of(str))

0 comments on commit d779cc8

Please sign in to comment.