Source code for qtrfblocks.ad9913_qt

from typing import (
    List, Dict, Optional
)
import copy
import time
import random
from math import sqrt, log10
from enum import Enum, IntFlag
import serial
import json
import asyncio

import numpy as np
from scipy import interpolate

from rfblocks import (
    ad9552, ad9913, AD9913Controller,
    ModulusConstraintException,
    list_available_serial_ports, create_serial,
    write_cmd, query_cmd, DEFAULT_SOCKET_URL
)

from qtrfblocks import (
    ClkModule
)

from qasync import (
    QEventLoop, QThreadExecutor, asyncSlot, asyncClose
)

from PyQt5.QtWidgets import (
    QWidget, QLabel, QAbstractSpinBox, QDoubleSpinBox, QVBoxLayout,
    QLineEdit, QHBoxLayout, QGroupBox, QMainWindow, QComboBox,
    QCheckBox, QPushButton, QRadioButton, QButtonGroup, QMessageBox,
    QFormLayout, QErrorMessage, QApplication, QScrollArea,
    QMenu, QFileDialog, QSpinBox, QDialog, QDialogButtonBox,
    QAction, QActionGroup
)

from PyQt5.QtCore import (
    Qt, QObject, pyqtSlot
)


class ModulationType(IntFlag):
    FREQUENCY = 0b0
    PHASE = 0b1


