From 6bbf93bf7d1ffa5af5e0971fc6c7326e3771c85c Mon Sep 17 00:00:00 2001 From: "Ryan@SOLARIS-MAC" Date: Wed, 31 Jul 2024 16:16:51 -0500 Subject: [PATCH] added README.md, complete the influx for V2 --- .gitignore | 5 ++ HVGUI.py | 211 ++++++++++++++++++++++++++++++++++++++++++++++----- HVLibrary.py | 13 ++++ README.md | 28 +++++++ test.py | 35 +++++++-- 5 files changed, 266 insertions(+), 26 deletions(-) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcf0953 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__ + +*.txt + +*.csv \ No newline at end of file diff --git a/HVGUI.py b/HVGUI.py index e8a6037..864ecae 100755 --- a/HVGUI.py +++ b/HVGUI.py @@ -3,22 +3,59 @@ import HVLibrary as hv import sys +import datetime +import csv +import socket +import time +print("================== SOLARIS HV Controller for CAEN SY4527") + +#-----------assign a port, to prevent the script run mulitple time +s = socket.socket() +host = socket.gethostname() +port = 4305 +s.bind((host,port)) + +#------ database +import influxdb_client +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS + +with open('INFLUX_TOKEN.txt', 'r') as f: + token = f.readline().replace('\n', '') + +org = "Argonne National Laboratory" +databaseIP = "http://192.168.0.200:8086" +write_client = influxdb_client.InfluxDBClient(url=databaseIP, token=token, org=org) +bucket = "CAEN_SY4527" +write_api = write_client.write_api(write_options=ASYNCHRONOUS) + +print("------- Database") +print(" IP : " + databaseIP) +print(" Buket : " + bucket) +print(" token : " + token) + +pushToDB = False + +print("--------- Generating GUI") +#------ GUI from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QCheckBox, QLineEdit, QLabel, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QMessageBox, QFileDialog, QProgressBar from PyQt6.QtCore import Qt, QThread, QTimer, QObject, pyqtSignal from functools import partial -ColStrList = ["Name", "Ch", 'On/Off', "Set V [V]", "Set I [uA]", "Out V [V]", "Out I [uA]"] - nMod = 2 nChPerMod = [48, 48] updateTime = 3 #sec +###################################### + bd = [] for k in range(nMod): bd.append(hv.Board(3 + k)) +ColStrList = ["Name", "Ch", 'On/Off', "Set V [V]", "Set I [uA]", "Out V [V]", "Out I [uA]"] +NameList = [] V0SetList = [] I0SetList = [] OnOffList = [] @@ -26,6 +63,7 @@ outVList = [] outIList = [] for k in range(nMod): + NameList.append(bd[k].GetPV('Name')) V0SetList.append(bd[k].GetPV('V0Set')) I0SetList.append(bd[k].GetPV('I0Set')) OnOffList.append(bd[k].GetPV('Pw')) @@ -37,7 +75,7 @@ class MyWindow(QMainWindow): super().__init__() self.setWindowTitle("Iseg Controller") - self.setGeometry(100, 100, 500, 200) + self.setGeometry(100, 100, 600, 200) widget = QWidget() layout = QVBoxLayout() @@ -56,6 +94,34 @@ class MyWindow(QMainWindow): self.timer.start(updateTime*1000) self.time = 0 + #=========== database and refresh time + gLayout = QGridLayout() + layout.addLayout(gLayout) + + # lbIP = QLabel("Database IP : ", self) + # lbIP.setAlignment(Qt.AlignmentFlag.AlignRight) + # gLayout.addWidget(lbIP, 0, 0) + + # self.txtIP = QLineEdit(self) + # self.txtIP.setText(IP) + # self.txtIP.textChanged.connect(partial(self.TextChange, self.txtIP)) + # self.txtIP.returnPressed.connect(partial(self.UnSetTextColor, self.txtIP)) + # gLayout.addWidget(self.txtIP, 0, 1) + + lb1 = QLabel("Refresh period [sec] :", self) + lb1.setAlignment(Qt.AlignmentFlag.AlignRight) + gLayout.addWidget(lb1, 1, 0) + + self.txtRefresh = QLineEdit(self) + self.txtRefresh.setText(str(updateTime)) + self.txtRefresh.textChanged.connect(partial(self.TextChange, self.txtRefresh)) + self.txtRefresh.returnPressed.connect(self.SetTimer) + self.txtRefresh.returnPressed.connect(partial(self.UnSetTextColor, self.txtRefresh)) + gLayout.addWidget(self.txtRefresh, 1, 1) + + self.chkDB = QCheckBox("Enable DataBase Output", self) + gLayout.addWidget(self.chkDB, 0, 2) + #=========== set tab self.tabWidget = QTabWidget(self) layout.addWidget(self.tabWidget) @@ -74,7 +140,12 @@ class MyWindow(QMainWindow): for j, lb in enumerate(ColStrList) : #------------ name if j == 0: + self.txtName[k][i].setText(NameList[k][i]) layout_tab.addWidget(self.txtName[k][i], 1+i, j) + + self.txtName[k][i].returnPressed.connect(partial(self.SetName, k, i) ) + self.txtName[k][i].returnPressed.connect(partial(self.UnSetTextColor, self.txtName[k][i])) + self.txtName[k][i].textChanged.connect(partial(self.TextChange, self.txtName[k][i])) #------------ Ch if j == 1: @@ -110,7 +181,7 @@ class MyWindow(QMainWindow): # #------------ V out if j == 5: - self.txtVOut[k][i].setText("{:.2f}".format(outVList[k][i])) + self.txtVOut[k][i].setText("{:.3f}".format(outVList[k][i])) self.txtVOut[k][i].setAlignment(Qt.AlignmentFlag.AlignRight) layout_tab.addWidget(self.txtVOut[k][i], 1+i, j) self.txtVOut[k][i].setReadOnly(True) @@ -118,21 +189,41 @@ class MyWindow(QMainWindow): # #------------ I out if j == 6: - self.txtIOut[k][i].setText("{:.2f}".format(outIList[k][i])) + self.txtIOut[k][i].setText("{:.3f}".format(outIList[k][i])) self.txtIOut[k][i].setAlignment(Qt.AlignmentFlag.AlignRight) layout_tab.addWidget(self.txtIOut[k][i], 1+i, j) self.txtIOut[k][i].setReadOnly(True) self.txtIOut[k][i].setStyleSheet("background-color: lightgrey; color: black;") - self.tabWidget.addTab(tab, "Mod-" + str(k)) + self.tabWidget.addTab(tab, "Mod-" + str(k+3)) + #============= Save setting + sLayout = QGridLayout() + layout.addLayout(sLayout) + + bLoad = QPushButton("Load", self) + bLoad.clicked.connect(self.LoadSetting) + sLayout.addWidget(bLoad, 0, 0) + + self.txtFile = QLineEdit(self) + self.txtFile.textChanged.connect(partial(self.TextChange, self.txtFile)) + sLayout.addWidget(self.txtFile, 0, 1) + + bSave = QPushButton("Save", self) + bSave.clicked.connect(self.SaveSetting) + sLayout.addWidget(bSave, 0, 3) #================================= def UnSetTextColor(self, qLineEdit : QLineEdit): qLineEdit.setStyleSheet("") def TextChange(self, qLineEdit : QLineEdit): - qLineEdit.setStyleSheet("color : darkgreen;") + qLineEdit.setStyleSheet("color : green;") + + def SetTimer(self): + sec = float(self.txtRefresh.text()) + self.timer.stop() + self.timer.start(int(sec * 1000)) #--------------------------------- def SetOnOff(self, mod, ch): @@ -147,6 +238,15 @@ class MyWindow(QMainWindow): print("mod : " + str(mod) + ", ch : " + str(ch) + " | " + str(state) + " | " + str(value)) self.chkON[mod][ch].setChecked(value == 1) + #--------------------------------- + def SetName(self, mod, ch): + value = self.txtName[mod][ch].text() + print("mod : " + str(mod) + ", ch : " + str(ch) + " | " + value) + bd[mod].Channel[ch].SetName(value) + newValue = bd[mod].Channel[ch].GetName() + NameList[mod][ch] = newValue + self.txtName[mod][ch].setText(newValue) + #--------------------------------- def SetHV(self, mod, ch): value = float(self.txtV[mod][ch].text()) @@ -168,31 +268,100 @@ class MyWindow(QMainWindow): #--------------------------------- def updateTimer(self): self.time += 1 - print(f'Time: {self.time}') - # outVList = mpod.GetAllOutputHV() - # outIList = mpod.GetAllLC() - # # print(outVList) - + # print(f'Time: {self.time}') + + outVList.clear() + outIList.clear() for k in range(2): outVList.append(bd[k].GetPV('VMon')) outIList.append(bd[k].GetPV('IMon')) - # if self.chkDB.checkState() == Qt.CheckState.Checked: - # points = [] + if self.chkDB.checkState() == Qt.CheckState.Checked: + points = [] for k in range(0, nMod): for i in range(bd[k].numCh) : - self.txtVOut[k][i].setText("{:.2f}".format(outVList[k][i])) - self.txtIOut[k][i].setText("{:.2f}".format(outIList[k][i])) + self.txtVOut[k][i].setText("{:.3f}".format(outVList[k][i])) + self.txtIOut[k][i].setText("{:.3f}".format(outIList[k][i])) - # if self.chkDB.checkState() == Qt.CheckState.Checked: - # points.append(Point("Voltage").tag("Ch",int(chList[i] + 100 * k)).field("value",float(outVList[i]))) - # points.append(Point("LeakageCurrent").tag("Ch",int(chList[i] + 100 * k)).field("value",float(outIList[i]))) + if self.chkDB.checkState() == Qt.CheckState.Checked: + points.append(Point("Voltage").tag("bd", int(k)).tag("Ch",int(i)).field("value",float(outVList[k][i]))) + points.append(Point("LeakageCurrent").tag("bd", int(k)).tag("Ch",int(i)).field("value",float(outIList[k][i]))) - # if self.chkDB.checkState() == Qt.CheckState.Checked: - # write_api.write(bucket=bucket, org=org, record=points) + if self.chkDB.checkState() == Qt.CheckState.Checked: + write_api.write(bucket=bucket, org=org, record=points) + def SaveSetting(self): + fileName = self.txtFile.text() + if fileName == "" : + msg_box = QMessageBox() + msg_box.setWindowTitle("Information") + msg_box.setText("Type file name first, then save.") + msg_box.setIcon(QMessageBox.Icon.Information) + msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) + msg_box.exec() + return + pos = fileName.rfind('.') + if pos == -1: + fileName = fileName + ".csv" + self.txtFile.setText(fileName) + outfile = open(fileName, "w") + csv_writer = csv.writer(outfile) + for k in range(0, nMod): + for i in range(bd[k].numCh) : + papap = [str(k), str(i), self.txtName[k][i].text(), self.txtV[k][i].text(),self.txtI[k][i].text()] + csv_writer.writerow( papap ) + outfile.close() + self.txtFile.setStyleSheet("") + msg_box = QMessageBox() + msg_box.setWindowTitle("Information") + msg_box.setText("Setting saved to " + fileName + " as a csv file.") + msg_box.setIcon(QMessageBox.Icon.Information) + msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) + msg_box.exec() + def LoadSetting(self): + file_path, _ = QFileDialog.getOpenFileName(self, "Open File", "", "CSV Files (*.csv);;All Files (*)") + + if file_path: + infile = open(file_path, "r") + csv_reader = csv.reader(infile) + row_count = sum(1 for row in csv_reader) + infile.seek(0) + count = 0 + for row in csv_reader: + mod = row[0] + ch = row[1] + if mod.isdigit() and ch.isdigit(): + chID = int(ch) + modID = int(mod) + + NameList[modID][chID] = row[2] + V0SetList[modID][chID] = float(row[3]) + I0SetList[modID][chID] = float(row[4]) + + for k in range(nMod): + bd[k].PutPV('Name', NameList[k]) + bd[k].PutPV('V0Set', V0SetList[k]) + bd[k].PutPV('I0Set', I0SetList[k]) + + self.txtFile.setText(file_path) + self.txtFile.setStyleSheet("") + + NameList.clear() + V0SetList.clear() + I0SetList.clear() + for k in range(0, nMod): + NameList.append(bd[k].GetPV('Name')) + V0SetList.append(bd[k].GetPV('V0Set')) + I0SetList.append(bd[k].GetPV('I0Set')) + for i in range(bd[k].numCh) : + self.txtName[k][i].setText(NameList[k][i]) + self.txtV[k][i].setText(str(V0SetList[k][i])) + self.txtI[k][i].setText(str(I0SetList[k][i])) + self.txtName[k][i].setStyleSheet("") + self.txtV[k][i].setStyleSheet("") + self.txtI[k][i].setStyleSheet("") if __name__ == "__main__": app = QApplication(sys.argv) diff --git a/HVLibrary.py b/HVLibrary.py index 3663829..e58178c 100755 --- a/HVLibrary.py +++ b/HVLibrary.py @@ -98,6 +98,9 @@ class Channel: return get_value(self.bdCh + "PDown") + def SetName(self, name : str): + set_value(self.bdCh + "Name", name) + def SetV0Set(self, volt: float): set_value(self.bdCh + "V0Set", volt) @@ -164,3 +167,13 @@ class Board: for i in range(48): pvList.append('solarisHV:' + self.id + ":" + f"{i:03d}" + ":" + PV_Name) return epics.caget_many(pvList) + + def PutPV(self, PV_Name : str, ValueList ): + pvList = [] + for i in range(48): + pvList.append('solarisHV:' + self.id + ":" + f"{i:03d}" + ":" + PV_Name) + + if len(ValueList) == len(pvList): + epics.caput_many(pvList, ValueList) + else: + print("PutPV: Value List size does not match channel number") diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fd1998 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# About + +This is a HV controller for the python Qt6 GUI for CAEN SY4527 + A4528 CPU module. It communicate the built-in EPICS server to control and retrive parameters. It can also push the current Voltage and leakage current to influxDB via network connection. + +# Require package + +in Ubuntu 20+ + + ~>sudo apt install python3-pyqt6 snmp snmp-mibs-downloader curl python3-pip + ~>python3 -m pip install pyqt6 influxdb-client + +# Code + +### HVLibrary.py + +this is the library to setup the basic of epics. User need to modify the IP and hostName. + +### HVGUI.py + +this is the GUI code. + +# DataBase connection + +The program use the influxdb_client to push data to a influx database. In influxDB V2, a token is needed for secure connection. For security, a token should be save in INFLUX_TOKEN.txt, the program will load it for token. The IP address, org, bucket are hardcoded, so please change it as needed. + +# Usage + + ~>./HVGUI.py \ No newline at end of file diff --git a/test.py b/test.py index c2fc236..dbf0974 100755 --- a/test.py +++ b/test.py @@ -52,6 +52,10 @@ import HVLibrary as hv # hv.epics.caput('solarisHV:03:015:Pw', 1) # hv.epics.cainfo('solarisHV:03:015:Pw') +# hv.epics.cainfo('solarisHV:03:015:VMon') +# hv.epics.cainfo('solarisHV:03:015:V0Set') +# hv.epics.cainfo('solarisHV:03:015:Status') +# hv.epics.cainfo('solarisHV:03:BdStatus') # m1 = hv.epics.caget('solarisHV:03:015:Pw', use_monitor= False) @@ -66,10 +70,31 @@ import HVLibrary as hv # hv.epics.camonitor('solarisHV:03:015:Pw') -pvList = [] -for i in range(48): - pvList.append('solarisHV:03:' + f"{i:03d}" + ":VMon") +# pvList = [] +# pvValue = [] +# for i in range(10): +# pvList.append('solarisHV:03:' + f"{i:03d}" + ":V0Set") +# pvValue.append(10) -haha = hv.epics.caget_many(pvList) +# # haha = hv.epics.caget_many(pvList) +# haha = hv.epics.caput_many(pvList, pvValue) + +# print(haha) + +import influxdb_client +from influxdb_client import InfluxDBClient, Point, WritePrecision +from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS + + +ip = "192.168.0.200:8086" +write_client = influxdb_client.InfluxDBClient(url=ip) +bucket = "HV" +write_api = write_client.write_api(write_options=ASYNCHRONOUS) + + +points = [] + +points.append(Point("Voltage").tag("Bd", 3).tag("Ch", 0).field("value",float(10))) + +write_api.write(bucket=bucket, record=points) -print(haha) \ No newline at end of file