Skip to main content

Quick'n dirty view / save utility to retrieve and store waveforms from SCPI compliant devices

Project description

OsziTrace -- EPICS IOC for Retrieving Oscilloscope Traces

[[TOC]]

Overview

Oszitrace exposes the data traces (i.e. digitized signals) of an attached oscilloscope. For a supported device access to the main data is offered for each channel, as well as a device-wide trigger arming functionality.

The IOC is deliberately kept simple and does not go into details about oscilloscope configuration. The generat strategy if you require specific setup and behavior control to pass an experiment-specific initialization talk routine to the IOC on startup.

We advise you to consider keeping a number of those in stock for your most common usage patterns.

Quick Start

Obtaining


There are three ways how you can get Oszitrace up & running at your site:

  • You can download it straigt off PyPI and run it from the command line:

    $ pip install oszitrace-ioc
    $ oszitrace-ioc [...]
    
  • You can download it off Gitlab and install it locally:

    $ git clone oszitrace
    $ pip install -e ./oszitrace
    $ oszitrace-ioc [...]
    
  • ...or you can use the pre-built image off Gitlab with Docker or Podman:

    $ podman run -ti --rm registry.gitlab.com/kmc3-xpp/oszitrace:latest [...]
    

Running & Customizing

Most of Oszitrace's behavior customized through environment variables. Currently there are no command line parameters.

Here is a list of variables that Oszitrace currently reacts to:

  • OSZI_EPICS_PREFIX the EPICS prefix the IOC will use
  • OSZI_NAME the "name" of the oscilloscope device; Oszitrace is, in principle, designed to run many different instances within the same network, if you have the need for it. By default, the EPICS prefix of each instance will be of the form {prefix}:{name}, with {prefix} being used from OSZI_EPICS_PREFIX (see above), and {name} being the last component of the module providing the dialogue object. Sometimes this is not enough, though -- e.g. when you have several devices of the same kind that you want to use. OSZI_NAME allows you to override the {name} component of the prefix.
  • OSZI_INIT_TALK this should contain the path of an init-talk file that will be fed to the device upon IOC startup.
  • OSZI_DIALOGUE this should contain a module / class specification of the form [oszitrace.]{module}[:{class}]. Here, the {module} part refers to a Python module available within the system for Oszitrace to use. If {module} is a single-name (i.e. contains no dot .), and is not found, oszitrace.{module} is also tried (e.g. oszitrace.lecroy instead of just lecroy). The dialogue object within that module is expected to be called Dialogue, and be compatible with the Dialogue device access API. This can be overridden by the {class} part of the dialogue specification. For example: OSZI_DIALOGUE=oszitrace.lecroy:LecroyDialogue.
  • OSZI_LOG_LEVEL one of "ERROR", "WARNING", "INFO" or "DEBUG".

