def setUp(self):
     self.app = f'joulescope_cmdp_{os.getpid()}'
     self.paths = paths.paths_current(app=self.app)
     os.makedirs(self.paths['dirs']['config'])
     self.c = CommandProcessor(synchronous=True, app=self.app)
     self.commands = []
     self.parameter_subscriber_history = []
     self.parameter_subscriber_value = None
Example #2
0
 def __init__(self, parent=None, app=None):
     super(Preferences, self).__init__(parent)
     self._app = app
     self._path = paths.paths_current(self._app)['files']['config']
     self._defines = {}
     self._profiles = {BASE_PROFILE: {}}
     self._profile_active = BASE_PROFILE
     self.define(name='/', dtype='container')
Example #3
0
def cache_path(version=None):
    if version is None:
        version = VERSIONS['data']['production']
    version_str = version.replace('.', '_')
    filename = VERSIONS['data']['format'].format(version=version_str)

    # Attempt local cache
    firmware_path = paths_current()['dirs']['firmware']
    firmware_path = os.path.join(firmware_path, 'js110')
    if not os.path.isdir(firmware_path):
        os.makedirs(firmware_path)
    path = os.path.join(firmware_path, filename)
    return path
Example #4
0
def loader(name):
    theme_path = os.path.join(paths_current()['dirs']['themes'], name)
    theme_index = os.path.join(theme_path, 'index.json')
    with open(theme_index, 'r', encoding='utf-8') as f:
        index = json.load(f)
    theme_css = os.path.join(theme_path, 'style.qss')
    if not os.path.isfile(theme_css):
        raise ValueError('generate theme')  # todo
    f = QtCore.QFile(theme_css)
    f.open(QtCore.QFile.ReadOnly | QtCore.QFile.Text)
    stream = QtCore.QTextStream(f)
    app = QtCore.QCoreApplication.instance()
    app.setStyleSheet(stream.readAll())
    return index
Example #5
0
def theme_configure(index, target_name, target_path=None):
    """Configure the theme generator.

    :param index: The theme index from theme_index_loader or the theme name string.
    :param target_name: The target name, usually the profile name.
    :param target_path: The target path.  Used to override the default for
        unit testing.
    :return: The theme index.
    """
    if isinstance(index, str):
        index = theme_index_loader(index)
    if target_path is None:
        target_path = paths_current()['dirs']['themes']
    target_path = os.path.join(target_path, target_name)
    index['generator']['target_path'] = target_path
    return index
 def setUp(self):
     self.app = f'joulescope_unittest_{os.getpid()}'
     self.paths = paths.paths_current(app=self.app)
