Nan in current and voltage samples

Hi JouleScope Forum,

I just got a JouleScope for energy measurement tasks. At the moment I’m using the JouleScope in a similar way as using an oscilloscope: start capture → wait for a certain amount of time → stop capture → read samples.

After conducting a few energy measurement tests, I notice that sometimes the JouleScope would give me varying amounts of nan in current and voltage readings. Can anyone help me with the following issues:

  1. nan in sample readings
  2. find out what does “in frame_id mismatch xxx!= 0” mean

This is some information of an example test script and system info:
example test script:

import joulescope
from joulescope.v1.js220 import DeviceJs220
import numpy as np
from time import sleep
from numpy.typing import NDArray


OSC_FREQUENCY = 5000
TIME_WINDOW = 1200
for _i in range(20):
    print(f"Test iteration {_i + 1}")

    joulescope_device: DeviceJs220 = joulescope.scan_require_one(
        name="joulescope", config="auto"
    )
    print(f"JouleScope {joulescope_device.model} is connected.")

    joulescope_device.open()

    # Set configuration
    joulescope_device.parameter_set("sampling_frequency", int(OSC_FREQUENCY))
    assert (
        int(joulescope_device.parameter_get("sampling_frequency", "actual"))
        == OSC_FREQUENCY
    )
    joulescope_device.parameter_set("buffer_duration", int(TIME_WINDOW))
    assert (
        int(joulescope_device.parameter_get("buffer_duration", "actual")) == TIME_WINDOW
    )

    joulescope_device.start(contiguous_duration=TIME_WINDOW)
    print("Started data streaming.")

    if not joulescope_device.is_streaming:
        print("JouleScope is not streaming after start!")
        break

    print("Waiting for 1190 seconds.")
    sleep(1190)

    joulescope_device.stop()
    print("Stop data streaming and get samples.")

    (
        starting_sample_id,
        ending_sample_id,
    ) = joulescope_device.stream_buffer.sample_id_range
    samples = joulescope_device.stream_buffer.samples_get(
        starting_sample_id, ending_sample_id
    )
    statistics = joulescope_device.stream_buffer.statistics_get(
        starting_sample_id, ending_sample_id
    )

    current: NDArray[np.float32] = samples["signals"]["current"]["value"]
    voltage: NDArray[np.float32] = samples["signals"]["voltage"]["value"]
    nan_current = np.sum(np.isnan(current))
    nan_voltage = np.sum(np.isnan(voltage))

    if nan_current > 0 or nan_voltage > 0:
        print(f"Got {nan_current} nan in current values.")
        print(f"Got {nan_voltage} nan in voltage values.")
        print("Fail to fetch complete waveform")

    joulescope_device.close()
    print("JouleScope is closed.")

snapshot of test result:

Test iteration 1
JouleScope js220 is connected.
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Got 5598 nan in current values.
Got 5598 nan in voltage values.
Fail to fetch complete waveform
JouleScope is closed.
Test iteration 2
JouleScope js220 is connected.
in frame_id mismatch 983 != 0
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Got 1025 nan in current values.
Got 1025 nan in voltage values.
Fail to fetch complete waveform
JouleScope is closed.
Test iteration 3
JouleScope js220 is connected.
in frame_id mismatch 17226 != 0
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Got 873 nan in current values.
Got 873 nan in voltage values.
Fail to fetch complete waveform
JouleScope is closed.

a.t.m I’m sticking with Python 3.8 so I chose to install joulescope version 1.1.6

python -m joulescope info
System information
Python: 3.8.10 (default, May 26 2023, 14:05:08)
[GCC 9.4.0]
Platform: Linux-5.15.0-75-generic-x86_64-with-glibc2.29 (linux)
Processor: x86_64
executable: /home/allan/.cache/pypoetry/virtualenvs/testing-S71WflWI-py3.8/bin/python
frozen: False

joulescope version: 1.1.6
Found 1 connected Joulescope:
JS220-000882 ctl=1.0.4 sensor=1.0.3

Hi @yfen44 and welcome to the Joulescope forum!

Do you know if these are always the starting or ending samples? One thing I notice is that buffer_duration is set to the same value as contiguous_duration. The buffer_duration should be able to store at least a few seconds more of data, which could possibly explain the NaNs. You can try setting buffer_duration to int(TIME_WINDOW + OSC_FREQUENCY * 3).

On a separate note, I am a fan of separating recording and analyzing. This allows you to keep your historical data and improve your analysis later. With this approach, you would record to file (like JLS), then load and analyze the JLS.

Here’s an example that does everything together:

# Copyright 2023 Jetperch LLC
#
# 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.


"""
Record Joulescope data and analyze.

To configure your computer:
1. Install python on your host computer
2. python -m pip install -U --upgrade-strategy eager pyjls pyjoulescope_driver numpy matplotlib

To run this script:
python record_analyze.py
"""


