Protocol decoder API
This page describes how Protocol Decoders (PDs) work in sigrok.
Architecture
The frontend gets input from the user on which PDs to use in an acquisition session. It then configures these into the session with session_pd_add(). As the first PD is added, the session sets up an additional datafeed callback to itself, which it uses as input to the first PD in the stack. The output of that is sent to the frontend, along with its original datafeed, as well as fed into the next PD in the stack.
The frontend thus gets the raw datafeed as well as a feed from every PD in the stack. Which of these different feeds is actually displayed to the user is a matter of configuration or selection by the user; it should be possible, for example, to have sigrok-cli print only the top of the PD stack's output on stdout.
- All PDs are written in Python.
- Every PD registers its name, description, capabilities, etc by populating a hash (dictionary) in its own main namespace called register.
- PDs will be stacked together, so the user can construct a decoding pipeline. The control of communication to/from PDs is done by the PD controller code in libsigrokdecode.
- The data feed into the PDs will be streamed, so they will run in real time as the data comes in from the hardware (or from a file).
- In order to keep PDs simple, they don't have to deal with the intricacies of the datafeed packets. Instead, the PD controller will hide the details from the Python code:
- When receiving a DF_HEADER packet going to a PD, the controller intercepts the packet and instead generates a DF_HEADER "coming from" that PD across the session bus, with that PD's output characteristics (which it derived from the PD's register dictionary).
- Data packets get translated into a bytestream, which the PDs access through an API: the function get() is a blocking call into the controller, which only returns when a datafeed packet has arrived, and its payload queued up for the PD.
- DF_END packets are translated into an EOF on the get() call.
API
A module called sigrok is provided which contains functions and definitions for PDs to use.
- get_meta(): returns a dictionary with meta information about the stream.
The information in this dictionary will not change during the stream; it is essentially fixed from the start. The following keys may be provided:
Key Guaranteed Description driver yes The name of the hardware driver which is feeding this data into the module. When input comes from a sigrok session file, the name of the driver originally used will be given. unitsize yes The size of an item of data, in bytes. This corresponds to the length of each array in the list returned by the get() function. starttime yes The date/time at which the acquisition started, expressed as a floating point number representing seconds since the epoch. Python's time module can be used to do convert this to e.g. a printable string. probes yes A list containing probe names, in the order in which they will be returned from get(). The length of this list corresponds to the exact number of probes that were enabled in the acquisition. samplerate yes The samplerate at which the acquisition is done, specified in Hz.
- get(): returns a dictionary with the following keys:
- time: the time when this set of samples was captured, expressed as an offset in picoseconds since the session's starttime as returned by the meta() function.
- duration: the time it took, in picoseconds, for all the data in this set to be captured.
- data: a list containing items of data, such as samples or output from protocol decoders lower on the stack. Each item consists of an array of type B, which is an unsigned char. The length of this array corresponds with the unitsize value returned by the meta() call.
- put():
This is used to provide the decoded data back into the backend. It takes a single argument, which is in the same form as that returned by the get() function.
Since a frontend uses the time and duration parameters of the various PDs to align their output with each other, it is vital that the time and duration keys be correct. If the PD was not able to handle all the data it received from the get() function, the duration key must be recalculated to take this into account. The next batch of data must then have its time parameter adjusted accordingly.
The data parameter's arrays must be of whatever length is appropriate to this PD's output. In other words, it must match the PD's native unitsize.
Module registration
A PD must contain at least a populated dictionary register which defines the name, capabilities etc of the PD. The following keys can be used:
Key Required Description Protocol Decoder information id yes A short unique identifier for this protocol decoder. It should be all-lowercase, and only contains a-z, 0-9 and underscores. The sigrok-cli tool uses this to specify PDs. desc yes A freeform one-line description of the decoder. Used when listing available PDs. author no Name of the author of this PD. no Email address of the author of this PD. version no Version of this PD. apiversion yes The sigrok API version which this module uses. This is currently 1. license yes The license under which the module is provided. This must be either gplv2+ (meaning the GNU General Public License 2 or later), or gplv3+ (GNU General Public License 2 or later). No other licenses for modules are permitted in sigrok. in yes The type of input this decoder needs. If the decoder takes input from a logic analyzer driver, this should be set to logic, which maps to DF_LOGIC, the datafeed type. If it takes input from another PD, it should be set to the value of the out key of that PD. It should conform to the same rules as the id key (lowercase, no spaces, and so on). out no If this decoder can feed decoded data back into the datafeed stream, its output will be identified with this key's value. It should conform to the same rules as the id key. If not specified, this decoder cannot feed data back into the stream. This typically means it only does analysis on the whole stream, producing a report at the end of acquisition. Available functions start yes Reference to a Python function that will be called to start the decoder. The PD controller will call this function when the first packet with real data in it comes in on the stream. See below for arguments to this function. report no If given, this is a reference to a function which returns a report in freeform text, describing some analysis done on the stream. For example, PDs for counting logic level transitions, bit rate analysis and so on would use this to report back. This function is typically only called at the end of the stream, so the whole stream is analyzed. However, the function may be called at any time -- possibly giving an analysis of data received up until that point. Protocol Decoder configuration probes no This key lists the probes (pins) the hardware should feed to this PD. If this key is specified, the probes in this list MUST be present in the data; the PD will not be able to work without them. For example, an SPI decoder has to know which probe has the clock, which has the chip select, and so on. This key contains a list of probe entries, where each entry can be either a string with the probe name ("SCLK") or a list with the probe name and description, such as ["SCLK", "Clock"]. extra_probes no If present, this is a list of probes the PD can make use of, but are not strictly required. The list is in the same format as that of the probes key, above. options no A dictionary with options for this decoder. The keys should follow the same rules as for the id key above, and each value is a list consisting of a short freeform description of the option, and the default value for that option. For example, an SPI decoder might have an entry with key cpol, and value ['Clock polarity', 0].
Here's an example for an SPI decoder:
register = { 'id': 'spi', 'desc': 'Serial peripheral interface', 'author': 'sigrok project', 'email': 'sigrok-devel@lists.sourceforge.net', 'license': 'gplv3+', 'in': 'logic', 'out': 'spi', 'probes': [ ['sclk', 'Clock, also known as SCK, or CLK'], 'mosi', 'miso', ['ncs', 'Chip Select, also known as CS, NCS, or CE'], ], 'options': { 'cpol': ['Clock polarity', 0], 'cpha': ['Clock phase', 0], 'wordsize': ['Word size (in bits)', 8], }, 'start': spi_start, 'report': report_stats }
The start function is called by libsigrokdecode at the start of a session which has this PD in its pipeline. It should implement a loop calling get() and put(), and only return when an EOF is detected in that loop.
File structure
Code
A protocol decoder always has its own directory, named after the PD's ID, corresponding to the id field in the register dictionary. The directory should have a __init__.py file in it. Like any other python module, this is the file that is responsible for important and organizing the rest of the module's code into the namespace.
The register dictionary is the only symbol that absolutely MUST be in the module's main namespace, since that's where sigrok expects to find it. Here's an example __init__.py:
import foo register = { 'id': 'spi', [...] 'start': foo.start }
Source code and copyright
The module MUST come with source code in the form of .py files. No pre-compiled code should be present, python or otherwise. The module must not use any helpers that are not provided as source code under the same license as the module itself.
The register struct must have a license declaration (see above), stating the license under which all the contents in the module directory are provided.
Example/test files
Every protocol decoder module MUST come with example input and output files, that put the decoder through its paces. They must have the following names (in case of a PD called foo):
- foo.in: example input as it would be received from sigrok, either raw samples from a hardware device, or decoded data from an upstream PD. In other words, the data in this file corresponds in format to the in field of this PD's register struct. Just as with live data in a real session, the module should not assume it will be fed this file at any particular speed, or in same-sized chunks.
- foo.out: this must correspond exactly with what this PD will output, having been fed the input file above. No more, no less.
These two files must be present in the module's main directory.