Example #7
0
def preferences_def(p):
    # --- METADATA ---
    p.define('_meta/', 'Preferences metadata')
    p.define('_meta/def_version', dtype='int', default=1)
    p.define('_meta/app_version', dtype='str', default=VERSION)

    # --- GENERAL ---
    p.define('General/', 'General application settings.')
    p.define(
        topic='General/starting_profile',
        brief='The profile to use when launching the application.',
        detail='Use "previous" to automatically restore the previous state. ' +
        'Since this preference selects the starting profile, the ' +
        'value is shared between all profiles.',
        dtype='str',
        options=lambda: ['previous', 'app defaults'] +
        [x for x in p.preferences.profiles if x != 'defaults'],
        default='previous',
        default_profile_only=True,
    )
    p.define(
        topic='General/data_path',
        brief='Default data directory',
        dtype='str',
        # dtype='path',
        # attributes=['exists', 'dir'],
        default=paths_current()['dirs']['data'])
    p.define(topic='General/update_check',
             brief='Automatically check for software updates',
             dtype='bool',
             default=True)
    p.define(topic='General/update_channel',
             brief='The software release channel for updates',
             dtype='str',
             options=['alpha', 'beta', 'stable'],
             default='stable')
    p.define(topic='General/log_level',
             brief='The logging level',
             dtype='str',
             options=[
                 'OFF', 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'ALL'
             ],
             default='INFO')
    p.define(topic='General/window_location',
             brief='The window location',
             dtype='str',
             options=['center', 'previous'],
             default='center')
    p.define(topic='General/window_size',
             brief='The window size',
             dtype='str',
             options=['previous', 'minimum', '50%', '75%', '100%'],
             default='previous')
    p.define(topic='General/developer',
             brief='Enable developer features',
             dtype='bool',
             default=False,
             default_profile_only=True)

    # --- DEVICE ---
    p.define('Device/', 'Joulescope device-specific default settings')
    p.define(topic='Device/autostream',
             brief='Start streaming when the device connects',
             dtype='bool',
             default=True)
    p.define('Device/parameter/', 'Joulescope device-specific parameters')
    p.define(topic='Device/parameter/source',
             brief='Select the streaming data source',
             detail='Do not edit this setting for normal use',
             dtype='str',
             options=[
                 'off', 'raw', 'pattern_usb', 'pattern_control',
                 'pattern_sensor'
             ],
             default='raw')
    p.define(topic='Device/parameter/i_range',
             brief='Select the current measurement range (shunt resistor)',
             dtype='str',
             options={
                 'auto': {
                     'brief':
                     'Perform fast autoranging to select the best shunt value'
                 },
                 'off': {
                     'brief': 'Disable the shunt for high impedance'
                 },
                 '10 A': {
                     'aliases': ['0'],
                     'brief': 'Least resistance (highest current range)'
                 },
                 '2 A': {
                     'aliases': ['1']
                 },
                 '180 mA': {
                     'aliases': ['2']
                 },
                 '18 mA': {
                     'aliases': ['3']
                 },
                 '1.8 mA': {
                     'aliases': ['4']
                 },
                 '180 µA': {
                     'aliases': ['5']
                 },
                 '18 µA': {
                     'aliases': ['6'],
                     'brief': 'Most resistance (lowest current range)'
                 }
             },
             default='auto')
    p.define(topic='Device/parameter/v_range',
             brief='Select the voltage measurement range (gain)',
             dtype='str',
             options={
                 '15V': {
                     'brief': '15V range (recommended)',
                     'aliases': ['high']
                 },
                 '5V': {
                     'brief':
                     '5V range with improved resolution for lower voltages',
                     'aliases': ['low']
                 }
             },
             default='15V')
    p.define(topic='Device/parameter/io_voltage',
             brief='The GPI/O high-level voltage.',
             dtype='str',
             options=['1.8V', '2.1V', '2.5V', '2.7V', '3.0V', '3.3V', '5.0V'],
             default='3.3V')
    p.define(topic='Device/parameter/gpo0',
             brief='The GPO bit 0 output value.',
             dtype='str',
             options=['0', '1'],
             default='0')
    p.define(topic='Device/parameter/gpo1',
             brief='The GPO bit 1 output value.',
             dtype='str',
             options=['0', '1'],
             default='0')
    p.define(topic='Device/parameter/current_lsb',
             brief='The current signal least-significant bit mapping.',
             dtype='str',
             options=['normal', 'gpi0'],
             default='normal')
    p.define(topic='Device/parameter/voltage_lsb',
             brief='The voltage signal least-significant bit mapping.',
             dtype='str',
             options=['normal', 'gpi1'],
             default='normal')
    p.define(
        topic='Device/rescan_interval',
        brief='The manual device rescan interval in seconds',
        detail='Device rescan normally happens when devices are connected ' +
        'to the computer.  For long running-tests, selecting an additional manual '
        +
        'rescan interval assists recovery on USB and device failures.  However, '
        +
        'enabling this feature automatically selects a device on Device->disable.',
        dtype='str',
        options=['off', '1', '2', '5', '10', '20', '50'],
        default='off')
    p.define(topic='Device/firmware_update',
             brief='Firmware update settings.',
             dtype='str',
             options=['never', 'auto', 'always'],
             default='auto')
    p.define(topic='Device/on_close',
             brief='Device configuration on device close.',
             dtype='str',
             options=['keep', 'sensor_off', 'current_off', 'current_auto'],
             default='keep')
    p.define(topic='Device/buffer_duration',
             brief='The stream buffer duration in seconds.',
             detail='Use care when setting this value. ' +
             'The software requires 1.5 GB of RAM for every 60 seconds.',
             dtype='str',
             options=['15', '30', '60', '90', '120', '180', '240', '300'],
             default='30')
    p.define('Device/#state/name', dtype=str, default='')
    p.define('Device/#state/source',
             dtype=str,
             options=['None', 'USB', 'Buffer', 'File'],
             default='None')
    p.define('Device/#state/sample_drop_color', dtype=str, default='')
    p.define('Device/#state/play', dtype=bool, default=False)
    p.define('Device/#state/record', dtype=bool, default=False)
    p.define('Device/#state/energy', dtype=str, default='')
    p.define('Device/#state/sampling_frequency', dtype=float, default=0.0)
    p.define('Device/#state/status', dtype=dict, default={})
    p.define('Device/#state/statistics', dtype=dict, default={})
    p.define('Device/#state/x_limits', dtype=object)  # [x_min, x_max]

    # --- CURRENT RANGING ---
    p.define(
        topic='Current Ranging/',
        brief=
        'Configure the current range behavior including the filtering applied during range switches.'
    )
    p.define(topic='Current Ranging/type',
             brief='The filter type.',
             dtype='str',
             options=['off', 'mean', 'NaN'],
             default='mean')
    p.define(
        topic='Current Ranging/samples_pre',
        brief='The number of samples before the range switch to include.',
        detail='Only valid for type "mean" - ignored for "off" and "NaN".',
        dtype='str',
        options=['0', '1', '2', '3', '4', '5', '6', '7', '8'],
        default='2')
    p.define(
        topic='Current Ranging/samples_window',
        brief='The number of samples to adjust.',
        detail='Use "n" for automatic duration based upon known response time.',
        dtype='str',
        options=[
            'n', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11',
            '12'
        ],
        default='n')
    p.define(
        topic='Current Ranging/samples_post',
        brief='The number of samples after the range switch to include.',
        detail='Only valid for type "mean" - ignored for "off" and "NaN".',
        dtype='str',
        options=['0', '1', '2', '3', '4', '5', '6', '7', '8'],
        default='2')

    # --- Plugins ---
    p.define('Plugins/', 'Joulescope UI Plugins')
    p.define('Plugins/#registered', dtype=object, default=None)

    # --- DataView ---
    p.define('DataView/', 'Data view configuration for waveform')
    p.define(
        'DataView/#service/x_change_request',
        dtype=object,
        brief='Request an x-axis range change.',
        detail='List of [x_min: float, x_max: float, x_count: int] where\n' +
        'x_min: The minimum x_axis value to display in the range.\n' +
        'x_max: The maximum x_axis value to display in the range.\n' +
        'x_count: The desired number of samples in the range.\n')
    p.define(
        'DataView/#service/range_statistics',
        dtype=object,
        brief='Request statistics over data ranges.',
        detail='dict containing:\n' +
        'ranges: list of (x_start, x_stop) ranges in view seconds\n' +
        'source_id: Source indicator to allow for coalescence.\n' +
        'reply_topic: The topic for the response which is a dict with\n' +
        'request and response.  On error, response is None.\n' +
        'On success, response is a list of joulescope.view.View.statistics_get\n'
        + 'return values.')
    p.define('DataView/#data',
             dtype=object,
             brief='The latest data from the view.')

    return p
 def setUp(self):
     self.listener_calls = []
     self.app = f'joulescope_preferences_{os.getpid()}'
     self.paths = paths.paths_current(app=self.app)
     os.makedirs(self.paths['dirs']['config'])
     self.p = Preferences(app=self.app)