from pyjls import Reader, SignalType
from pyjoulescope_driver.__main__ import run as jsdrv_run
from pyjoulescope_driver import time64
import argparse
import matplotlib.pyplot as plt
import numpy as np


def get_parser():
    p = argparse.ArgumentParser(
        description='Record Joulescope data and analyze.',
    )
    p.add_argument('--duration',
                   type=time64.duration_to_seconds,
                   default=4.0,
                   help='The capture duration in float seconds. '
                        + 'Add a suffix for other units: s=seconds, m=minutes, h=hours, d=days')
    p.add_argument('--frequency', '-f',
                   type=int,
                   default=10000,
                   help='The sampling frequency in Hz.')
    p.add_argument('--signals',
                   default='current,voltage,power',
                   help='The comma-separated list of signals to capture which include '
                        + 'current, voltage, power.'
                        + 'Defaults to current,voltage,power.')
    p.add_argument('filename',
                   nargs='?',
                   default=time64.filename(),
                   help='The JLS filename to record. '
                        + 'If not provided, construct filename from current time.')
    return p


def record(args):
    # Record Joulescope data to a JLS file
    jsdrv_run(args=[
        'record', 
        '--frequency', str(args.frequency),
        '--duration', str(args.duration),
        '--signals', args.signals,
        args.filename])
    return args.filename, args.signals.split(',')


def load(filename, signal_names):
    # Load the recorded data
    data = {}
    with Reader(filename) as r:
        signals = []
        
        # time align signals (necessary for JS220.  JS110 already aligned, so does nothing)
        for signal_name in signal_names:
            s = r.signal_lookup(signal_name)
            print(f'signal {s.name}: offset={s.sample_id_offset}, length={s.length}, sample_rate={s.sample_rate}')
            signals.append(s)
        sample_id_offset = max([s.sample_id_offset for s in signals])
        length = min([s.sample_id_offset + s.length - sample_id_offset for s in signals])
        frequencies = [s.sample_rate for s in signals]
        if min(frequencies) != max(frequencies):
            raise RuntimeError(f'frequency mismatch: {frequencies}')
        frequency = min(frequencies)

        # extract the data for each signal
        x = np.arange(length, dtype=float) * (1.0 / frequency)
        for s in signals:
            data[s.name] = {
                'x': x,
                'y': r.fsr(s.signal_id, sample_id_offset - s.sample_id_offset, length),
                'name': s.name,
                'units': s.units,
            }
    return data


def analyze(data):
    # Perform custom analysis - plot and show the data
    f = plt.figure()
    ax1, ax = None, None
    for idx, d in enumerate(data.values()):
        ax = f.add_subplot(len(data), 1, idx + 1, sharex=ax1)
        if ax1 is None:
            ax.set_title('Analysis')
            ax1 = ax
        ax.grid(True)
        ax.set_ylabel(f'{d["name"]} ({d["units"]})')
        ax.plot(d['x'], d['y'])
    if ax is not None:
        ax.set_xlabel('Time (seconds)')
    plt.show()


def run(args=None):
    args = get_parser().parse_args(args)
    filename, signal_names = record(args)
    data = load(filename, signal_names)
    analyze(data)


if __name__ == '__main__':
    run()

The in frame_id mismatch comes from here. This mismatch is expected once and only once when you open a JS220. We should probably add some code to prevent the warning on the first input frame. If you see this warning again, then it means samples were dropped, usually because the host computer decided to do something else (antivirus, system backup, updates) for a while rather than servicing USB.

Thanks @mliberty , I have tried your suggestion of having a buffer_duration larger than contiguous_duration, but unfortunately, Nan still exists in samples. I also investigated Nan’s location, and they appeared in different spots pretty randomly.
The fact that 9/20 tests contained missing samples made me frastrating, am I using the JouleScope in the wrong way?
Here is the output of the updated test, which displays the location of Nan:

JouleScope js220 is connected.
Test iteration 1
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950280, corresponds to 1190.056 seconds.
Got 5489 nan in current values.
Rough location of Nan in current array:
----------------------------------------------------------NN–N-------------------------------------
Got 5489 nan in voltage values.
Rough location of Nan in voltage array:
----------------------------------------------------------NN–N-------------------------------------
Test iteration 2
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950257, corresponds to 1190.0514 seconds.
Got 7313 nan in current values.
Rough location of Nan in current array:
--------------------------------------------------------------------------N-N--------------N------N-
Got 7314 nan in voltage values.
Rough location of Nan in voltage array:
--------------------------------------------------------------------------N-N--------------N------N-
Test iteration 3
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950227, corresponds to 1190.0454 seconds.
Got 5518 nan in current values.
Rough location of Nan in current array:
N---------------------------------------------------------------------------------------------------
Got 5519 nan in voltage values.
Rough location of Nan in voltage array:
N---------------------------------------------------------------------------------------------------
Test iteration 4
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950288, corresponds to 1190.0576 seconds.
Got 908 nan in current values.
Rough location of Nan in current array:
--------------------------------------------------------------------------------N-------------------
Got 907 nan in voltage values.
Rough location of Nan in voltage array:
--------------------------------------------------------------------------------N-------------------
Test iteration 5
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950328, corresponds to 1190.0656 seconds.
Got 5675 nan in current values.
Rough location of Nan in current array:
--------------------------------------------------------------------------NN------------------------
Got 5676 nan in voltage values.
Rough location of Nan in voltage array:
--------------------------------------------------------------------------NN------------------------
Test iteration 6
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950223, corresponds to 1190.0446 seconds.
Test iteration 7
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950173, corresponds to 1190.0346 seconds.
Test iteration 8
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950127, corresponds to 1190.0254 seconds.
Got 79 nan in current values.
Rough location of Nan in current array:
----------------------------------------------------------------------------------------N-----------
Got 79 nan in voltage values.
Rough location of Nan in voltage array:
----------------------------------------------------------------------------------------N-----------
Test iteration 9
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950248, corresponds to 1190.0496 seconds.
Got 81 nan in current values.
Rough location of Nan in current array:
--------------------------------------------------------------------------N-------------------------
Got 81 nan in voltage values.
Rough location of Nan in voltage array:
--------------------------------------------------------------------------N-------------------------
Test iteration 10
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950202, corresponds to 1190.0404 seconds.
Test iteration 11
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950329, corresponds to 1190.0658 seconds.
Test iteration 12
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950223, corresponds to 1190.0446 seconds.
Test iteration 13
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950203, corresponds to 1190.0406 seconds.
Test iteration 14
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950334, corresponds to 1190.0668 seconds.
Got 83 nan in current values.
Rough location of Nan in current array:
----------------------NN----------------------------N-----------------------------------------------
Got 83 nan in voltage values.
Rough location of Nan in voltage array:
----------------------NN----------------------------N-----------------------------------------------
Test iteration 15
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
Got 93 nan in current values.
Rough location of Nan in current array:
N---------------------------------------------------------------------------------------------------
Got 92 nan in voltage values.
Rough location of Nan in voltage array:
N---------------------------------------------------------------------------------------------------
Test iteration 16
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950203, corresponds to 1190.0406 seconds.
Test iteration 17
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950200, corresponds to 1190.04 seconds.
Test iteration 18
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950204, corresponds to 1190.0408 seconds.
Test iteration 19
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950223, corresponds to 1190.0446 seconds.
Test iteration 20
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950223, corresponds to 1190.0446 seconds.
JouleScope is closed.

Here is the updated test script just for reference:

import joulescope
from joulescope.v1.js220 import DeviceJs220
import numpy as np
from time import sleep
from math import ceil, floor
from numpy.typing import NDArray


def display_nan_location(array_values: NDArray[np.float32], array_name: str) -> None:
    nan_indices = np.where(np.isnan(array_values))[0]
    nan_number = len(nan_indices)

    if nan_number > 0:
        print(f"Got {nan_number} nan in {array_name} values.")

        # Display Nan location with resolution of 1%
        array_partition_number = 100

        nan_location_list = ["-"] * array_partition_number
        for partition_index, _character in enumerate(nan_location_list):
            starting_position = floor(
                len(array_values) / array_partition_number * partition_index
            )
            ending_position = floor(
                len(array_values) / array_partition_number * (partition_index + 1)
            )
            for nan_index in nan_indices:
                if nan_index >= starting_position and nan_index < ending_position:
                    nan_location_list[partition_index] = "N"
                    break

        print(f"Rough location of Nan in {array_name} array:")
        print("".join(nan_location_list))


OSC_FREQUENCY = 5000
TIME_WINDOW = 1200


joulescope_device: DeviceJs220 = joulescope.scan_require_one(
    name="joulescope", config="auto"
)
print(f"JouleScope {joulescope_device.model} is connected.")

