Get actual input voltage

Hi,
I have a measurement loop:

  • get battery voltage
  • capture
  • analyze

for that I just need 1 single value of the input voltage(and if there are intermittend “outages” I would handle that myself).
In the capture and analyze loop I have changed from joulescope package to pyjoulescope_driver.
To get the battery voltage I have found multiple solutions:

I would prefer a simple method call if possible. Is there any I haven’t found?

Hi @lukGWF - The pyjoulescope_driver code does not have a “read present value” call. However, it is not hard to implement. How about something like this:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright 2024 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.

"""Implement an at-will block statistics read for pyjoulescope_driver.
"""

from pyjoulescope_driver import Driver
from queue import Queue, Empty
import sys
import time


class Measure:

    def __init__(self):
        self._queue = Queue()
        self._previous = None
        self._capture = True
        
    def on_statistics(self, topic, value):
        self._previous = value
        if self._capture:
            self._queue.put(value)
     
    def value(self):
        return self._previous

    def __call__(self):
        while True:
            try:
                self._queue.get(block=False)
            except Empty:
                break
        self._capture = True
        v0 = self._queue.get()  # wait for first value to avoid overlapping past time
        v1 = self._queue.get()
        self._capture = False
        return v1


def run():
    frequency = 1000
    with Driver() as jsdrv:
        jsdrv.log_level = 'WARNING'

        device_paths = sorted(jsdrv.device_paths())
        device_path = device_paths[0]
        jsdrv.open(device_path)
        
        measure = Measure()
        
        if 'js110' in device_path:
            jsdrv.publish(f'{device_path}/s/i/range/select', 'auto')
            jsdrv.publish(f'{device_path}/s/v/range/select', '15 V')
            # use host-side statistics
            jsdrv.publish(device_path + '/s/i/ctrl', 'on')
            jsdrv.publish(device_path + '/s/v/ctrl', 'on')
            jsdrv.publish(device_path + '/s/p/ctrl', 'on')
            scnt = int(round(2_000_000 / frequency))
            jsdrv.publish(device_path + '/s/stats/scnt', scnt)
            jsdrv.publish(device_path + '/s/stats/ctrl', 'on')
            jsdrv.subscribe(device_path + '/s/stats/value', 'pub', measure.on_statistics)            
        elif 'js220' in device_path:
            jsdrv.publish(f'{device_path}/s/i/range/mode', 'auto')
            jsdrv.publish(f'{device_path}/s/v/range/mode', 'auto')
            # JS220, always sensor-side statistics
            scnt = int(round(1_000_000 / frequency))
            jsdrv.publish(device_path + '/s/stats/scnt', scnt)
            jsdrv.publish(device_path + '/s/stats/ctrl', 1)
            jsdrv.subscribe(device_path + '/s/stats/value', 'pub', measure.on_statistics)            

        time.sleep(0.5)  # do something else in your code for a while
        print(measure()['signals']['voltage']['avg']['value'])
        time.sleep(1.0)  # do something else in your code for a while
        print(measure()['signals']['voltage']['avg']['value'])
    return 0


if __name__ == '__main__':
    sys.exit(run())

This script configures the Joulescope statistics streaming. The statistics go to the Measure instance, which normally just discards them. When you call the Measure instance using measure(), it gets the next two statistics values and discards the first. You could use measure.value() with the queue if you don’t care about potentially measuring the voltage prior to calling measure().

Does this make sense and work for you?

Hi @mliberty,
Thanks for this fast solution. This does fit my requirements.
BR

Luk

1 Like

@mliberty, I am not sure how some sections of this code work.

Why does __call__ have the following section:

while True:
    try:
        self._queue.get(block=False)
    except Empty:
        break

Isn’t the queue always empty upon entering this function?
Next:

v0 = self._queue.get()  # wait for first value to avoid overlapping past time
v1 = self._queue.get()

This makes that repeatedly calling:

measure()['signals']['voltage']['avg']['value']

Results in having only half the set sample rate. I hope you can clarify why it looks this way.

If you call this method often enough, then yes, the queue should be empty. However, the original desire was to read the statistics value rarely. If you wait a few seconds between calls, it will not be empty as the Joulescope will have reported statistics data from the pyjoulescope_driver thread.

Correct. That was the desire for the original question. The next statistics value likely has measurement data from before the call to Measure. To ensure future data, this script discards the next update. If you want all of the statistics data, you do not want to use the Measure class in this sample. You can simply register your own statistics callback function, like this:

jsdrv.subscribe(device_path + '/s/stats/value', 'pub', my_custom_function)

See the statistics pyjoulescope_driver entry point.

If you would rather use the joulescope package, see the statistics.py example. This example also puts the statistics values into a queue for processing from the main process thread.