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
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')
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
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
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)
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)
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, }
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
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 = []