Source code for mesycontrol.devices.mhv4

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# mesycontrol - Remote control for mesytec devices.
# Copyright (C) 2015-2016 mesytec GmbH & Co. KG <info@mesytec.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__author__ = 'Florian Lüke'
__email__  = 'f.lueke@mesytec.com'

from .. qt import pyqtSlot
from .. qt import Qt
from .. qt import QtCore
from .. qt import QtGui

import itertools
import weakref

from .. import parameter_binding as pb
from .. import util
from .. specialized_device import DeviceBase
from .. specialized_device import DeviceWidgetBase

import mhv4_profile

NUM_CHANNELS = 4

RAMP_SPEEDS = {
        0:   '5 V/s',
        1:  '25 V/s',
        2: '100 V/s',
        3: '500 V/s'
        }

TCOMP_SOURCES = {
        0: '0',
        1: '1',
        2: '2',
        3: '3',
        4: 'off'
        }

TCOMP_SOURCE_OFF = 4

NUM_TEMP_SENSORS = 4
TEMP_NO_SENSOR = 999
MAX_VOLTAGE_V  = 800.0

Polarity = mhv4_profile.Polarity

# ==========  Device ========== 
[docs]class MHV4(DeviceBase): def __init__(self, app_device, display_mode, write_mode, parent=None): super(MHV4, self).__init__(app_device, display_mode, write_mode, parent) params = ('channel%d_polarity_write' % i for i in range(NUM_CHANNELS)) self._polarity_write_addresses = [self.profile[pn].address for pn in params] def _on_hw_parameter_changed(self, address, value): super(MHV4, self)._on_hw_parameter_changed(address, value) if self.write_mode & util.HARDWARE and address in self._polarity_write_addresses: index = self.profile[address].index self.set_parameter('channel%d_voltage_write' % index, 0) # ========== GUI ==========
[docs]class WheelEventFilter(QtCore.QObject): """Event filter to filter out QEvent::Wheel events.""" def __init__(self, parent=None): super(WheelEventFilter, self).__init__(parent)
[docs] def eventFilter(self, obj, event): return event.type() == QtCore.QEvent.Wheel
[docs]class PolarityLabelBinding(pb.DefaultParameterBinding): def __init__(self, pixmaps, **kwargs): super(PolarityLabelBinding, self).__init__(**kwargs) self._pixmaps = pixmaps def _update(self, rf): try: self.target.setPixmap(self._pixmaps[int(rf)]) except Exception: pass
[docs]class ChannelEnablePolarityBinding(pb.DefaultParameterBinding): """Used for the polarity label. Disables/enables the label depending on the channels enable state.""" def __init__(self, **kwargs): super(ChannelEnablePolarityBinding, self).__init__(**kwargs) def _update(self, rf): self.target.setEnabled(int(rf))
[docs]class ChannelEnableButtonBinding(pb.DefaultParameterBinding): def __init__(self, **kwargs): super(ChannelEnableButtonBinding, self).__init__(**kwargs) self.target.clicked.connect(self._button_clicked) def _update(self, rf): is_enabled = int(rf) self.target.setChecked(is_enabled) self.target.setText("On" if is_enabled else "Off") def _button_clicked(self, checked): self._write_value(int(checked))
[docs]class ChannelWidget(QtGui.QWidget): def __init__(self, device, channel, display_mode, write_mode, parent=None): super(ChannelWidget, self).__init__(parent) util.loadUi(":/ui/mhv4_channel.ui", self) self.device = device self.channel = channel self.bindings = list() self.pb_channelstate.installEventFilter(self) sz = self.label_polarity.minimumSize() self.polarity_pixmaps = { Polarity.positive: QtGui.QPixmap(":/polarity-positive.png").scaled( sz, Qt.KeepAspectRatio, Qt.SmoothTransformation), Polarity.negative: QtGui.QPixmap(":/polarity-negative.png").scaled( sz, Qt.KeepAspectRatio, Qt.SmoothTransformation) } self._last_temperature = None self._last_tcomp_source = None self.slider_target_voltage.installEventFilter(WheelEventFilter(self)) # Voltage write self.bindings.append(pb.factory.make_binding( device=device, profile=device.profile['channel%d_voltage_write' % channel], display_mode=display_mode, write_mode=write_mode, target=self.spin_target_voltage, unit_name='volt')) self.bindings.append(pb.factory.make_binding( device=device, profile=device.profile['channel%d_voltage_write' % channel], display_mode=display_mode, write_mode=write_mode, target=self.slider_target_voltage, unit_name='volt', update_on='slider_released')) # Polarity self.bindings.append(PolarityLabelBinding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_polarity_read' % channel], device.profile['channel%d_polarity_write' % channel]), target=self.label_polarity, display_mode=display_mode, write_mode=write_mode, pixmaps=self.polarity_pixmaps)) # Channel enable self.bindings.append(ChannelEnablePolarityBinding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_enable_read' % channel], device.profile['channel%d_enable_write' % channel]), target=self.label_polarity, display_mode=display_mode, write_mode=write_mode)) self.bindings.append(ChannelEnableButtonBinding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_enable_read' % channel], device.profile['channel%d_enable_write' % channel]), target=self.pb_channelstate, display_mode=display_mode, write_mode=write_mode)) # Voltage self.bindings.append(pb.factory.make_binding( device=device, profile=device.profile['channel%d_voltage_read' % channel], target=self.lcd_voltage, display_mode=display_mode, write_mode=write_mode, unit_name='volt', precision=2)) # Voltage limit self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_voltage_limit_read' % channel], device.profile['channel%d_voltage_limit_write' % channel]), target=self.label_upper_voltage_limit, display_mode=display_mode, write_mode=write_mode, unit_name='volt').add_update_callback(self._voltage_limit_updated)) # Current self.bindings.append(pb.factory.make_binding( device=device, profile=device.profile['channel%d_current_read' % channel], target=self.lcd_current, display_mode=display_mode, write_mode=write_mode, unit_name='microamps', precision=3 ).add_update_callback(self._current_updated)) # Current limit self.bindings.append(pb.TargetlessParameterBinding( device=device, profile=device.profile['channel%d_current_limit_read' % channel], display_mode=display_mode, write_mode=write_mode ).add_update_callback(self._current_limit_updated)) # Sensors for i in range(NUM_TEMP_SENSORS): self.bindings.append(pb.TargetlessParameterBinding( device=device, profile=device.profile['sensor%d_temp_read' % i], display_mode=display_mode, write_mode=write_mode ).add_update_callback(self._sensor_temperature_changed)) # TComp source self.bindings.append(pb.TargetlessParameterBinding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_source_read' % channel], device.profile['channel%d_tcomp_source_write' % channel]), display_mode=display_mode, write_mode=write_mode ).add_update_callback(self._tcomp_source_changed)) def _voltage_limit_updated(self, rf): unit = self.device.profile[rf.result().address].get_unit('volt') voltage = unit.unit_value(int(rf)) self.spin_target_voltage.setMaximum(voltage) self.slider_target_voltage.setMaximum(voltage) self.slider_target_voltage.setTickInterval(100 if voltage > 200.0 else 10) def _current_updated(self, f_current): def done(f_current_limit): self._update_current_lcd_color(f_current, f_current_limit) self.device.get_parameter('channel%d_current_limit_read' % self.channel ).add_done_callback(done) def _current_limit_updated(self, f_current_limit): def done(f_current): self._update_current_lcd_color(f_current, f_current_limit) self.device.get_parameter('channel%d_current_read' % self.channel ).add_done_callback(done) def _update_current_lcd_color(self, f_current, f_current_limit): # Set LCD color to red if current limit is exceeded try: limit = int(f_current_limit) current = abs(int(f_current)) color = 'red' if current >= limit else 'black' css = 'QLCDNumber { color: %s; }' % color self.lcd_current.setStyleSheet(css) except (KeyError, TypeError): pass def _sensor_temperature_changed(self, rf_sensor): try: sensor_profile = self.device.profile[rf_sensor.result().address] sensor_num = sensor_profile.index except pb.ParameterUnavailable: return def tcomp_source_done(rf_source): source = int(rf_source) if source == sensor_num: self._update_temperature_display(rf_source, rf_sensor) if self.device.read_mode != util.HARDWARE: return self.device.get_parameter('channel%d_tcomp_source_read' % self.channel ).add_done_callback(tcomp_source_done) def _tcomp_source_changed(self, rf_source): source = int(rf_source) if source == TCOMP_SOURCE_OFF: self._update_temperature_display(rf_source, None) else: def sensor_read_done(rf_sensor): self._update_temperature_display(rf_source, rf_sensor) if self.device.read_mode != util.HARDWARE: return self.device.get_parameter('sensor%d_temp_read' % source ).add_done_callback(sensor_read_done) def _update_temperature_display(self, f_source, f_sensor): if f_sensor is None: self.label_temperature.clear() else: source_str = TCOMP_SOURCES[int(f_source)] try: temp_raw = int(f_sensor) except (KeyError, IndexError): temp_raw = TEMP_NO_SENSOR if temp_raw == TEMP_NO_SENSOR: text = "Temp: -, Src: %s" % source_str else: unit = self.device.profile['sensor0_temp_read'].get_unit( 'degree_celcius') temperature = unit.unit_value(temp_raw) text = "Temp: %.1f °C, Src: %s" % (temperature, source_str) self.label_temperature.setText(QtCore.QString.fromUtf8(text)) @pyqtSlot(int)
[docs] def on_slider_target_voltage_valueChanged(self, value): slider = self.slider_target_voltage slider.setToolTip("%d V" % value) if slider.isVisible(): cursor_pos = QtGui.QCursor.pos() global_pos = slider.mapToGlobal(slider.rect().topRight()) global_pos.setY(cursor_pos.y()) tooltip_event = QtGui.QHelpEvent(QtCore.QEvent.ToolTip, QtCore.QPoint(0, 0), global_pos) QtGui.QApplication.sendEvent(slider, tooltip_event)
[docs] def eventFilter(self, watched_object, event): if watched_object == self.pb_channelstate: t = event.type() c = self.pb_channelstate.isChecked() if t == QtCore.QEvent.Enter: if c: self.pb_channelstate.setText("Turn\n off ") else: self.pb_channelstate.setText("Turn\n on") elif t == QtCore.QEvent.Leave: if c: self.pb_channelstate.setText("On") else: self.pb_channelstate.setText("Off") return False
[docs]class ChannelSettingsWidget(QtGui.QWidget): def __init__(self, device, channel, display_mode, write_mode, labels_on=True, parent=None): super(ChannelSettingsWidget, self).__init__(parent) util.loadUi(":/ui/mhv4_channel_settings.ui", self) self.device = device self.channel = channel self.bindings = list() self.display_mode = display_mode self.write_mode = write_mode if not labels_on: for label in self.findChildren(QtGui.QLabel, QtCore.QRegExp("label_\\d+")): label.hide() # Voltage limit self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_voltage_limit_read' % channel], device.profile['channel%d_voltage_limit_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_actual_voltage_limit, unit_name='volt')) self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_voltage_limit_read' % channel], device.profile['channel%d_voltage_limit_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_target_voltage_limit, unit_name='volt')) # Current limit self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_current_limit_read' % channel], device.profile['channel%d_current_limit_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_actual_current_limit, unit_name='microamps')) self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_current_limit_read' % channel], device.profile['channel%d_current_limit_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_target_current_limit, unit_name='microamps')) # Polarity self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_polarity_read' % channel], device.profile['channel%d_polarity_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.combo_target_polarity ).add_update_callback(self._polarity_updated)) # TComp Slope self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_slope_read' % channel], device.profile['channel%d_tcomp_slope_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_actual_tcomp_slope, unit_name='volt_per_deg')) self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_slope_read' % channel], device.profile['channel%d_tcomp_slope_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_target_tcomp_slope, unit_name='volt_per_deg')) # TComp Offset self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_offset_read' % channel], device.profile['channel%d_tcomp_offset_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_actual_tcomp_offset, unit_name='degree_celcius')) self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_offset_read' % channel], device.profile['channel%d_tcomp_offset_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.spin_target_tcomp_offset, unit_name='degree_celcius')) # TComp Source self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['channel%d_tcomp_source_read' % channel], device.profile['channel%d_tcomp_source_write' % channel]), display_mode=display_mode, write_mode=write_mode, target=self.combo_target_tcomp_source ).add_update_callback(self._tcomp_source_updated)) def _polarity_updated(self, rf): dev = self.device.cfg if self.display_mode == util.CONFIG else self.device.hw rw = 'read' if dev is self.device.hw else 'write' n = 'channel%d_polarity_%s' % (self.channel, rw) p = self.device.profile[n] r = rf.result() if r.address == p.address: text = self.combo_target_polarity.itemData(r.value, Qt.DisplayRole).toString() self.le_actual_polarity.setText(text) def _tcomp_source_updated(self, rf): dev = self.device.cfg if self.display_mode == util.CONFIG else self.device.hw rw = 'read' if dev is self.device.hw else 'write' n = 'channel%d_tcomp_source_%s' % (self.channel, rw) p = self.device.profile[n] r = rf.result() if r.address == p.address: text = self.combo_target_tcomp_source.itemData(r.value, Qt.DisplayRole).toString() self.le_actual_tcomp_source.setText(text)
[docs]class GlobalSettingsWidget(QtGui.QWidget): def __init__(self, device, display_mode, write_mode, parent=None): super(GlobalSettingsWidget, self).__init__(parent) util.loadUi(":/ui/mhv4_global_settings.ui", self) self.device = device self.bindings = list() self.display_mode = display_mode self.write_mode = write_mode # Ramp speed self.bindings.append(pb.factory.make_binding( device=device, profile=pb.ReadWriteProfile( device.profile['ramp_speed_read'], device.profile['ramp_speed_write']), display_mode=display_mode, write_mode=write_mode, target=self.combo_target_ramp_speed ).add_update_callback(self._ramp_speed_updated)) def _ramp_speed_updated(self, rf): dev = self.device.cfg if self.display_mode == util.CONFIG else self.device.hw rw = 'read' if dev is self.device.hw else 'write' n = 'ramp_speed_%s' % rw p = self.device.profile[n] r = rf.result() if r.address == p.address: text = self.combo_target_ramp_speed.itemData(r.value, Qt.DisplayRole).toString() self.le_actual_ramp_speed.setText(text)
[docs]class MHV4Widget(DeviceWidgetBase): def __init__(self, device, display_mode, write_mode, parent=None): super(MHV4Widget, self).__init__(device, display_mode, write_mode, parent) self.channels = list() self.settings_widgets = list() # Channel controls channel_layout = QtGui.QHBoxLayout() channel_layout.setContentsMargins(4, 4, 4, 4) for i in range(NUM_CHANNELS): groupbox = QtGui.QGroupBox("Channel %d" % i, self) channel_widget = ChannelWidget(device, i, display_mode, write_mode) groupbox_layout = QtGui.QHBoxLayout(groupbox) groupbox_layout.setContentsMargins(4, 4, 4, 4) groupbox_layout.addWidget(channel_widget) channel_layout.addWidget(groupbox) self.channels.append(weakref.ref(channel_widget)) layout = QtGui.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(channel_layout) widget = QtGui.QWidget() widget.setLayout(layout) self.tab_widget.addTab(widget, device.profile.name) self.settings_bindings = self._add_settings_tab()
[docs] def get_parameter_bindings(self): bindings = list() for cw in self.channels: bindings.append(cw().bindings) bindings.extend(self.settings_bindings) return itertools.chain(*bindings)
[docs] def clear_parameter_bindings(self): for cw in self.channels: cw().bindings = list() for sw in self.settings_widgets: sw().bindings = list()
def _add_settings_tab(self): bindings = list() tabs = QtGui.QTabWidget() for i in range(NUM_CHANNELS): csw = ChannelSettingsWidget(self.device, i, self.display_mode, self.write_mode) tabs.addTab(csw, "Channel %d" % i) bindings.append(csw.bindings) self.settings_widgets.append(weakref.ref(csw)) gsw = GlobalSettingsWidget(self.device, self.display_mode, self.write_mode) tabs.addTab(gsw, "Global") bindings.append(gsw.bindings) self.settings_widgets.append(weakref.ref(gsw)) self.tab_widget.addTab(tabs, "Settings") for b in itertools.chain(*bindings): b.populate() return bindings
idc = 27 device_class = MHV4 device_ui_class = MHV4Widget profile_dict = mhv4_profile.profile_dict