Getting started
This guide shows the basic lifecycle for the Joulescope driver in Python
(pyjoulescope_driver) and in C (joulescope_driver): find a device, open it,
configure it, receive statistics and streaming samples, and shut down
cleanly. It applies to the JS110, JS220 and JS320. The driver also has
Node.js bindings in node_api/, which this guide does not cover.
The payload formats for statistics and streaming samples are in streaming_topics.md. The per-model topic references are js220.txt and js320.json.
Install
Python:
python -m pip install pyjoulescope_driver
python -m pyjoulescope_driver scan
On Linux, also install the udev rules described in the
README. scan lists the connected device paths.
C: build the static library, the jsdrv command-line tool and the examples
with CMake as described in the README. example/quickstart.c is the C
version of this guide and builds as jsdrv_quickstart.
Concepts
Publish-subscribe: every setting, command and measurement is a topic with a value. Set a value with publish, read a retained value with query, and receive updates with subscribe.
Device path:
{backend}/{model}/{serial_number}, such asu/js320/8W2A. Device topics are{device_path}/{topic}, such asu/js320/8W2A/s/i/range/mode. The driver lists every USB device it recognizes, including non-Joulescope devices such asu/mb/93NP, so select by brand or model.Retained and command topics: a topic whose last segment starts with
!is a command or a data stream and is not retained. All other topics retain their last value. Subscribing with the retain flag delivers the retained value immediately.Topic prefixes:
h/host-side settings such as the sample rate,s/sensor streaming and ranging,c/controller, and@/driver and device lifecycle. The full list for a connected device:python -m pyjoulescope_driver metadata.Open modes:
defaultspushes the host’s retained values, else the metadata defaults, to the device.restorereads the device’s current state into the host and leaves it unchanged.rawis for firmware update only.Callbacks run on the driver thread. Keep them short and copy the data out. A driver call from a callback cannot wait, so the driver forces it asynchronous whatever its timeout: publish, subscribe and unsubscribe return at once and report errors only to the log, and query returns zeros.
jsdrv_is_driver_thread()andDriver.is_driver_thread()tell the two cases apart.Finalize: each Python
Drivermust be finalized, by the context manager orfinalize(), and each C context must be passed tojsdrv_finalize(). Finalize stops the driver threads and closes the devices that are still open.
Python
The program below measures for two seconds on the first Joulescope. It works on all three models.
import time
from pyjoulescope_driver import Driver
def on_statistics(topic, value):
# value is a nested dict, see doc/streaming_topics.md
i = value['signals']['current']['avg']['value']
v = value['signals']['voltage']['avg']['value']
print(f'{i:.6f} A, {v:.3f} V')
def on_current(topic, value):
# value['data'] is a numpy float32 array in amperes
print(f"{len(value['data'])} samples from sample_id {value['sample_id']}")
with Driver() as d:
# Select by brand, or by spec such as 'js320', '8W2A' or 'js320-8W2A'.
device_path = d.find_one_device(brand='joulescope')
print(f'Using {device_path}: model={device_path.model}')
with d.open(device_path, mode='defaults') as device:
# Topics passed to device are relative to device_path.
if device_path.model == 'js110':
device.publish('s/i/range/select', 'auto')
else:
device.publish('s/i/range/mode', 'auto')
for signal in ['i', 'v', 'p']:
device.publish(f's/{signal}/ctrl', 1) # enable streaming
device.publish('s/stats/scnt', 500_000) # samples per block
device.publish('s/stats/ctrl', 1) # enable statistics
device.subscribe('s/stats/value', 'pub', on_statistics)
device.subscribe('s/i/!data', 'pub', on_current)
time.sleep(2.0)
# Leaving the block unsubscribes and closes the device.
# Leaving the block finalizes the driver.
The same operations with absolute topics:
d.publish(f'{device_path}/s/i/range/mode', 'auto')
d.subscribe(f'{device_path}/s/stats/value', 'pub', on_statistics)
value = d.query(f'{device_path}/s/i/range/mode')
Topic values accept the metadata option names, such as 'auto', or the
underlying numeric value. Boolean topics accept 1 and 0.
python -m pyjoulescope_driver values --device js320 prints every retained
value for a device, and metadata prints each topic’s type, default and
options.
See the Driver, DeviceContext and SubscribeContext docstrings for the
full API, and pyjoulescope_driver/entry_points/ for complete programs.
C
example/quickstart.c is the complete, buildable program. It uses only
the public headers and performs these steps:
#include "jsdrv.h"
#include "jsdrv/union.h"
#include "jsdrv/topic.h"
#include "jsdrv/os_thread.h"
struct jsdrv_context_s * context = NULL;
jsdrv_initialize(&context, NULL, JSDRV_TIMEOUT_MS_INIT);
// List the devices: a comma-separated string of device paths.
char devices[1024];
struct jsdrv_union_s list = jsdrv_union_str(devices);
list.size = sizeof(devices);
jsdrv_query(context, JSDRV_MSG_DEVICE_LIST, &list, JSDRV_TIMEOUT_MS_DEFAULT);
// Open, configure and subscribe. Build each topic from the device path.
jsdrv_open(context, device, JSDRV_DEVICE_OPEN_MODE_DEFAULTS, JSDRV_TIMEOUT_MS_DEFAULT);
struct jsdrv_topic_s t;
jsdrv_topic_set(&t, device);
jsdrv_topic_append(&t, "s/i/range/mode");
jsdrv_publish(context, t.topic, &jsdrv_union_cstr_r("auto"), JSDRV_TIMEOUT_MS_DEFAULT);
jsdrv_topic_set(&t, device);
jsdrv_topic_append(&t, "s/stats/ctrl");
jsdrv_publish(context, t.topic, &jsdrv_union_u8_r(1), JSDRV_TIMEOUT_MS_DEFAULT);
jsdrv_topic_set(&t, device);
jsdrv_topic_append(&t, "s/stats/value");
jsdrv_subscribe(context, t.topic, JSDRV_SFLAG_PUB, on_statistics, user_data,
JSDRV_TIMEOUT_MS_DEFAULT);
jsdrv_thread_sleep_ms(2000);
jsdrv_close(context, device, JSDRV_TIMEOUT_MS_DEFAULT);
jsdrv_finalize(context, 0);
Subscriber callbacks receive a struct jsdrv_union_s. Statistics and
streaming payloads are binary with value->app set to the payload type.
Cast value->value.bin to struct jsdrv_statistics_s or
struct jsdrv_stream_signal_s as described in
streaming_topics.md.
Link against the jsdrv CMake target. include/jsdrv.h documents the
API, and example/jsdrv/ contains the larger jsdrv tool.
Command-line tools
Command |
Purpose |
|---|---|
|
List the connected device paths. |
|
Driver, package and device summary. |
|
Every retained value for one device. |
|
Topic types, defaults and options. |
|
Set a value. |
|
Print statistics. |
|
Capture to a JLS file. |
|
Update JS220 or JS320 firmware. |
|
The C equivalents. |
Each command accepts --help. Most take --device (-d) with a device
path, backend/model such as u/js320/, model, serial number or
model-serial_number, and the generic
commands also take --brand.
Model differences
JS110 |
JS220 |
JS320 |
|
|---|---|---|---|
Native sample rate |
2 MHz |
1 MHz |
1 MHz |
Sample rate topic |
|
|
|
Current range topic |
|
|
|
Range after |
auto |
off |
off |
Statistics |
|
|
|
|
2 Msps samples |
1 Msps samples |
1 Msps samples |
|
yes |
yes |
no |
The JS220 and JS320 open with the current range off, so publish
s/i/range/mode auto (or a fixed range) before expecting current
readings. Before 2.5.0, a JS220 defaults open did not push anything,
so the instrument kept the settings from the previous session until it
was power cycled. Use restore to keep the instrument’s settings.
The JS220 and JS320 compute statistics on the instrument. The JS110
s/sstats/value is on-instrument at a fixed 2 Hz without standard
deviation. For host-side JS110 statistics, enable s/i/ctrl, s/v/ctrl
and s/p/ctrl, then s/stats/ctrl, as the example above does.
Pitfalls
A callback cannot wait for a result. Callbacks run on the driver thread, which completes every command, so the driver forces commands issued from a callback to
timeout=0(JSDRV_TIMEOUT_MS_ASYNC). A publish from a callback works, but its error is only logged, and a query returns zeros. Queue work that needs a result to another thread.A C timeout of 0 means asynchronous for
jsdrv_publish,jsdrv_subscribe,jsdrv_unsubscribe,jsdrv_openandjsdrv_close, but means the default timeout forjsdrv_initialize,jsdrv_queryandjsdrv_finalize. UseJSDRV_TIMEOUT_MS_DEFAULTto block.Finalize is mandatory. A
Driverthat is never finalized leaves threads running and devices open. Usewith Driver() as d:.The JS220 and JS320 current range defaults to off. See the table above.
defaultsvsrestore.defaultsoverwrites the device configuration. Userestoreto observe a device that another process or the user configured, then set only what you need.Device paths are not only Joulescopes.
device_paths()returns every recognized device. Passbrand='joulescope'or a model.recordneeds pyjls, which is not a declared dependency:python -m pip install pyjls.Time is time64: a signed 64-bit integer with 2^30 ticks per second since 2018-01-01 00:00:00 UTC. Convert with
pyjoulescope_driver.time64orinclude/jsdrv/time.h.Linux permissions. Without the udev rules,
scanlists nothing.Multiple Joulescopes make
find_one_device(brand='joulescope')raiseDeviceFilterError. Pass a serial number or model.Closing a removed device raises in Python and returns an error in C. Subscribe to
@/!remove, or useDriver.device_watch, to track removal.