Example #9
0
import faulthandler
import json
import multiprocessing
import threading
import traceback
import queue
import os
import sys
import platform
from joulescope_ui.paths import paths_current
from . import __version__ as UI_VERSION
from . import frozen
from joulescope import VERSION as DRIVER_VERSION


paths = paths_current()
LOG_PATH = paths['dirs']['log']
EXPIRATION_SECONDS = 7 * 24 * 60 * 60
STREAM_SIMPLE_FMT = "%(levelname)s:%(name)s:%(message)s"
STREAM_VERBOSE_FMT = "%(levelname)s:%(asctime)s:%(filename)s:%(lineno)d:%(name)s:%(message)s"
FILE_FMT = "%(levelname)s:%(asctime)s:%(filename)s:%(lineno)d:%(name)s:%(message)s"

LEVELS = {
    'OFF': 100,
    'CRITICAL': logging.CRITICAL,
    'ERROR': logging.ERROR,
    'WARNING': logging.WARNING,
    'INFO': logging.INFO,
    'DEBUG': logging.DEBUG,
    'ALL': 0,
}
Example #10
0
def preferences_def(p):
    # --- METADATA ---
    p.define('_meta/', 'Preferences metadata')
    p.define('_meta/def_version', dtype='int', default=1)
    p.define('_meta/app_version', dtype='str', default=__version__)

    # --- GENERAL ---
    p.define('General/', 'General application settings.')
    p.define(
        topic='General/starting_profile',
        brief='The profile to use when launching the application.',
        detail='Use "previous" to automatically restore the previous state. ' +
               'Since this preference selects the starting profile, the ' +
               'value is shared between all profiles.',
        dtype='str',
        options=lambda: ['previous', 'app defaults'] + [x for x in p.preferences.profiles if x != 'defaults'],
        default='previous',
        default_profile_only=True,
    )
    p.define(
        topic='General/data_path',
        brief='Default data directory',
        dtype='str',
        # dtype='path',
        # attributes=['exists', 'dir'],
        default=paths_current()['dirs']['data'])
    p.define(
        topic='General/data_path_type',
        brief='Specify the default data path.',
        dtype='str',
        options=['Use fixed data_path', 'Most recently saved', 'Most recently used'],
        default='Most recently used'),
    p.define('General/_path_most_recently_saved', dtype='str', default=paths_current()['dirs']['data']),
    p.define('General/_path_most_recently_used', dtype='str', default=paths_current()['dirs']['data']),
    p.define(
        topic='General/update_check',
        brief='Automatically check for software updates',
        dtype='bool',
        default=True)
    p.define(
        topic='General/update_channel',
        brief='The software release channel for updates',
        dtype='str',
        options=['alpha', 'beta', 'stable'],
        default='stable')
    p.define(
        topic='General/log_level',
        brief='The logging level',
        dtype='str',
        options=['OFF', 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'ALL'],
        default='INFO')
    p.define(
        topic='General/window_location',
        brief='The window location',
        dtype='str',
        options=['center', 'previous'],
        default='center')
    p.define(
        topic='General/window_size',
        brief='The window size',
        dtype='str',
        options=['previous', 'minimum', '50%', '75%', '100%'],
        default='previous')
    p.define(
        topic='General/window_on_top',
        brief='Force the Joulescope UI window to stay on top.',
        detail='This feature keeps the Joulescope UI in the foreground, ' +
               'even if other applications are selected. ' +
               'This feature does not take effect until the Joulescope UI ' +
               'restarts.',
        dtype='bool',
        default=False)
    p.define(
        topic='General/developer',
        brief='Enable developer features',
        dtype='bool',
        default=False,
        default_profile_only=True)
    p.define(
        topic='General/process_priority',
        brief='The OS process priority',
        detail='This feature is currently only supported on Windows.',
        dtype='str',
        options=['normal', 'elevated'],
        default='elevated')
    p.define(
        topic='General/mru',
        brief='The number of most recently used files to remember.',
        dtype='str',
        options=['0', '3', '5', '10', '20'],
        default='10')
    p.define(topic='General/_mru_open', dtype=object, default=[])

    # --- UNITS ---
    p.define('Units/', 'Units to display.')
    p.define(
        topic='Units/accumulator',
        brief='The accumulation field to display.',
        dtype='str',
        options=['energy', 'charge'],
        default='energy')
    p.define(
        topic='Units/charge',
        brief='The units to display for charge, the integral of current.',
        dtype='str',
        options=['C', 'Ah'],
        default='C')
    p.define(
        topic='Units/energy',
        brief='The units to display for energy, the integral of power.',
        dtype='str',
        options=['J', 'Wh'],
        default='J')
    p.define('Units/elapsed_time',
        brief='The elapsed time format.',
        dtype='str',
        options=['seconds', 'D:hh:mm:ss'],
        default='seconds')

    # --- DEVICE ---
    p.define('Device/', 'Joulescope device-specific default settings')
    p.define(
        topic='Device/autostream',
        brief='Start streaming when the device connects',
        dtype='bool',
        default=True)
    p.define(
        topic='Device/rescan_interval',
        brief='The manual device rescan interval in seconds',
        detail='Device rescan normally happens when devices are connected ' +
               'to the computer.  For long running-tests, selecting an additional manual ' +
               'rescan interval assists recovery on USB and device failures.  However, ' +
               'enabling this feature automatically selects a device on Device->disable.',
        dtype='str',
        options=['off', '1', '2', '5', '10', '20', '50'],
        default='off')
    p.define(
        topic='Device/firmware_update',
        brief='Firmware update settings.',
        dtype='str',
        options=['never', 'auto', 'always'],
        default='auto')
    p.define(
        topic='Device/on_close',
        brief='Device configuration on device close.',
        dtype='str',
        options=['keep', 'sensor_off', 'current_off', 'current_auto'],
        default='keep')
    p.define('Device/setting/', 'Joulescope device-specific settings.')
    p.define('Device/extio/', 'Joulescope external general purpose input/output control.')
    p.define(
        topic='Device/Current Ranging/',
        brief='Configure the current range behavior including the filtering applied during range switches.')
    for parameter in PARAMETERS:
        if 'developer' in parameter.flags or 'hidden' in parameter.flags:
            prefix = '_'
        else:
            prefix = ''
        default = _DEVICE_PARAMETER_DEFAULT_OVERRIDE.get(parameter.name, parameter.default)
        if parameter.path in ['setting', 'extio']:
            topic = f'Device/{parameter.path}/{prefix}{parameter.name}'
        elif parameter.path == 'current_ranging':
            if parameter.name == 'current_ranging':
                continue
            name = parameter.name.replace('current_ranging_', '')
            topic = f'Device/Current Ranging/{name}'
        else:
            continue
        p.define(
            topic=topic,
            brief=parameter.brief,
            detail=parameter.detail,
            dtype='str',
            options=[x[0] for x in parameter.options],
            default=default)

    p.define('Device/#state/name', dtype=str, default='')
    p.define('Device/#state/source', dtype=str, options=['None', 'USB', 'Buffer', 'File'], default='None')
    p.define('Device/#state/stream', dtype=str, default='inactive')
    p.define('Device/#state/play',   dtype=bool, default=False)
    p.define('Device/#state/record', dtype=bool, default=False)
    p.define('Device/#state/record_statistics', dtype=bool, default=False)
    p.define('Device/#state/sampling_frequency', dtype=float, default=0.0)
    p.define('Device/#state/status', dtype=dict, default={})
    p.define('Device/#state/statistics', dtype=dict, default={})
    p.define('Device/#state/x_limits', dtype=object)  # [x_min, x_max]

    # --- Appearance ---
    p.define('Appearance/', 'Adjust the UI appearance')
    p.define('Appearance/Theme', dtype=str, options=['system', 'js1.dark', 'js1.light'], default='js1.dark')
    p.define('Appearance/__index__', dtype=dict, default={})
    p.define('Appearance/Fonts/', 'Adjust fonts')
    p.define('Appearance/Colors/', 'Adjust colors')
    p.define('Appearance/Colors/ui_override', dtype=str, default='')

    # --- Plugins ---
    p.define('Plugins/', 'Joulescope UI Plugins')
    p.define('Plugins/#registered', dtype=object, default=None)

    # --- DataView ---
    p.define('DataView/', 'Data view configuration for waveform')
    p.define(
        'DataView/#service/x_change_request', dtype=object,
        brief='Request an x-axis range change.',
        detail='List of [x_min: float, x_max: float, x_count: int] where\n' +
               'x_min: The minimum x_axis value to display in the range.\n' +
               'x_max: The maximum x_axis value to display in the range.\n' +
               'x_count: The desired number of samples in the range.\n')
    p.define(
        'DataView/#service/range_statistics', dtype=object,
        brief='Request statistics over data ranges.',
        detail='dict containing:\n' +
               'ranges: list of (x_start, x_stop) ranges in view seconds\n' +
               'source_id: Source indicator to allow for coalescence.\n' +
               'reply_topic: The topic for the response which is a dict with\n' +
               'request and response.  On error, response is None.\n' +
               'On success, response is a list of joulescope.view.View.statistics_get\n' +
               'return values.')
    p.define(
        'DataView/#data', dtype=object,
        brief='The latest data from the view.')

    return p
Example #11
0
 def setUp(self):
     self.app = f'joulescope_cmdp_{os.getpid()}'
     self.paths = paths.paths_current(app=self.app)
     os.makedirs(self.paths['dirs']['config'])
     self.c = CommandProcessor(synchronous=True, app=self.app)
     self.commands = []