[docs]class DDSChan(QObject): MIN_FREQUENCY: float = 0.0 MAX_FREQUENCY: float = 100.0 MIN_RATE: float = 0.004 MAX_RATE: float = 262.0 MIN_PHASE: float = 0.0 MAX_PHASE: float = 360.0 FREQ_SUFFIX: str = ' MHz' RATE_SUFFIX: str = ' uS' PHASE_SUFFIX: str = u"\N{DEGREE SIGN}" FREQ_RANGE: List[float] = [MIN_FREQUENCY, MAX_FREQUENCY] PHASE_RANGE: List[float] = [MIN_PHASE, MAX_PHASE] RATE_RANGE: List[float] = [MIN_RATE, MAX_RATE] LVL_UNITS = Enum('Lvl_Units', 'DAC MV DBM') # The following number is derived from measurement of # the DDS_2 module output RMS voltages # DAC_TO_MILLIVOLTS = 510/1013 DAC_CODE_RANGE: List[int] = [1, 1023] # MILLIVOLTS_RANGE = [DAC_TO_MILLIVOLTS * 1, DAC_TO_MILLIVOLTS * 1023] # DBM_RANGE = [10*log10( ((DAC_TO_MILLIVOLTS * 1e-3) * 1)**2 / 50e-3 ), # 10*log10( ((DAC_TO_MILLIVOLTS * 1e-3) * 1023)**2 / 50e-3 )] def __init__(self, app: QWidget, controller: AD9913Controller): """Create an instance of ``DDSChan``. :param app: The parent application for the channel display. :type app: QWidget :param controller: AD9913 DDS controller :type controller: :py:class:`rfblocks.AD9913Controller` """ super().__init__(app) self._app: QWidget = app self._controller: AD9913Controller = controller self._min_freq: float = DDSChan.MIN_FREQUENCY self._max_freq: float = DDSChan.MAX_FREQUENCY self._level_units: Enum = DDSChan.LVL_UNITS.DBM self.label: str = self._controller.label self._modulation_type: ModulationType = ModulationType.FREQUENCY self._modulation_tones: int = 2 self._modulating: bool = False self._group_box: Optional[QGroupBox] = None @classmethod def level_units_control(cls, app: QWidget, parent: QObject) -> QPushButton: level_units_btn: QPushButton = QPushButton("Units") level_units_menu: QMenu = QMenu() units_group: QActionGroup = QActionGroup(app) dac_units_action: QAction = QAction('DAC Code', units_group, triggered=parent.set_dac_units, checkable=True) mv_units_action: QAction = QAction('Millivolts', units_group, triggered=parent.set_millivolt_units, checkable=True) dbm_units_action: QAction = QAction('dBm', units_group, triggered=parent.set_dbm_units, checkable=True) dbm_units_action.setChecked(True) level_units_menu.addAction(dac_units_action) level_units_menu.addAction(mv_units_action) level_units_menu.addAction(dbm_units_action) level_units_btn.setMenu(level_units_menu) return level_units_btn @property def ctl(self) -> AD9913Controller: """Return a reference to the :py:class:`AD9913Controller`. """ return self._controller @classmethod def level_units_control(cls, app: QWidget, parent: QObject) -> QPushButton: level_units_btn: QPushButton = QPushButton("Units") level_units_menu: QMenu = QMenu() units_group: QActionGroup = QActionGroup(app) dac_units_action: QAction = QAction('DAC Code', units_group, triggered=parent.set_dac_units, checkable=True) mv_units_action: QAction = QAction('Millivolts', units_group, triggered=parent.set_millivolt_units, checkable=True) dbm_units_action: QAction = QAction('dBm', units_group, triggered=parent.set_dbm_units, checkable=True) dbm_units_action.setChecked(True) level_units_menu.addAction(dac_units_action) level_units_menu.addAction(mv_units_action) level_units_menu.addAction(dbm_units_action) level_units_btn.setMenu(level_units_menu) return level_units_btn
[docs] def build_ui(self) -> QGroupBox: """Build on-screen UI components for a DDS_2 channel. """ self._group_box = QGroupBox("{} ({})".format( self.label, self.ctl._ad9913.model)) vbox: QVBoxLayout = QVBoxLayout() fbox: QFormLayout = QFormLayout() self._freq_box = QDoubleSpinBox() self._freq_box.setRange(self.min_freq, self.max_freq) self._freq_box.setDecimals(5) self._freq_box.setStyleSheet("""QDoubleSpinBox { font-size: 25pt; }""") self._freq_box.setValue(self.freq) self._freq_box.setSuffix(DDSChan.FREQ_SUFFIX) self._freq_box.valueChanged.connect(self.set_freq) fbox.addRow(QLabel("Freq.:"), self._freq_box) self._ph_box = QDoubleSpinBox() self._ph_box.setRange(*DDSChan.PHASE_RANGE) self._ph_box.setDecimals(2) self._ph_box.setStyleSheet("""QDoubleSpinBox { font-size: 25pt; }""") self._ph_box.setValue(self.phase) self._ph_box.setSuffix(DDSChan.PHASE_SUFFIX) self._ph_box.valueChanged.connect(self.set_phase) fbox.addRow(QLabel("Phase:"), self._ph_box) hbox3: QHBoxLayout = QHBoxLayout() self._lvl_box = QDoubleSpinBox() self._lvl_box.setStyleSheet("""QDoubleSpinBox { font-size: 25pt; }""") self._lvl_box.setRange(*self.dbm_range) self._lvl_box.setDecimals(1) self._lvl_box.setSuffix(' dBm') self._lvl_box.setValue(self.dbm) self._lvl_box.valueChanged.connect(self.set_dbm) hbox3.addWidget(self._lvl_box) level_units_btn: QPushButton = DDSChan.level_units_control( self._app, self) hbox3.addWidget(level_units_btn) fbox.addRow(QLabel("Level:"), hbox3) self._pmod_box: QCheckBox = QCheckBox("") self._pmod_box.setChecked(False) self._pmod_box.stateChanged.connect(self.set_pmod) lbl: QLabel = QLabel("Programmable:\nModulus") lbl.setWordWrap(True) fbox.addRow(lbl, self._pmod_box) self._state_box: QCheckBox = QCheckBox("") if self.set_state == ad9913.POWER_UP: self._state_box.setChecked(True) else: self._state_box.setChecked(False) self._state_box.stateChanged.connect(self.set_state) fbox.addRow(QLabel("Active:"), self._state_box) vbox.addLayout(fbox) # ---------------------------------------------------- # Module hardware configure # hbox3 = QHBoxLayout() self._config_btn = QPushButton("Configure") self._config_btn.clicked.connect(self.configure) hbox3.addWidget(self._config_btn) hbox3.addStretch() vbox.addLayout(hbox3) hbox2: QHBoxLayout = QHBoxLayout() self._sweep_btn: QPushButton = QPushButton("Sweep...") self._sweep_btn.clicked.connect(self.show_sweep) hbox2.addWidget(self._sweep_btn) self._profile_btn = QPushButton("Profiles...") self._profile_btn.clicked.connect(self.show_profiles) hbox2.addWidget(self._profile_btn) hbox2.addStretch() vbox.addLayout(hbox2) self.ctl.state_changed.connect(self.update_chan_state) self.ctl.freq_changed.connect(self.update_chan_freq) self.ctl.phase_changed.connect(self.update_chan_phase) self.ctl.level_changed.connect(self.update_chan_level) self.ctl.pmod_changed.connect(self.update_chan_pmod) self._group_box.setLayout(vbox) return self._group_box
def set_dac_units(self) -> None: self._level_units = DDSChan.LVL_UNITS.DAC self._lvl_box.disconnect() self._lvl_box.setRange(*DDSChan.DAC_CODE_RANGE) self._lvl_box.setDecimals(0) self._lvl_box.setSuffix('') self._lvl_box.setValue(self.level) self._lvl_box.valueChanged.connect(self.set_level) def set_millivolt_units(self) -> None: self._level_units = DDSChan.LVL_UNITS.MV self._lvl_box.disconnect() self._lvl_box.setRange(*self.millivolts_range) self._lvl_box.setDecimals(1) self._lvl_box.setSuffix(' mV') self._lvl_box.setValue(self.millivolts) self._lvl_box.valueChanged.connect(self.set_millivolts) def set_dbm_units(self) -> None: self._level_units = DDSChan.LVL_UNITS.DBM self._lvl_box.disconnect() self._lvl_box.setRange(*self.dbm_range) self._lvl_box.setDecimals(1) self._lvl_box.setSuffix(' dBm') self._lvl_box.setValue(self.dbm) self._lvl_box.valueChanged.connect(self.set_dbm) @property def millivolts_range(self) -> List[float]: return [self.ctl.level_to_millivolts(l) for l in DDSChan.DAC_CODE_RANGE] @property def dbm_range(self) -> List[float]: return [self.ctl.level_to_dbm(l) for l in DDSChan.DAC_CODE_RANGE] @property def enabled(self) -> bool: """Enable/disable the on screen clock module UI components. """ if self._group_box: return self._group_box.isEnabled() else: return False @enabled.setter def enabled(self, en: bool) -> None: if self._group_box: self._group_box.setEnabled(en) @property def state(self) -> int: """The state of the DDS channel. Either powered up or powered down. """ return self.ctl.state
[docs] def set_state(self, flag: int): """Set the DDS channel state. :param flag: This should be either :py:`ad9913.POWER_UP` or :py:`ad9913.POWER_DOWN`. :type flag: int """ self.ctl.state = flag
[docs] @pyqtSlot(int) def update_chan_state(self, state: int) -> None: """Update the displayed DDS channel state. """ if state == ad9913.POWER_UP: self._state_box.setChecked(True) else: self._state_box.setChecked(False)
@property def chan_id(self) -> str: """The channel identifier. """ return self.ctl._controller_id def write_chan_cmd(self, cmd: str) -> None: try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: write_cmd(ser, cmd) except serial.serialutil.SerialException as se: print(str(se)) @property def freq(self) -> float: """Return the DDS output frequency (in MHz). """ return self.ctl.freq
[docs] def set_freq(self, f: float) -> None: """Set the DDS output frequency (in MHz). Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.freq = f # If necessary, recalculate the output millivolts or dBm if self._level_units == DDSChan.LVL_UNITS.MV: self._lvl_box.setValue(self.ctl.millivolts) self._lvl_box.setRange(*self.millivolts_range) elif self._level_units == DDSChan.LVL_UNITS.DBM: self._lvl_box.setValue(self.ctl.dbm) self._lvl_box.setRange(*self.dbm_range)
[docs] @pyqtSlot(float) def update_chan_freq(self, f: float) -> None: """Update the displayed DDS channel frequency. """ self._freq_box.setValue(f)
@property def phase(self) -> float: """Return the DDS phase offset (in degrees). """ return self.ctl.phase
[docs] def set_phase(self, p: float) -> None: """Set the DDS phase offset (in degrees). Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.phase = p
[docs] @pyqtSlot(float) def update_chan_phase(self, p: float) -> None: """Update the displayed DDS channel phase offset. """ self._ph_box.setValue(p)
@property def level_units(self) -> Enum: return self._level_units @property def level(self) -> float: """Return the DDS output level (in DDS DAC units). """ return self.ctl.level
[docs] def set_level(self, l: float) -> None: """Set the DDS output level (in DDS DAC units). Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.level = l
@property def millivolts(self) -> float: """Return the DDS output level (in millivolts RMS) """ return self.ctl.millivolts
[docs] def set_millivolts(self, mv: float): """Set the DDS output level (in millivolts). Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.millivolts = mv
@property def dbm(self) -> float: """Return the DDS output level (in dBm). """ return self.ctl.dbm
[docs] def set_dbm(self, d: float): """Set the DDS output level (in dBm) Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.dbm = d
[docs] @pyqtSlot(float) def update_chan_level(self, l: float) -> None: """Update the displayed DDS channel output level. """ if self._level_units == DDSChan.LVL_UNITS.MV: self._lvl_box.setValue(self.ctl.level_to_millivolts(l)) elif self._level_units == DDSChan.LVL_UNITS.DBM: self._lvl_box.setValue(self.ctl.level_to_dbm(l)) else: self._lvl_box.setValue(l)
@property def min_freq(self) -> float: return self._min_freq @min_freq.setter def min_freq(self, f: float) -> None: self._min_freq = f @property def max_freq(self) -> float: return self._max_freq @max_freq.setter def max_freq(self, f: float) -> None: self._max_freq = f
[docs] def set_pmod(self, state: bool) -> None: """Set the DDS programmable modulus state. Note that :py:meth:`configure` must be invoked in order to update the DDS hardware """ self.ctl.pmod = state if state: # Disable direct switch mode when enabling # programmable modulus self.set_direct_switch_enabled(False)
[docs] @pyqtSlot(bool) def update_chan_pmod(self, state: bool) -> None: """Update the displayed prog. modulus checkbox. """ self._pmod_box.setChecked(state)
@property def direct_switch_enabled(self) -> bool: return self.ctl.direct_switch_enabled def set_direct_switch_enabled(self, enable: bool) -> None: self.ctl.direct_switch_enabled = enable @property def selected_profile(self) -> int: return self.ctl.selected_profile def set_selected_profile(self, state: bool, profnum: int) -> None: if state: self.ctl.selected_profile = profnum @property def profile_type(self) -> ad9913.SweepType: return self.ctl.profile_type def set_profile_type(self, state: bool, ptype: ad9913.SweepType) -> None: if state: self.ctl.profile_type = ptype @property def modulation_type(self) -> ModulationType: return self._modulation_type def set_modulation_type(self, state: bool, mtype: ModulationType) -> None: if state: self._modulation_type = mtype @property def modulation_tones(self) -> int: return self._modulation_tones def set_modulation_tones(self, state: bool, mtones: int) -> None: if state: self._modulation_tones = mtones @property def modulating(self) -> bool: return self._modulating def set_modulating(self, m: bool) -> None: self._modulating = m def show_sweep(self) -> None: sweep_dialog = SweepDialog(self._app, self) sweep_dialog.exec_() def show_profiles(self) -> None: profiles_dialog = ProfilesDialog(self._app, self, self.ctl.profiles) profiles_dialog.exec_()
[docs] def dump_config(self) -> Dict: """Return the current configuration for this clock module. :return: A dictionary containing the current clock modules configuration: :freq: The currently set output frequency (in MHz). :channels: A dictionary containing the channel configurations keyed using the channel id. """ return self.ctl.dump_config()
[docs] def load_config(self, config: Dict) -> None: """Set the current configuration for this clock module. :param config: A dictionary containing the module configuration to be set. :type config: Dict """ self.ctl.load_config(config)
[docs] @asyncSlot() async def configure(self): """Update the DDS hardware using the currently set configuration. """ try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.configure_hw, ser) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self._app) error_dialog.showMessage(str(se))
[docs] def initialize_hw(self, ser: serial.Serial) -> None: """Initialize the DDS board hardware. """ self.ctl.initialize(ser)
def configure_hw(self, ser: serial.Serial) -> None: self.ctl.configure(ser)
[docs] def initialize_ui(self) -> None: """Initialize the user interface. """ self.configure_ui(self.ctl._initial_config)
def configure_ui(self, config: Dict) -> None: self._freq_box.setValue(config['freq']) self._ph_box.setValue(config['ph']) self.set_level(config['lvl']) if self._level_units == DDSChan.LVL_UNITS.DAC: self._lvl_box.setValue(self.ctl.level) elif self._level_units == DDSChan.LVL_UNITS.MV: self._lvl_box.setValue(self.ctl.millivolts) else: self._lvl_box.setValue(self.ctl.dbm) self._pmod_box.setChecked(config['pmod']) self._sweep = copy.deepcopy(config['sweep']) async def modulate(self) -> None: # # Use up to 8 profiles to generate the required modulation # tones. We assume that the profile frequencies and/or # phases have been set appropriately. # The profiles are used as follows: # # Tones Profile numbers # 2 00, 01 # 4 00, 01, 10, 11 # 8 000, 001, 010, 011, 100, 101, 110, 111 # port = self.ctl._ad9913.ps_port if not port: print("Channel is not configured for modulation") return loop = asyncio.get_event_loop() mask_str = '{:02X}'.format(self.ctl._ad9913.ps_mask) tones = self.modulation_tones rgen = random.Random() self.ctl._ad9913.set_profile_control(ad9913.ProfileControl.PINS) self.ctl._ad9913.set_direct_switch_mode_enable(True) if self.modulation_type == ModulationType.FREQUENCY: self.ctl._ad9913.set_sweep_type(ad9913.SweepType.FREQUENCY) else: self.ctl._ad9913.set_sweep_type(ad9913.SweepType.PHASE) cmd = self.ctl._ad9913.config_cfr1() await loop.run_in_executor(None, self.write_chan_cmd, cmd) while self.modulating: symbols = [round(rgen.random() * (tones-1)) for i in range(100)] signal_str = ','.join(['{:02X}'.format(b) for b in [self.ctl._ad9913.ps_bits(s) for s in symbols]]) cmd = 'M{},{},{}:'.format(port, mask_str, signal_str) await loop.run_in_executor(None, self.write_chan_cmd, cmd)
# ---------------------------------------------------------- # DDS channel dialogs
[docs]class SweepDialog(QDialog): """ """ def __init__(self, ddsApp, ddsChan, parent=None): QDialog.__init__(self, parent) self._app = ddsApp self._chan = ddsChan self._start_box = None self._sweep_config = ddsChan.ctl.sweep_config self._limit_suffix = DDSChan.FREQ_SUFFIX self._limit_range = DDSChan.FREQ_RANGE self._step_suffix = DDSChan.FREQ_SUFFIX self._step_range = DDSChan.FREQ_RANGE self._rate_suffix = DDSChan.RATE_SUFFIX self._rate_range = DDSChan.RATE_RANGE self.setWindowTitle("{} Sweep Settings".format(ddsChan.label)) self.build_ui() @property def ctl(self): return self._chan.ctl @property def start(self): return self._sweep_config['start'] def set_start(self, s): self._sweep_config['start'] = s @property def end(self): return self._sweep_config['end'] def set_end(self, e): self._sweep_config['end'] = e @property def sweep_type(self): return self._sweep_config['type'] def set_sweep_type(self, t): self._sweep_config['type'] = t self.set_sweep_range()
[docs] def set_sweep_range(self): """Set the sweep range and suffix. """ t = self._sweep_config['type'] if t in [ad9913.SweepType.FREQUENCY]: self._limit_suffix = self._step_suffix = DDSChan.FREQ_SUFFIX self._limit_range = self._step_range = DDSChan.FREQ_RANGE else: self._limit_suffix = self._step_suffix = DDSChan.PHASE_SUFFIX self._limit_range = self._step_range = DDSChan.PHASE_RANGE if self._start_box: self._start_box.setSuffix(self._limit_suffix) self._start_box.setRange(*self._limit_range) self._end_box.setSuffix(self._limit_suffix) self._end_box.setRange(*self._limit_range) self._rising_step_box.setSuffix(self._limit_suffix) self._rising_step_box.setRange(*self._step_range) self._falling_step_box.setSuffix(self._limit_suffix) self._falling_step_box.setRange(*self._step_range)
@property def ramp_type(self): return self._sweep_config['ramp'] def set_ramp_type(self, r): self._sweep_config['ramp'] = r @property def rising_step(self): return self._sweep_config['delta'][0] def set_rising_step(self, r): self._sweep_config['delta'][0] = r @property def falling_step(self): return self._sweep_config['delta'][1] def set_falling_step(self, r): self._sweep_config['delta'][1] = r @property def rising_rate(self): return self._sweep_config['rate'][0] def set_rising_rate(self, r): self._sweep_config['rate'][0] = r @property def falling_rate(self): return self._sweep_config['rate'][1] def set_falling_rate(self, r): self._sweep_config['rate'][1] = r def build_ui(self): outer_vbox = QVBoxLayout() outer_hbox = QHBoxLayout() outer_vbox.addLayout(outer_hbox) vbox1 = QVBoxLayout() vbox2 = QVBoxLayout() outer_hbox.addLayout(vbox1) outer_hbox.addLayout(vbox2) self._sweep_gbox = QGroupBox("Sweep Parameters") fbox1 = QFormLayout() # ---------------------------------------------------- # Sweep type # hbox1 = QHBoxLayout() self._sweep_type_widgets = {} type_group = QButtonGroup(hbox1) rb = QRadioButton("Freq.") type_group.addButton(rb) rb.sweep_type = ad9913.SweepType.FREQUENCY rb.toggled.connect(lambda state, w=rb: self.set_sweep_type(w.sweep_type)) hbox1.addWidget(rb) self._sweep_type_widgets[rb.sweep_type] = rb rb = QRadioButton("Phase") type_group.addButton(rb) rb.sweep_type = ad9913.SweepType.PHASE rb.toggled.connect(lambda state, w=rb: self.set_sweep_type(w.sweep_type)) hbox1.addWidget(rb) self._sweep_type_widgets[rb.sweep_type] = rb self._sweep_type_widgets[self.sweep_type].setChecked(True) fbox1.addRow(QLabel("Type:"), hbox1) # ---------------------------------------------------- # Start and end # self._start_box = QDoubleSpinBox() self._start_box.setRange(*self._limit_range) self._start_box.setDecimals(5) self._start_box.setSuffix(self._limit_suffix) self._start_box.setValue(self.start) self._start_box.valueChanged.connect(self.set_start) fbox1.addRow(QLabel("Start:"), self._start_box) self._end_box = QDoubleSpinBox() self._end_box.setRange(*self._limit_range) self._end_box.setDecimals(5) self._end_box.setSuffix(self._limit_suffix) self._end_box.setValue(self.end) self._end_box.valueChanged.connect(self.set_end) fbox1.addRow(QLabel("End:"), self._end_box) # ---------------------------------------------------- # Ramp type # hbox2 = QHBoxLayout() self._ramp_type_widgets = {} ramp_group = QButtonGroup(hbox2) rb = QRadioButton("Up") ramp_group.addButton(rb) rb.ramp_type = ad9913.SweepRampType.RAMP_UP rb.toggled.connect(lambda state, w=rb: self.set_ramp_type(w.ramp_type)) hbox2.addWidget(rb) self._ramp_type_widgets[rb.ramp_type] = rb rb = QRadioButton("Down") ramp_group.addButton(rb) rb.ramp_type = ad9913.SweepRampType.RAMP_DOWN rb.toggled.connect(lambda state, w=rb: self.set_ramp_type(w.ramp_type)) hbox2.addWidget(rb) self._ramp_type_widgets[rb.ramp_type] = rb rb = QRadioButton("BiDir.") ramp_group.addButton(rb) rb.ramp_type = ad9913.SweepRampType.RAMP_BIDIR rb.toggled.connect(lambda state, w=rb: self.set_ramp_type(w.ramp_type)) hbox2.addWidget(rb) self._ramp_type_widgets[rb.ramp_type] = rb rb = QRadioButton("Sweep Off") ramp_group.addButton(rb) rb.ramp_type = ad9913.SweepRampType.SWEEP_OFF rb.toggled.connect(lambda state, w=rb: self.set_ramp_type(w.ramp_type)) hbox2.addWidget(rb) self._ramp_type_widgets[rb.ramp_type] = rb self._ramp_type_widgets[self.ramp_type].setChecked(True) fbox1.addRow(QLabel("Ramp:"), hbox2) # ---------------------------------------------------- # Step # self._rising_step_box = QDoubleSpinBox() self._rising_step_box.setRange(*self._step_range) self._rising_step_box.setDecimals(5) self._rising_step_box.setSuffix(self._limit_suffix) self._rising_step_box.setValue(self.rising_step) self._rising_step_box.valueChanged.connect(self.set_rising_step) fbox1.addRow(QLabel("Rising Step:"), self._rising_step_box) self._falling_step_box = QDoubleSpinBox() self._falling_step_box.setRange(*self._step_range) self._falling_step_box.setDecimals(5) self._falling_step_box.setSuffix(self._limit_suffix) self._falling_step_box.setValue(self.falling_step) self._falling_step_box.valueChanged.connect(self.set_falling_step) fbox1.addRow(QLabel("Falling Step:"), self._falling_step_box) # ---------------------------------------------------- # Rate # self._rise_rate_box = QDoubleSpinBox() self._rise_rate_box.setRange(*self._rate_range) self._rise_rate_box.setDecimals(3) self._rise_rate_box.setSuffix(self._rate_suffix) self._rise_rate_box.setValue(self.rising_rate) self._rise_rate_box.valueChanged.connect(self.set_rising_rate) fbox1.addRow(QLabel("Rise Rate:"), self._rise_rate_box) self._fall_rate_box = QDoubleSpinBox() self._fall_rate_box.setRange(*self._rate_range) self._fall_rate_box.setDecimals(3) self._fall_rate_box.setSuffix(self._rate_suffix) self._fall_rate_box.setValue(self.falling_rate) self._fall_rate_box.valueChanged.connect(self.set_falling_rate) fbox1.addRow(QLabel("Fall Rate:"), self._fall_rate_box) hbox3 = QHBoxLayout() config_btn = QPushButton("Configure") start_btn = QPushButton("Start Sweep") stop_btn = QPushButton("Stop Sweep") config_btn.clicked.connect(self.configure) start_btn.clicked.connect(self.start_sweep) stop_btn.clicked.connect(self.stop_sweep) hbox3.addStretch() hbox3.addWidget(config_btn) hbox3.addWidget(start_btn) hbox3.addWidget(stop_btn) fbox1.addRow(hbox3) self._sweep_gbox.setLayout(fbox1) vbox1.addWidget(self._sweep_gbox) # ---------------------------------------------------- # self._modulation_gbox = QGroupBox("Modulation") # vbox2.addWidget(self._modulation_gbox) bbox = QDialogButtonBox(QDialogButtonBox.Close) bbox.rejected.connect(self.reject) outer_vbox.addWidget(bbox) self.ctl.sweep_start_changed.connect(self.update_sweep_start) self.ctl.sweep_end_changed.connect(self.update_sweep_end) self.ctl.sweep_type_changed.connect(self.update_sweep_type) self.ctl.ramp_type_changed.connect(self.update_ramp_type) self.ctl.rising_step_changed.connect(self.update_rising_step) self.ctl.falling_step_changed.connect(self.update_falling_step) self.ctl.rising_rate_changed.connect(self.update_rising_rate) self.ctl.falling_rate_changed.connect(self.update_falling_rate) self.setLayout(outer_vbox)
[docs] @pyqtSlot(float) def update_sweep_start(self, start): """Update the displayed sweep start value. """ self._start_box.setValue(start)
[docs] @pyqtSlot(float) def update_sweep_end(self, end): """Update the displayed sweep end value. """ self._end_box.setValue(end)
[docs] @pyqtSlot(ad9913.SweepType) def update_sweep_type(self, st): """Update the displayed sweep type. This also sets the sweep range and suffix to the values appropriate for the sweep type. """ self._sweep_type_widgets[st].setChecked(True) self.set_sweep_range()
[docs] @pyqtSlot(ad9913.SweepRampType) def update_ramp_type(self, rt): """Update the displayed sweep ramp type. """ self._ramp_type_widgets[rt].setChecked(True)
[docs] @pyqtSlot(float) def update_rising_step(self, v): """Update the displayed sweep rising step value. """ self._rising_step_box.setValue(v)
[docs] @pyqtSlot(float) def update_falling_step(self, v): """Update the displayed sweep falling step value. """ self._falling_step_box.setValue(v)
[docs] @pyqtSlot(float) def update_rising_rate(self, v): """Update the displayed sweep rising rate value. """ self._rise_rate_box.setValue(v)
[docs] @pyqtSlot(float) def update_falling_rate(self, v): """Update the displayed sweep falling rate value. """ self._fall_rate_box.setValue(v)
@asyncSlot() async def configure(self): try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.ctl.configure_sweep, ser) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self) error_dialog.showMessage(str(se)) @asyncSlot() async def start_sweep(self): try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.ctl.start_sweep, ser) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self) error_dialog.showMessage(str(se)) @asyncSlot() async def stop_sweep(self): try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.ctl.stop_sweep, ser) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self) error_dialog.showMessage(str(se))
[docs] def reject(self): QDialog.reject(self)
[docs]class ProfilesDialog(QDialog): """ """ def __init__(self, ddsApp, ddsChan, profiles, parent=None): QDialog.__init__(self, parent) self._app = ddsApp self._chan = ddsChan self._profiles = profiles self.setWindowTitle("{} Profiles Settings".format(ddsChan.label)) self.build_ui() @property def ctl(self): return self._chan.ctl def profile_freq(self, profnum): return self._profiles[profnum][0] def set_profile_freq(self, profnum, f): self._profiles[profnum][0] = f def profile_phase(self, profnum): return self._profiles[profnum][1] def set_profile_phase(self, profnum, p): self._profiles[profnum][1] = p def profiles_enable(self, enable): if enable: self._profile_select_gbox.setEnabled(True) self._profiles_gbox.setEnabled(True) else: self._profiles_gbox.setEnabled(False) self._profile_select_gbox.setEnabled(False) @asyncSlot() async def modulation_start(self): self.profiles_enable(False) self._chan.set_modulating(True) await self._chan.modulate() @asyncSlot() async def modulation_stop(self): self._chan.set_modulating(False) self.profiles_enable(True) def build_ui(self): outer_vbox = QVBoxLayout() outer_hbox = QHBoxLayout() outer_vbox.addLayout(outer_hbox) vbox1 = QVBoxLayout() vbox2 = QVBoxLayout() outer_hbox.addLayout(vbox1) outer_hbox.addLayout(vbox2) # -------------------------------------------------------------- self._profiles_gbox = QGroupBox('Profiles') vbox1.addWidget(self._profiles_gbox) self._profile_widgets = [] fbox = QFormLayout() for profnum in range(8): hbox = QHBoxLayout() fw = QDoubleSpinBox() fw.setRange(*DDSChan.FREQ_RANGE) fw.setDecimals(5) fw.setValue(self.profile_freq(profnum)) fw.setSuffix(DDSChan.FREQ_SUFFIX) fw.valueChanged.connect( lambda fval, n=profnum: self.set_profile_freq(n, fval)) hbox.addWidget(fw) pw = QDoubleSpinBox() pw.setRange(*DDSChan.PHASE_RANGE) pw.setDecimals(2) pw.setValue(self.profile_phase(profnum)) pw.setSuffix(DDSChan.PHASE_SUFFIX) pw.valueChanged.connect( lambda pval, n=profnum: self.set_profile_phase(n, pval)) hbox.addWidget(pw) fbox.addRow(QLabel("Profile {}:".format(profnum)), hbox) self._profile_widgets.append([fw, pw]) hbox = QHBoxLayout() update_btn = QPushButton("Update") update_btn.clicked.connect(self.update) hbox.addStretch() hbox.addWidget(update_btn) fbox.addRow(hbox) self._profiles_gbox.setLayout(fbox) # -------------------------------------------------------------- self._profile_select_gbox = QGroupBox("Profile Selection") vbox2.addWidget(self._profile_select_gbox) hbox = QHBoxLayout() fbox = QFormLayout() self._enable_box = QCheckBox("") self._enable_box.setChecked(self._chan.direct_switch_enabled) self._enable_box.stateChanged.connect( self._chan.set_direct_switch_enabled) fbox.addRow(QLabel("Enable:"), self._enable_box) hbox1 = QHBoxLayout() type_group = QButtonGroup(hbox1) rb = QRadioButton("Freq.") type_group.addButton(rb) rb.profile_type = ad9913.SweepType.FREQUENCY rb.toggled.connect( lambda state, w=rb: self._chan.set_profile_type( state, w.profile_type)) self._freq_type_btn = rb hbox1.addWidget(rb) rb = QRadioButton("Phase") type_group.addButton(rb) rb.profile_type = ad9913.SweepType.PHASE rb.toggled.connect( lambda state, w=rb: self._chan.set_profile_type( state, w.profile_type)) self._phase_type_btn = rb hbox1.addWidget(rb) fbox.addRow(QLabel("Type:"), hbox1) if self._chan.profile_type == ad9913.SweepType.FREQUENCY: self._freq_type_btn.setChecked(True) else: self._phase_type_btn.setChecked(True) hbox2 = QHBoxLayout() profGroup = QButtonGroup(hbox2) self._profile_select_btns = [] for profnum in range(8): rb = QRadioButton("{}".format(profnum)) rb.profile = profnum rb.toggled.connect( lambda state, w=rb: self._chan.set_selected_profile( state, w.profile)) profGroup.addButton(rb) self._profile_select_btns.append(rb) hbox2.addWidget(rb) self._profile_select_btns[self._chan.selected_profile].setChecked(True) fbox.addRow(QLabel("Profile:"), hbox2) select_btn = QPushButton("Select") select_btn.clicked.connect(self.configure) hbox.addStretch() hbox.addWidget(select_btn) fbox.addRow(hbox) self.ctl.selected_profile_changed.connect(self.update_selected_profile) self.ctl.profile_type_changed.connect(self.update_profile_type) self.ctl.profile_freq_changed.connect(self.update_profile_freq) self.ctl.profile_phase_changed.connect(self.update_profile_phase) self._profile_select_gbox.setLayout(fbox) # -------------------------------------------------------------- self._modulation_gbox = QGroupBox("Modulation") vbox2.addWidget(self._modulation_gbox) fbox = QFormLayout() hbox1 = QHBoxLayout() mod_group = QButtonGroup(hbox1) rb = QRadioButton("PSK") mod_group.addButton(rb) rb.modulation_type = ModulationType.PHASE rb.toggled.connect( lambda state, w=rb: self._chan.set_modulation_type( state, w.modulation_type)) self._psk_type_btn = rb hbox1.addWidget(rb) rb = QRadioButton("FSK") mod_group.addButton(rb) rb.modulation_type = ModulationType.FREQUENCY rb.toggled.connect( lambda state, w=rb: self._chan.set_modulation_type( state, w.modulation_type)) self._fsk_type_btn = rb hbox1.addWidget(rb) fbox.addRow(QLabel("Modulation:"), hbox1) if self._chan.modulation_type == ModulationType.FREQUENCY: self._fsk_type_btn.setChecked(True) else: self._psk_type_btn.setChecked(True) hbox1 = QHBoxLayout() tones_group = QButtonGroup(hbox1) self._tone_btns = {} for tone in [2, 4, 8]: rb = QRadioButton("{}".format(tone)) tones_group.addButton(rb) rb.mod_tones = tone rb.toggled.connect( lambda state, w=rb: self._chan.set_modulation_tones( state, w.mod_tones)) self._tone_btns[tone] = rb hbox1.addWidget(rb) fbox.addRow(QLabel("Tones:"), hbox1) self._tone_btns[self._chan.modulation_tones].setChecked(True) hbox = QHBoxLayout() mod_start_btn = QPushButton("Start") mod_start_btn.clicked.connect(self.modulation_start) mod_stop_btn = QPushButton("Stop") mod_stop_btn.clicked.connect(self.modulation_stop) hbox.addStretch() hbox.addWidget(mod_start_btn) hbox.addWidget(mod_stop_btn) fbox.addRow(hbox) self._modulation_gbox.setLayout(fbox) # -------------------------------------------------------------- bbox = QDialogButtonBox(QDialogButtonBox.Close) outer_vbox.addWidget(bbox) bbox.rejected.connect(self.reject) # -------------------------------------------------------------- # If the channel modulation is active disable profile # selections. if self._chan.modulating: self.profiles_enable(False) self.setLayout(outer_vbox)
[docs] @pyqtSlot(int) def update_selected_profile(self, p): """Update the displayed selected profile checkboxes. """ self._profile_select_btns[p].setChecked(True)
[docs] @pyqtSlot(ad9913.SweepType) def update_profile_type(self, ptype): """Update the displayed profile type. """ if ptype == ad9913.SweepType.FREQUENCY: self._freq_type_btn.setChecked(True) else: self._phase_type_btn.setChecked(True)
[docs] @pyqtSlot(int, float) def update_profile_freq(self, profnum, f): """Update the displayed frequency for a profile. """ self._profile_widgets[profnum][0].setValue(f)
[docs] @pyqtSlot(int, float) def update_profile_phase(self, profnum, p): """Update the displayed phase for a profile. """ self._profile_widgets[profnum][1].setValue(p)
[docs] @asyncSlot() async def update(self): # Update frequency tuning and phase offset words for # each of the eight profile. try: for profnum in range(8): with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor( None, self.ctl.update_profile, ser, profnum) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self) error_dialog.showMessage(str(se))
@asyncSlot() async def configure(self): try: with create_serial(self._app.ctl_device, self._app.baudrate) as ser: loop = asyncio.get_event_loop() await loop.run_in_executor(None, self.ctl.configure_profile, ser) except serial.serialutil.SerialException as se: error_dialog = QErrorMessage(self) error_dialog.showMessage(str(se))
[docs] def reject(self): QDialog.reject(self)