joulescope_device.open()
for _i in range(20):
    print(f"Test iteration {_i + 1}")

    # Set configuration
    joulescope_device.parameter_set("sampling_frequency", int(OSC_FREQUENCY))
    assert (
        int(joulescope_device.parameter_get("sampling_frequency", "actual"))
        == OSC_FREQUENCY
    )
    joulescope_device.parameter_set("buffer_duration", int(TIME_WINDOW + 100))
    assert int(joulescope_device.parameter_get("buffer_duration", "actual")) == (
        TIME_WINDOW + 100
    )

    joulescope_device.start(contiguous_duration=TIME_WINDOW)
    print("Started data streaming.")

    if not joulescope_device.is_streaming:
        print("JouleScope is not streaming after start!")
        break

    print(f"Waiting for {int(TIME_WINDOW - 10)} seconds.")
    sleep(int(TIME_WINDOW - 10))

    joulescope_device.stop()
    print("Stop data streaming and get samples.")

    (
        starting_sample_id,
        ending_sample_id,
    ) = joulescope_device.stream_buffer.sample_id_range
    samples = joulescope_device.stream_buffer.samples_get(
        starting_sample_id, ending_sample_id
    )
    statistics = joulescope_device.stream_buffer.statistics_get(
        starting_sample_id, ending_sample_id
    )

    current: NDArray[np.float32] = samples["signals"]["current"]["value"]
    voltage: NDArray[np.float32] = samples["signals"]["voltage"]["value"]
    current_sample_number = len(current)
    voltage_sample_number = len(voltage)
    assert current_sample_number == voltage_sample_number

    print(
        f"Number of samples: {current_sample_number}, corresponds to "
        f"{current_sample_number / OSC_FREQUENCY} seconds."
    )

    display_nan_location(current, "current")
    display_nan_location(voltage, "voltage")

joulescope_device.close()
print("JouleScope is closed.")

Hi @yfen44 , and thanks for trying to track this down. I really like your simple NaN location display!

I have been running this on my Windows computer, and it seems to work great:

python .\nan.py
JouleScope js220 is connected.
in frame_id mismatch 0 != 52179
Test iteration 1
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949678, corresponds to 1189.9356 seconds.
Test iteration 2
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949683, corresponds to 1189.9366 seconds.
Test iteration 3
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949683, corresponds to 1189.9366 seconds.
Test iteration 4
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949679, corresponds to 1189.9358 seconds.
Test iteration 5
Started data streaming.
Waiting for 1190 seconds.

I just started the same on an Ubuntu machine running on a 5-year-old Intel NUC. I’ll let this run for a while and post the results.

In general, the host machine needs to be available and continue processing the streaming data. Anything that blocks the host machine or maxes out the CPU can starve the Joulescope streaming data processing. This can result in dropped samples that are represented as NaN. On Windows, things like anti-virus scan, backups, greedy above-priority processes, out-of-memory, memory paging, and some poorly written programs (like Dell’s update service), can cause sample drops. On Linux, things tend to work better.

  1. Do you still see these drops if you close all other applications, shut down any unnecessary services, and only run this capture?

  2. Are you running this capture natively (no VM)?

Hi @mliberty , thanks for your quick reply. I’m developing my test suites in VMware and had the JouleScope tested in VMware, but eventually, the test suites will be running in an Intel NUC (I think it runs Ubuntu natively).

Is there a workaround for supporting and testing the JouleScope in VM, before I officially put my work in the NUC?

Hi @yfen44

As you can see, the Joulescope host software mostly works inside a VM. The challenge is ensuring that the VM keeps setting up the USB Bulk IN transactions needed to convey the Joulescope’s measured data. The JS220 has limited buffering, so USB must continue to work.

I am not sure what is causing the VM to have glitches in USB servicing. If you want to try to figure out a way to make this run better, you have two approaches:

  1. Ensure that the VM keeps on issuing the USB Bulk IN transactions. There may be something that is blocking the VM from timely servicing of the USB traffic.
  2. Reduce the JS220 data rate. Unfortunately, downsampling is still performed host-side with the JS220. We do plan to move downsampling to the FPGA. So, for now, the only way to reduce the data rate is to disable signals. So, you could try only streaming current, no voltage, power, GPI, Trigger.

For what it’s worth, the test ran perfectly on my Intel NUC with Ubuntu:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.2 LTS
Release:        22.04
Codename:       jammy
$ python nan.py
JouleScope js220 is connected.
Test iteration 1
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
Test iteration 2
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950208, corresponds to 1190.0416 seconds.
Test iteration 3
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 4
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 5
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 6
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950204, corresponds to 1190.0408 seconds.
Test iteration 7
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 8
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950204, corresponds to 1190.0408 seconds.
Test iteration 9
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950204, corresponds to 1190.0408 seconds.
Test iteration 10
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950207, corresponds to 1190.0414 seconds.
Test iteration 11
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 12
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
Test iteration 13
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950204, corresponds to 1190.0408 seconds.
Test iteration 14
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
Test iteration 15
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949957, corresponds to 1189.9914 seconds.
Test iteration 16
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950205, corresponds to 1190.041 seconds.
Test iteration 17
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
Test iteration 18
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950081, corresponds to 1190.0162 seconds.
Test iteration 19
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5949829, corresponds to 1189.9658 seconds.
Test iteration 20
Started data streaming.
Waiting for 1190 seconds.
Stop data streaming and get samples.
Number of samples: 5950206, corresponds to 1190.0412 seconds.
JouleScope is closed