# -*- coding: utf-8 -*-
"""
This script defines a GUI class for the MiniSCT instrument, which is part of the PyLab-ML project.
The GUI allows users to interact with the MiniSCT instrument, send MQTT commands, and receive updates.
The class inherits from a base instrument GUI class and implements specific functionality for the MiniSCT instrument.
Created on Mon Jan 3 09:19:17 2022
@author: C. Jung
TODO:
Bug: no bugs known, but in work.....
"""
import sys
import qdarkstyle
import os
from PyQt5 import QtWidgets
import qtawesome as qta
from pylab_ml.gui.instruments.base_instrument import Gui as Guibase
from pylab_ml.gui.instruments.base_instrument import load_ui
# from SCT8.gui.miniSCT import _properties_ch as proberties
__author__ = "Zlin526F"
__credits__ = ["Zlin526F"]
__email__ = "Zlin526F@github"
__version__ = "0.0.3"
# TODO: in docu ch(0) but in python CH(0)
# "PE_F", drv.off/pmu.off but how works the enable
icon = qta.icon("mdi6.remote", color="green", scale_factor=1.0)
mqttcmds = {
"relais!": {
# svg-name : (None, value for connect/disconnect)
"SPST.230": (None, "termination"), # no command connect/disconnect use cmp.cfg_dif(termination) :-(
"SPST.255": (None, "FS"),
"SPST.323": (None, "PE_50BP"),
# "SPDT": (None, "PE_F"), # not yet implemented, drv.off/pmu.off but how works the enable
"SPST.252": (None, "PE_S"),
"SPST.77": (None, "PBUS_F"),
"SPST.286": (None, "PBUS_S"),
"SPST.72": (None, "ABUS0_F"),
"SPST.281": (None, "ABUS0_S"),
"SPST.67": (None, "ABUS1_F"),
"SPST.276": (None, "ABUS1_S"),
"SPST.62": (None, "CBUS_F"),
"SPST.271": (None, "CBUS_S"),
"SPST.44": (None, "GBUS_F"),
"SPST.266": (None, "GBUS_S"),
"SPST": (None, "DUT_F"),
"SPST.227": (None, "DUT_S"),
},
"commands": {
'CH': {
# svg-name: (command for the MiniSCT, range)
"relais!1": ("connect()", 1), # relais!1 means: this command have to translate with the dictionary 'relais'
"relais!2": ("disconnect()", 0), # todo: do we need in LIne 157: self.mqtt_cmds.append(f"{svgfile}{channel}.{cmd.split('!')[0]}")
# drv
# "force_state": ("drv.force_state", "Force_state"),
# "vdh": ("drv.vdh", [-2.0, 22.0]),
# "vdl": ("drv.vdl", [-3.0, 18.0]),
# "slew_pos": ("drv.slew_pos", [0.1, 1.0]),
# "slew_neg": ("drv.slew_neg", [0.1, 1.0]),
# cmp
# "vch": ("cmp.vch", [-4.0, 18.0]),
# "vcl": ("cmp.vcl", [-4.0, 18.0]),
# "termination": ("cmp.termination", "Termination"),
# "read_state": ("cmp.read_state", None), # in docu get_state ? -> ask
# "mux": ("cmp.mux", "Mux_out"), # cfg_cpu(mux_out , dig_level) mux_out = CA, CB, CA|CB
},
'dps': {
# "v_range": ("v_range", [-2.0, 2.0]) # not implemented yet
}
}
}
[docs]
class Gui(Guibase):
"""
MiniSCT Gui.
Inherited from base_instrument
status
"""
_states = {
"connect": [
"connect",
"color: rgb(0, 255, 0)",
], # overwrite the definitions in base_instrument
"disconnect": ["disconnect", "color: rgb(255, 0, 0)"],
"on": ["running", "color: rgb(0, 255, 0)"],
"off": ["waiting for start...", "color: palette(HighlightedText)"],
}
_color_display_enable = (
"color: rgb(0, 255, 255);background-color: rgb(0, 0, 0);"
)
_color_display_disable = "color: #d9d9d9;background-color: rgb(0, 0, 0);"
_maxTabs = 9
mqtt_cmds = []
[docs]
def __init__(self, parent, name="miniSCT", parentwindow=None, channel=None):
"""
Initialize the GUI for the MiniSCT instrument.
Parameters
----------
parent: object
The parent widget for this GUI.
name: str, optional
The name of the instrument. Default is "miniSCT".
parentwindow: object, optional
The parent window for this GUI. Default is None.
channel: int, optional
The channel number. Default is None.
"""
super().__init__(
grandparent=parent, name=name, parentwindow=parentwindow
)
self.mydir = os.path.dirname(__file__) + os.sep
self.myframe = load_ui(self.gui.myframe, self.mydir + "svg.ui")
self.mqtt2svgelement = []
self.myadjustUI()
self.gui.closeEvent = self.close
self.parent = parent
self.mqtt_initlist = (
[]
) # all attributes do you need for the initialisation from mqtt. Widgets start with MQTT will add automaticaly'
# self.gui.myframe.setEnabled(True)
[docs]
def myadjustUI(self):
"""Adjust the GUI elements for the MiniSCT instrument."""
# self.myframe.QTab.setMovable(True)
# self.myframe.QTab.setTabsClosable(True)
svgfiles = {"CH": ("sct8_v7_pdc", 2), "dps": ("sct8_v7_dps", 1)}
self.chtabs = []
dirname = os.path.dirname(__file__)
for svgfile in svgfiles:
for index in range(0, svgfiles[svgfile][1]):
self.chtabs.append(QtWidgets.QWidget())
tabname = (
f"{svgfile}{index}"
if svgfiles[svgfile][1] > 1
else svgfile
)
channel = index if svgfiles[svgfile][1] > 1 else None
self.myframe.QTab.addTab(self.chtabs[-1], tabname)
svgWidget = self.mqttConnectSVGWidget(
f"{dirname}/{svgfiles[svgfile][0]}.svg",
svgfile,
channel=channel,
)
svgWidget.setTextColor(
"#c00000"
) # find all Text elements with this color, listen to getattribute
svgWidget.setclickableText() # make the element clickable, now the element also listen to setattr
svgWidget.setAllsvgElementsWTxt(
"SPST"
) # search for all elements which has an text item 'SPST' -> this are the switches
svgWidget.setElementSvg(
"SWITCH", f"{dirname}/sw0.svg", f"{dirname}/sw1.svg"
)
svgWidget.setclickableElement() # make the elements cklickable
svgWidget.setAllsvgElementsWTxt("SPDT") # double-throw switch
svgWidget.setTextColor("#00B050")
for cmd in mqttcmds['commands'][svgfile].keys(): # Create a list with all mqtt-commands in self.mqtt_cmds
if channel is not None: # channels 0-7
if cmd.split("!")[0] in mqttcmds:
self.mqtt_cmds.append(f"{svgfile}{channel}.{cmd.split('!')[0]}!")
pass
else:
self.mqtt_cmds.append(f"{svgfile}{channel}.{cmd}")
self.mqtt2svgelement.append(f"{svgfile}{channel}.{mqttcmds['commands'][svgfile][cmd][0]}")
else:
self.mqtt2svgelement.append(f"{svgfile}.{mqttcmds['commands'][svgfile][cmd][0]}")
self.mqtt_cmds.append(f"{svgfile}.{mqttcmds['commands'][svgfile][cmd][0]}")
if self.gui.geometry().height() < svgWidget.height():
self.gui.setGeometry(
self.gui.geometry().x() + 15,
self.gui.geometry().y() + 15,
self.gui.geometry().width(),
svgWidget.height() + 100,
)
if self.gui.geometry().width() < svgWidget.width():
self.gui.setGeometry(
self.gui.geometry().x() + 15,
self.gui.geometry().y() + 15,
svgWidget.width() + 50,
self.gui.geometry().height(),
)
layout = QtWidgets.QFormLayout()
layout.addRow(svgWidget)
self.chtabs[-1].setLayout(layout)
# print(self.mqtt_cmds)
# set icons:
self.gui.runToolBar.setVisible(False)
geometry = self.gui.geometry()
wh = self.myframe.geometry()
self.gui.setWindowTitle(
f' {os.path.basename(__file__).split(".")[0]} (Version: {__version__})'
)
self.myframe.setGeometry(
geometry.x(), geometry.y(), wh.width() + 100, wh.height() + 250
)
self.gui.myframe.setEnabled(
True
) # for test, normaly it should be disabled after start,
# ======================================================
# attributes which connect to an extern call (mqtt-command)
[docs]
def mqttreceive(self, instName, msg):
"""
Common MQTT receive messages, get raw MQTT data for more information.
Parameters
----------
instName: str
The name of the instrument instance.
msg: dict
The MQTT message received.
"""
check = True
if "cmd" in tuple(msg.keys()) and msg["cmd"] in self.mqtt2svgelement: # translate mqtt command to real svg-name
oldcmd = msg["cmd"].split('.')
msg["cmd"] = self.mqtt_cmds[self.mqtt2svgelement.index(msg["cmd"])]
key = msg["cmd"].split('.')[-1]
if key.find("!") > 0:
key = key[:key.find("!")+1]
if key in mqttcmds: # is this a function? than another translate dictionary is available
payload = msg["payload"] if type(msg["payload"]) == str else msg["payload"][0]
index = [list(mqttcmds[key].values())[x][1] for x in range(0, len(mqttcmds[key].values()))].index(payload)
msg["cmd"] = f"{msg['cmd'].split('.')[0]}.{list(mqttcmds[key])[index]}"
for prefix in mqttcmds['commands'].keys(): # search for the translated values
if oldcmd[0].find(prefix) == 0:
for trcmd in mqttcmds['commands'][prefix]:
if trcmd.find(key) == 0 and mqttcmds['commands'][prefix][trcmd][0].find(oldcmd[-1]) == 0:
msg["payload"] = mqttcmds['commands'][prefix][trcmd][1]
check = False
if check:
print(f" for {key} not translation found in = {msg['cmd']} ")
print(f' {instName}.mqttreceive: translate to {msg}')
if super().mqttreceive(instName, msg, check):
ch = msg["cmd"].split(".")[0]
print(f" {instName}.mqttreceive: search for tab={ch}")
for index in range(0, self._maxTabs):
if self.myframe.QTab.tabText(index) == ch:
self.myframe.QTab.setCurrentIndex(index)
break
return
print(f" {instName}.mqttreceive: continue search ... with {msg} ")
# ======================================================================================
# definitions from 'complex' mqtt commands, if you need only a connection between the mqtt-command and a widget,
# begin the name of your widget with MQTT_
@property
def channel(self):
"""Get the channel number."""
return self.id
@channel.setter
def channel(self, msg):
self.id = msg
[docs]
def svg2mqtt(self, name, value):
"""
Overwrite from the base_instrument.svg2mqtt.
Translate svg-names to his mqtt command.
Parameters
----------
name: str
The name of the SVG element that triggered the event.
value: any
The value associated with the event, which may be used to determine the MQTT command to publish.
"""
rawname = name[name.find(".") + 1:]
if name in self.mqtt_cmds:
cmd = self.mqtt2svgelement[self.mqtt_cmds.index(name)]
elif rawname in mqttcmds["relais!"]:
cmd = name.split(".")[0]
cmd += ".connect()" if value == "1" else ".disconnect()"
value = mqttcmds["relais!"][rawname][1]
else:
print(f"svg2mqtt: {name} = {value} not implemented yet")
return
print(f"svg2mqtt: {cmd}, {value}")
self.publish(cmd, value)
# end extern calling attributes (mqtt)
# ======================================================================================
#
# connected GUI-function to buttons or menues
#
[docs]
def close(self, event=None):
"""Handle the close event for the GUI by publishing an 'off' command and then calling the base class's close method."""
self.publish("off()")
super().close(event)
if __name__ == "__main__":
from pylab_ml.common.mqtt_client import mqtt_init
# broker = 127.0.0.1'
mqttclient = mqtt_init() # prepare mqtt
mqttclient.init()
app = QtWidgets.QApplication(sys.argv)
app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
window = Gui(app)
app.exec_()
# window.scope.anim.event_source.stop()