There are also variables intended for specific dialogue types (e.g. for the built-in simulator.

The EPICS IOC Interface

Oszitrace exports the following EPICS variables:

  • {prefix}:{name}:arm if anything is written here, and the device is in any kind of triggered-mode, the trigger is armed and ready for a new measurement.
  • {prefix}:{name}:{channel}:signal the oscilloscope sampling data (a floating-point array)
  • {prefix}:{name}:{channel}:xaxis the axis that corresponds to the dataw (also as a floating-point array)
  • {prefix}:{name}:{channel}:xoffset (...also available: ...:xdelta and ...:xreach): these are the essential values that make up the X-axis data (xoffset being the starting point, xdelta the axis increment, and xreach, as a courtesy, the last point on the axis i.e. xoffset+xdelta*<num_pts>).

The {} format variables above have the following meaning:

  • {prefix} the IOC base prefix, specified as OSZI_EPICS_PREFIX
  • {name} the IOC device name (see above)
  • {channel} the name of the device channel; this is highly implementation specific, but we encourage, and indeed most built-in devices have, a scheme akin to ch{N}, where {N} is a channel index. Note that some devices may (or not) export additional data, e.g. an FFT array, with a different designation (e.g. ch1 and ch1fft).

Diving Deeper

The Built-in Simulator

For testing and the-fun-of-things purposes, Oszitrace has a built-in signal simulator. It will enable the IOC to be run without an attached device, and will provide a number of channels with the exact same functionality as a "real" oscislloscipe, the only difference being that the signal will be a simulated, running sin() wave.

Specific environment variables for the built-in simulator:

  • OSZI_SIM_TRIGGERED if set to "yes", the simulator will only deliver a signal after it has been triggered.

Initialization Talk

Generically, independently of the device type, an "initalization talk" object can be specified. This should be a YAML-compatible file, essentially like this:

type: oszitrace-ioc/ascii-talk
version: 0.1
init:
- q: "*IDN?"
  r: "{}Agilent Version {version}{}"
- q: ":WAV:FORM ASC"
- q: ":WAV:STR  ON"

In words: the object must be a list ([]) which contains dictionaries, and each dictionary MUST contain a q element, and MAY contain a r element -- each as strings.

The q element will be sent verbatim to the device over whichever means of communication is employed (e.g. PyVISA or something different).

If an r element exists, then an answer is expected, and the answer is parsed against r as a format string. The usual Python formats ({}, {name}, ...) are accepted. If the format parameter is named, it will be stored in an internal dictionary for later reference (see .parameters in the device access API).

If the r element does not exist, then the devices MUST NOT answer, otherwise this will be treated as an error.

Device Access API

Device access is facilitated by a Python object (a "dialogue" object), which is expected to meet the following API; you can use this as a stub to get started from, if you intend to support your own hardware:

class Dialogue:

    def __init__(self, env=None, ...):
	    ...
		
	async def init(self, init_chat=None):
	    ...
	
	async def retr_channel(self, channel, timeout=0.0):
	    ...
	
	async def arm_trigger(self):
	    ...
		
	@property
	def channels(self):
	    ...
		
	@property
	def parameters(self):
	    ...
	
  • .__init__(): initialization should work without parameters. This function must not block or hesitate, i.e. please move all device communication either in a working thread, or use the rest of the async API elements. However, .__init__() will receive a list of environment variables to use via the env={...} parameter. The class is free to use other environment variable sources (e.g. os.environ), but is discouraged from doing so. The env=... parameter will actually contain the system env-vars from os.environ if the circumstances didn't explicitly ask for a different set.

  • async def init(...): this is called once, after the asyncio loop has been etablished, to give the Dialogue class occasion to perform initial device communication and setup. If init_chat is specified, it's expected to be an init-talk list object.

  • async def retr_channel(...): this waits until either data for the specified channel is available, or timeout seconds have elapsed. If there is no data for whatever reason (either because timeout has occurred, or because oscilloscopes are generally tricky and sometimes unreliable), then a DialogueRetry exception must be raised -- either a DialogueTimeoutRetry or a DialogueEmptyRetry, depending on the reason. The rest of Oszitrace is designed to deal with that gracefully, without going into failure mode.

    If the intention is to fail (i.e. exit the IOC because a more serious reason is at play, e.g. loss of connectivity), then a different exception must be raised (e.g. RuntimeError, a DialogueError, or whatever the underlying communication mechanism raises).

    The data must be returned as an xarray.DataArray, with built-in scaling, both axis and signal as floating-point data type.

  • async def arm_trigger(): this function is supposed to arm the trigger for a new signal acquisition if the device is any kind of triggered mode.

  • property channels: this is a list of strings, each string representing the name of one device data channel. These are to be used as parameters, for instance, to .retr_channel() calls.

  • property parameters: this is provided as a courtesy to the user, and is expected to be a dictionary where the keys are named parse parameters from the init-talk r sections (see above), and the data is the data that has been extracted.

    Oszitrace (currently) does not use any of this information, and there aren't any specific keys that are expected or treated in a particular way -- everything we care about in terms of signal information (scaling, offsets, axis, ...) is expected to already be baked into the xarray that .retr_channel() is returning.

    But as Oszitrace API is still young, this may change in the future (though at this point it isn't clear how this would be beneficial, and how this could -- generically -- reach the IOC interface.)

Supported Devices

Here's a list of devices that Oszitrace supports our of the box (you can use any of these as a value for the OSZI_DIALOGUE env-var):

  • sim -- see above, a simulated device which delivers sine waves for testing.

  • lecroy -- tested on (?), but generally Lecroy devices have a fairly sophisticated discovery langauge built-in which describe essential parts of their internal workings and formats. Oszitrace goes a long way in automatically interpreting that language and adapting to the specific model, so chances are that a wide range of models are able to function out-of-the-box.

    Binary readout is enabled, so Oszitrace is able to rapidly read large amounts of data (4 channels, > 1 mio data points each, readout time in the range of a fraction of a second).

  • agilent -- tested on (?). Only ASCII readout supported via the :WAV:DATA? SCPI command. 4-channel, 150k-ish points readout performed within ~1 second.

Adding support for additional devices should be fairly easy.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oszitrace_ioc-0.2.0.tar.gz (43.5 kB view hashes)

Uploaded Source

Built Distribution

oszitrace_ioc-0.2.0-py3-none-any.whl (39.6 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page