diff --git a/src/MCP4822.py b/src/MCP4822.py
index 807f811..6480eef 100644
--- a/src/MCP4822.py
+++ b/src/MCP4822.py
@@ -20,7 +20,7 @@ class MCP4822:
'''
Constructor of the class, setup the GPIO pin for SPI-communication
'''
- def __init__(self, CLK_Pin, MOSI_Pin, CS_Pin, LDAC_Pin, vref=None):
+ def __init__(self, CLK_Pin, MOSI_Pin, CS_Pin, LDAC_Pin, nullVoltage=0, vref=None):
GPIO.setmode(GPIO.BCM)
self.clkPin = CLK_Pin
@@ -43,7 +43,8 @@ class MCP4822:
GPIO.output(self.mosiPin, GPIO.LOW)
GPIO.output(self.ldacPin, GPIO.LOW)
- self.write(1.29,1)
+ self.nullVoltage = nullVoltage
+ self.write(self.nullVoltage,1)
def U2dac(self,U,DAC):
#U: voltage (V) [0V-4.096V]
diff --git a/src/PID.py b/src/PID.py
new file mode 100644
index 0000000..60e0828
--- /dev/null
+++ b/src/PID.py
@@ -0,0 +1,219 @@
+'''
+Created on Tue Jan 9 2018
+
+@author: jan
+'''
+
+import time
+import globalvars
+import queue
+import zmq
+
+class PID:
+ def __init__(self, dev_num_adc, channel_num_adc, dev_num_dac, channel_num_dac, pid_num, kp=None, ki=None, kd=None, setpoint=None):
+ if kp is None:
+ self.kp = 1
+ else:
+ self.kp = float(kp)
+
+ if ki is None:
+ self.ki = 1
+ else:
+ self.ki = float(ki)
+
+ if kd is None:
+ self.kd = 1
+ else:
+ self.kd = float(kd)
+
+ if setpoint is None:
+ self.setpoint = 0.0
+ else:
+ self.setpoint = float(setpoint)
+
+ self.dev_num_adc = dev_num_adc
+ self.channel_num_adc = channel_num_adc
+ self.dev_num_dac = dev_num_dac
+ self.channel_dac = channel_num_dac
+ self.pid_num = pid_num
+
+ self.ITerm = 0
+ self.lastInput = self.setpoint
+ self.outMin = 0
+ self.outMax = 5
+ self.error = 0
+
+ self.start = time.time()
+ self.stop = 0
+
+ # Order of initialisation shouldn't matter, because zmq handel everything
+ self.context = zmq.Context(1)
+ self.SPIclient = self.context.socket(zmq.REQ)
+ self.SPIclient.connect("tcp://localhost:5555")
+
+ def computePID(self):
+ #Get ADC value
+ #self.out_queue.put(['ADC', self.dev_num, self.channel_num, self.pid_num])
+ #Input = self.in_queue.get()
+ msg = ['ADC', self.dev_num_adc, self.channel_num_adc, self.pid_num]
+ self.SPIclient.send('\t'.join(str(x) for x in msg).encode())
+ Input = float(self.SPIclient.recv().decode())
+
+ #Get elapsed time for right PID constants
+ self.stop = time.time()
+ elapsed_time = self.stop - self.start
+ self.start = time.time()
+
+ #print(type(self.ki))
+ Ki = float(self.ki) * elapsed_time
+ Kd = float(self.kd) / elapsed_time
+
+ #Calculate working variables
+ self.error = float(self.setpoint) - Input
+ dInput = Input - self.lastInput
+ self.ITerm = self.ITerm + (Ki * self.error)
+ # Check min-max for I-Term
+ if self.ITerm > self.outMax:
+ self.ITerm = self.outMax
+ elif self.ITerm < self.outMin:
+ self.ITerm = self.outMin
+
+ # Calculate PID Output
+ self.Output = float(self.kp) * self.error + self.ITerm - Kd * dInput
+ # Check min-max for output
+ if self.Output > self.outMax:
+ self.Output = self.outMax
+ elif self.Output < self.outMin:
+ self.Output = self.outMin
+
+ # Save variable for next run
+ self.lastInput = Input
+
+ # Set output value
+ #self.out_queue.put(['DAC', self.dev_num, self.channel_num, self.pid_num, self.Output])
+ msg = ['DAC', self.dev_num_dac, self.channel_num_dac, self.pid_num, self.Output]
+ self.SPIclient.send('\t'.join(str(x) for x in msg).encode())
+ ans = self.SPIclient.recv().decode()
+ #if self.in_queue.get() != 'ACK':
+ if ans != 'ACK':
+ print('Error setting DAC-value')
+
+ def setSetpoint(self, setpoint):
+ #self.ITerm = 0
+ self.setpoint = setpoint
+
+ def setKp(self,P):
+ self.kp=P
+
+ def setKi(self,I):
+ self.ki=I
+
+ def setKd(self,D):
+ self.kd=D
+
+ def setOutmax(self,outMax):
+ self.outMax = outMax
+
+ def getSetpoint(self):
+ return self.setpoint
+
+ def getError(self):
+ return self.error
+
+ def getKp(self):
+ return self.kp
+
+ def getKi(self):
+ return self.ki
+
+ def getKd(self):
+ return self.kd
+
+ def getOutput(self):
+ return self.Output
+
+ def getTemperatur(self):
+ return self.lastInput
+
+ def getRunValues(self):
+ return [self.lastInput, self.error, self.Output]
+
+
+def run(pid_obj, freq, num):
+ context = zmq.Context()
+ server = context.socket(zmq.REP)
+ port = 5556 + num
+ server.bind("tcp://*:%d" % port)
+ poller = zmq.Poller()
+ poller.register(server, zmq.POLLIN)
+
+ stop = False
+
+ while not globalvars.stopall and not stop:
+ cmd_recv = ''
+ if globalvars.pidrun[num]:
+ pid_obj.computePID()
+ #try:
+ # msg = in_queue.get(True,1/freq)
+ #except queue.Empty:
+ # msg = ['None']
+ socks = dict(poller.poll(1000))
+ if server in socks and socks[server] == zmq.POLLIN:
+ cmd_recv = server.recv_string()
+ else:
+ cmd_recv = 'None'
+ else:
+ socks = dict(poller.poll(1000))
+ if server in socks and socks[server] == zmq.POLLIN:
+ cmd_recv = server.recv_string()
+
+ msg = cmd_recv.split('\t')
+ ans = 'ACK'
+ if msg[0] == 'None':
+ pass
+ elif msg[0] == 'set_setpoint':
+ pid_obj.setSetpoint(msg[1])
+ server.send_string('ACK')
+ elif msg[0] == 'set_kp':
+ pid_obj.setKp(msg[1])
+ server.send_string('ACK')
+ elif msg[0] == 'set_ki':
+ pid_obj.setKi(msg[1])
+ server.send_string('ACK')
+ elif msg[0] == 'set_kd':
+ pid_obj.setKd(msg[1])
+ server.send_string('ACK')
+ elif msg[0] == 'set_outmax':
+ pid_obj.setOutmax(msg[1])
+ server.send_string('ACK')
+ elif msg[0] == 'get_setpoint':
+ #out_queue.put(pid_obj.getSetpoint() )
+ server.send_string(str(pid_obj.getSetpoint()))
+ elif msg[0] == 'get_error':
+ #out_queue.put(pid_obj.getError() )
+ server.send_string(str(pid_obj.getError()))
+ elif msg[0] == 'get_kp':
+ #out_queue.put(pid_obj.getKp() )
+ server.send_string(str(pid_obj.getKp()))
+ elif msg[0] == 'get_ki':
+ #out_queue.put(pid_obj.getKi() )
+ server.send_string(str(pid_obj.getKi()))
+ elif msg[0] == 'get_kd':
+ #out_queue.put(pid_obj.getKd() )
+ server.send_string(str(pid_obj.getKd()))
+ elif msg[0] == 'lock':
+ print('Starting PID')
+ server.send_string('Starting PID')
+ elif msg[0] == 'get_output':
+ #out_queue.put(pid_obj.getOutput() )
+ server.send_string(str(pid_obj.getOutput()))
+ elif msg[0] == 'get_temperatur':
+ #out_queue.put(pid_obj.getTemperatur() )
+ server.send_string(str(pid_obj.getTemperatur()))
+ elif msg[0] == 'get_run_values':
+ #out_queue.put(pid_obj.getRunValues() )
+ server.send_string(str(pid_obj.getRunValues()))
+
+ elif msg[0] == 'END':
+ server.send_string('ACK')
+ stop = True
diff --git a/src/clientthread.py b/src/clientthread.py
new file mode 100644
index 0000000..7202b57
--- /dev/null
+++ b/src/clientthread.py
@@ -0,0 +1,210 @@
+import queue
+import socket
+
+import zmq
+import globalvars
+import tcptools as TCP
+#TCP/IP client thread
+def clientthread(conn):
+ global lock,busy
+ welcom_msg = '--> Welcome to the Time-Bin-temperature controller <--\n'
+ welcom_msg = welcom_msg + ' '*(1024-len(welcom_msg))
+ conn.send(welcom_msg.encode())
+ print('\nopen thread...')
+
+ #Connect to SPI thread via zmq tcp socket
+ context = zmq.Context(1)
+ SPIclient = context.socket(zmq.REQ)
+ SPIclient.connect("tcp://localhost:5555")
+
+ #Connect to each pid threads
+ pidclient = [0]
+ context = zmq.Context(1)
+ pidclient[0] = context.socket(zmq.REQ)
+ pidclient[0].connect("tcp://localhost:5556")
+
+
+ while not globalvars.stopall:
+ try:
+ cmd_recv = conn.recv(1024)
+ except:
+ print('\nSocket broken')
+ break
+ if not cmd_recv:
+ break
+ if globalvars.stopall:
+ break
+
+ ans = ''
+
+ #split command
+ tmp = cmd_recv.decode().split('\t')
+
+ try:
+ tmp[2] = int(str2num(tmp[2]))
+ tmp[3] = str2num(tmp[3])
+ except IndexError:
+ pass
+
+ #print(tmp)
+
+ if tmp[0] == 'CMD:':
+ if tmp[1] == 'close':
+ TCP.send_msg(conn,'ANS: Bye')
+ break
+ elif tmp[1] == 'IDLE':
+ globalvars.pidrun[tmp[2]] = False
+ TCP.send_msg(conn, 'I set PID {:.0f} to {:.0f}'.format(tmp[2],False))
+ elif tmp[1] == 'LOCK':
+ globalvars.pidrun[tmp[2]] = True
+ #out_queue[tmp[2]].put(['lock'])
+ pidclient[tmp[2]].send_string('lock')
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set PID {:.0f} to {:.0f}'.format(tmp[2],True))
+ elif tmp[1] == 'SET_SETPOINT':
+ #out_queue[tmp[2]].put(['set_setpoint',tmp[3]])
+ pidclient[tmp[2]].send_string('set_setpoint\t%f' % tmp[3])
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set the setpoint for output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ elif tmp[1] == 'SET_KP':
+ #out_queue[tmp[2]].put(['set_kp',tmp[3]])
+ pidclient[tmp[2]].send_string('set_kp\t%f' % tmp[3])
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set kp for output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ elif tmp[1] == 'SET_KI':
+ #out_queue[tmp[2]].put(['set_ki',tmp[3]])
+ pidclient[tmp[2]].send_string('set_ki\t%f' % tmp[3])
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set ki for output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ elif tmp[1] == 'SET_KD':
+ #out_queue[tmp[2]].put(['set_kd',tmp[3]])
+ pidclient[tmp[2]].send_string('set_kd\t%f' % tmp[3])
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set kd for output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ elif tmp[1] == 'SET_OUTMAX':
+ #out_queue[tmp[2]].put(['set_outmax',tmp[3]])
+ pidclient[tmp[2]].send_string('set_outmax\t%f' % tmp[3])
+ pidclient[tmp[2]].recv_string()
+ TCP.send_msg(conn, 'I set maximal output voltage for output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ elif tmp[1] == 'GET_SETPOINT':
+ #out_queue[tmp[2]].put(['get_setpoint'])
+ pidclient[tmp[2]].send_string('get_setpoint')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_ERROR':
+ #out_queue[tmp[2]].put(['get_error'])
+ pidclient[tmp[2]].send_string('get_error')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_KP':
+ #out_queue[tmp[2]].put(['get_kp'])
+ pidclient[tmp[2]].send_string('get_kp')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_KI':
+ #out_queue[tmp[2]].put(['get_ki'])
+ pidclient[tmp[2]].send_string('get_ki')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_KD':
+ #out_queue[tmp[2]].put(['get_kd'])
+ pidclient[tmp[2]].send_string('get_kd')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_PID_STATUS':
+ ans = int(globalvars.pidrun[tmp[2]])
+ TCP.send_msg(conn, ans)
+ elif tmp[1] == 'GET_TEMP':
+ if globalvars.pidrun[tmp[2]]:
+ #out_queue[tmp[2]].put(['get_temperatur'])
+ pidclient[tmp[2]].send_string('get_temperatur')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ else:
+ if tmp[2] == 0:
+ dev_chan = ['ADC', 0, 0, 4]
+
+
+ #SPIQueue.put(dev_chan)
+ SPIclient.send('\t'.join(str(x) for x in dev_chan).encode())
+ ans = SPIclient.recv().decode()
+ #ans = tcpqueue.get()
+ TCP.send_msg(conn, ans)
+
+ elif tmp[1] == 'GET_RUN_VALUES':
+ if globalvars.pidrun[tmp[2]]:
+ #out_queue[tmp[2]].put(['get_run_values'])
+ pidclient[tmp[2]].send_string('get_run_values')
+ ans = eval(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ else:
+ if tmp[2] == 0:
+ dev_chan = ['ADC', 0, 0, 4]
+
+
+ #SPIQueue.put(dev_chan)
+ #ans = tcpqueue.get()
+ SPIclient.send('\t'.join(str(x) for x in dev_chan).encode())
+ ans = float(SPIclient.recv().decode())
+ #adc value, 0, output voltage
+ TCP.send_msg(conn, [ans, 0, globalvars.output_volt[tmp[2]]])
+
+ elif tmp[1] == 'GET_VOLTAGE':
+ if globalvars.pidrun[tmp[2]]:
+ #out_queue[tmp[2]].put(['get_output'])
+ pidclient[tmp[2]].send_string('get_output')
+ ans = float(pidclient[tmp[2]].recv_string())
+ #ans = in_queue[tmp[2]].get()
+ TCP.send_msg(conn, ans)
+ globalvars.output_volt[tmp[2]] = ans
+ else:
+ TCP.send_msg(conn, globalvars.output_volt[tmp[2]])
+
+ elif tmp[1] == 'SET_VOLTAGE':
+ if tmp[2] == 0:
+ dev_chan = ['DAC', 0, 0, 4, tmp[3]]
+ elif tmp[2] == 1:
+ dev_chan = ['DAC', 0, 1, 4, tmp[3]]
+
+
+ globalvars.output_volt[tmp[2]] = tmp[3]
+
+ #SPIQueue.put(dev_chan)
+ #ans = tcpqueue.get()
+ SPIclient.send('\t'.join(str(x) for x in dev_chan).encode())
+ ans = SPIclient.recv().decode()
+ if ans == 'ACK':
+ TCP.send_msg(conn, 'I set voltage of output {:.0f} to {: f}'.format(tmp[2],tmp[3]))
+ else:
+ print('Error!')
+ else:
+ print('Not a command!')
+ #reply = reply + ' '*(1024 - len(reply))
+ #conn.send(reply.encode())
+ busy = False
+
+ busy = False
+ if not globalvars.stopall:
+ print('close thread')
+ else:
+ print('close thread because of global stop')
+ #reply = reply + ' '*(1024 - len(reply))
+ #conn.sendall(reply.encode())
+ conn.close()
+
+#Function to convert byte string to int/float
+def str2num(s):
+ try:
+ return int(s)
+ except ValueError:
+ try:
+ return float(s)
+ except ValueError:
+ return 0
diff --git a/src/globalvars.py b/src/globalvars.py
new file mode 100644
index 0000000..5966e17
--- /dev/null
+++ b/src/globalvars.py
@@ -0,0 +1,4 @@
+
+stopall = False
+pidrun = [False, False, False, False]
+output_volt = [0,0,0,0]
diff --git a/src/gui-client/channel.py b/src/gui-client/channel.py
new file mode 100644
index 0000000..a57a426
--- /dev/null
+++ b/src/gui-client/channel.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'channel.ui'
+#
+# Created by: PyQt5 UI code generator 5.14.1
+#
+# WARNING! All changes made in this file will be lost!
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(846, 423)
+ self.gridLayout = QtWidgets.QGridLayout(Form)
+ self.gridLayout.setObjectName("gridLayout")
+ spacerItem = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
+ self.gridLayout.addItem(spacerItem, 6, 1, 1, 1)
+ self.label = QtWidgets.QLabel(Form)
+ self.label.setObjectName("label")
+ self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
+ self.label_2 = QtWidgets.QLabel(Form)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
+ self.gridLayout.addItem(spacerItem1, 7, 1, 1, 1)
+ self.PIDenableButton = QtWidgets.QPushButton(Form)
+ self.PIDenableButton.setStyleSheet("background-color: rgb(255,0,0)")
+ self.PIDenableButton.setCheckable(True)
+ self.PIDenableButton.setObjectName("PIDenableButton")
+ self.gridLayout.addWidget(self.PIDenableButton, 4, 0, 1, 1)
+ self.setpointBox = QtWidgets.QDoubleSpinBox(Form)
+ self.setpointBox.setDecimals(4)
+ self.setpointBox.setSingleStep(0.01)
+ self.setpointBox.setProperty("value", 22.0)
+ self.setpointBox.setObjectName("setpointBox")
+ self.gridLayout.addWidget(self.setpointBox, 0, 1, 1, 1)
+ self.kdBox = QtWidgets.QDoubleSpinBox(Form)
+ self.kdBox.setMinimum(-99.0)
+ self.kdBox.setSingleStep(0.01)
+ self.kdBox.setProperty("value", 1.0)
+ self.kdBox.setObjectName("kdBox")
+ self.gridLayout.addWidget(self.kdBox, 3, 1, 1, 1)
+ self.outputBox = QtWidgets.QDoubleSpinBox(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.outputBox.sizePolicy().hasHeightForWidth())
+ self.outputBox.setSizePolicy(sizePolicy)
+ self.outputBox.setMaximumSize(QtCore.QSize(100, 16777215))
+ self.outputBox.setDecimals(3)
+ self.outputBox.setSingleStep(0.01)
+ self.outputBox.setObjectName("outputBox")
+ self.gridLayout.addWidget(self.outputBox, 5, 0, 1, 1)
+ self.label_4 = QtWidgets.QLabel(Form)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout.addWidget(self.label_4, 0, 0, 1, 1)
+ self.kpBox = QtWidgets.QDoubleSpinBox(Form)
+ self.kpBox.setMinimum(-99.0)
+ self.kpBox.setSingleStep(0.01)
+ self.kpBox.setProperty("value", 1.0)
+ self.kpBox.setObjectName("kpBox")
+ self.gridLayout.addWidget(self.kpBox, 1, 1, 1, 1)
+ self.label_3 = QtWidgets.QLabel(Form)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
+ self.OutputButton = QtWidgets.QPushButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.OutputButton.sizePolicy().hasHeightForWidth())
+ self.OutputButton.setSizePolicy(sizePolicy)
+ self.OutputButton.setMaximumSize(QtCore.QSize(100, 16777215))
+ self.OutputButton.setObjectName("OutputButton")
+ self.gridLayout.addWidget(self.OutputButton, 5, 1, 1, 1)
+ self.kiBox = QtWidgets.QDoubleSpinBox(Form)
+ self.kiBox.setMinimum(-99.0)
+ self.kiBox.setSingleStep(0.01)
+ self.kiBox.setProperty("value", 1.0)
+ self.kiBox.setObjectName("kiBox")
+ self.gridLayout.addWidget(self.kiBox, 2, 1, 1, 1)
+ self.widget = QtWidgets.QWidget(Form)
+ self.widget.setObjectName("widget")
+ self.gridLayout_2 = QtWidgets.QGridLayout(self.widget)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.widget_2 = QtWidgets.QWidget(self.widget)
+ self.widget_2.setObjectName("widget_2")
+ self.temp_layout = QtWidgets.QHBoxLayout(self.widget_2)
+ self.temp_layout.setObjectName("temp_layout")
+ self.gridLayout_2.addWidget(self.widget_2, 0, 0, 1, 1)
+ self.widget_3 = QtWidgets.QWidget(self.widget)
+ self.widget_3.setObjectName("widget_3")
+ self.error_layout = QtWidgets.QHBoxLayout(self.widget_3)
+ self.error_layout.setObjectName("error_layout")
+ self.gridLayout_2.addWidget(self.widget_3, 0, 1, 1, 1)
+ self.widget_4 = QtWidgets.QWidget(self.widget)
+ self.widget_4.setObjectName("widget_4")
+ self.output_layout = QtWidgets.QHBoxLayout(self.widget_4)
+ self.output_layout.setObjectName("output_layout")
+ self.gridLayout_2.addWidget(self.widget_4, 1, 0, 1, 2)
+ self.gridLayout.addWidget(self.widget, 0, 2, 8, 1)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.label.setText(_translate("Form", "kp"))
+ self.label_2.setText(_translate("Form", "ki"))
+ self.PIDenableButton.setText(_translate("Form", "PID enable"))
+ self.label_4.setText(_translate("Form", "Setpoint (°C)"))
+ self.label_3.setText(_translate("Form", "kd"))
+ self.OutputButton.setText(_translate("Form", "Set output"))
diff --git a/src/gui-client/channel.ui b/src/gui-client/channel.ui
new file mode 100644
index 0000000..46cfeea
--- /dev/null
+++ b/src/gui-client/channel.ui
@@ -0,0 +1,208 @@
+
+
+ Form
+
+
+
+ 0
+ 0
+ 846
+ 423
+
+
+
+ Form
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Minimum
+
+
+
+ 20
+ 10
+
+
+
+
+ -
+
+
+ kp
+
+
+
+ -
+
+
+ ki
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Preferred
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
+ background-color: rgb(255,0,0)
+
+
+ PID enable
+
+
+ true
+
+
+
+ -
+
+
+ 4
+
+
+ 0.010000000000000
+
+
+ 22.000000000000000
+
+
+
+ -
+
+
+ -99.000000000000000
+
+
+ 0.010000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
+ 3
+
+
+ 0.010000000000000
+
+
+
+ -
+
+
+ Setpoint (°C)
+
+
+
+ -
+
+
+ -99.000000000000000
+
+
+ 0.010000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ kd
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
+ Set output
+
+
+
+ -
+
+
+ -99.000000000000000
+
+
+ 0.010000000000000
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+
-
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui-client/channelInfoClass.py b/src/gui-client/channelInfoClass.py
new file mode 100644
index 0000000..f0cc6af
--- /dev/null
+++ b/src/gui-client/channelInfoClass.py
@@ -0,0 +1,170 @@
+from PyQt5.QtCore import QObject
+
+import numpy as np
+import ast
+import socket
+
+import tcptools as TCP
+import globalvars
+
+from channelInfoWidgetClass import channelInfoWidget
+
+class channelInfo(QObject):
+ def __init__(self, channel_num, temp_field, out_field, enable_field, tabnumber, activTabWidget, parent):
+ super(channelInfo,self).__init__(parent)
+ self.widget_obj = channelInfoWidget(self)
+ self.channel_num = channel_num
+
+ self.temp_field = temp_field
+ self.out_field = out_field
+ self.enable_field = enable_field
+
+ self.tabNumber = tabnumber
+ self.activTabWidget = activTabWidget
+
+ self.data_length = 64
+ self.temp_data = np.array([21.0] * self.data_length)
+ self.error_data = np.array([0.0] * self.data_length)
+ self.output_data = np.array([0.0] * self.data_length)
+ self.interror_data = np.array([0.0] * self.data_length)
+
+ self.temp_plot = self.widget_obj.temp_plot.plot(y=self.temp_data,clickable=True)
+ self.output_plot = self.widget_obj.output_plot.plot(y=self.output_data,clickable=True)
+ self.error_plot = self.widget_obj.error_plot.plot(y=self.error_data,clickable=True)
+
+ self.widget_obj.setpointBox.valueChanged.connect(self.setpointChanged)
+ self.widget_obj.kpBox.valueChanged.connect(self.kpChanged)
+ self.widget_obj.kiBox.valueChanged.connect(self.kiChanged)
+ self.widget_obj.kdBox.valueChanged.connect(self.kdChanged)
+ self.widget_obj.PIDenableButton.clicked.connect(self.PIDenableButtonChanged)
+ self.widget_obj.OutputButton.clicked.connect(self.OutputButtonChanged)
+
+
+ def update(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tGET_RUN_VALUES\t'+str(self.channel_num)+'\t')
+ msg = TCP.recv_msg(globalvars.sock)
+
+ #ans = str2num(msg)
+ try:
+ ans = ast.literal_eval(msg)
+ except ValueError:
+ ans = [100, 0, 0]
+
+
+ self.temp_data = np.roll(self.temp_data, -1)
+ self.temp_data[self.data_length-1] = ans[0]
+ self.temp_field.setText("{:.4f}".format(ans[0]))
+
+ self.error_data = np.roll(self.error_data, -1)
+ self.error_data[self.data_length-1] = ans[1]
+
+ self.output_data = np.roll(self.output_data, -1)
+ self.output_data[self.data_length-1] = ans[2]
+ self.out_field.setText("{:.4f}".format(ans[2]))
+
+ if self.tabNumber == self.activTabWidget.currentIndex():
+ self.temp_plot.setData(y=self.temp_data)
+ self.error_plot.setData(y=self.error_data)
+ self.output_plot.setData(y=self.output_data)
+
+
+ def setpointChanged(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tSET_SETPOINT\t'+str(self.channel_num)+'\t'+str(self.widget_obj.setpointBox.value())+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+ globalvars.settings["ch{0}".format(self.channel_num)]["setpoint"] = self.widget_obj.setpointBox.value()
+
+ def kpChanged(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tSET_KP\t'+str(self.channel_num)+'\t'+str(self.widget_obj.kpBox.value())+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+
+ def kiChanged(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tSET_KI\t'+str(self.channel_num)+'\t'+str(self.widget_obj.kiBox.value())+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+
+ def kdChanged(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tSET_KD\t'+str(self.channel_num)+'\t'+str(self.widget_obj.kdBox.value())+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+
+ def PIDenableButtonChanged(self):
+ if globalvars.conStatus:
+ if self.widget_obj.PIDenableButton.isChecked():
+ self.widget_obj.PIDenableButton.setStyleSheet("background-color: rgb(0,255,0)")
+ self.widget_obj.OutputButton.setEnabled(False)
+ TCP.send_cmd(globalvars.sock, 'CMD:\tLOCK\t'+str(self.channel_num)+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+ self.enable_field.setStyleSheet("background-color: rgb(0,255,0)")
+ else:
+ self.widget_obj.PIDenableButton.setStyleSheet("background-color: rgb(255,0,0)")
+ self.widget_obj.OutputButton.setEnabled(True)
+ TCP.send_cmd(globalvars.sock, 'CMD:\tIDLE\t'+str(self.channel_num)+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+ self.enable_field.setStyleSheet("background-color: rgb(255,0,0)")
+ else:
+ self.widget_obj.PIDenableButton.setChecked(False)
+
+ def OutputButtonChanged(self):
+ if globalvars.conStatus:
+ TCP.send_cmd(globalvars.sock, 'CMD:\tSET_VOLTAGE\t'+str(self.channel_num)+'\t'+str(self.widget_obj.outputBox.value())+'\t')
+ ans = TCP.recv_msg(globalvars.sock)
+ print(ans)
+
+ def setEnabled(self, val):
+ self.widget_obj.setEnabled(val)
+
+ def loadSettings(self):
+ if globalvars.remoteSettings:
+ TCP.send_cmd(globalvars.sock,"CMD:\tGET_SETPOINT\t{0}\t".format(self.channel_num))
+ setpoint = float(TCP.recv_msg(globalvars.sock))
+ self.widget_obj.setpointBox.blockSignals(True)
+ self.widget_obj.setpointBox.setValue(setpoint)
+ self.widget_obj.setpointBox.blockSignals(False)
+
+ TCP.send_cmd(globalvars.sock,"CMD:\tGET_KP\t{0}\t".format(self.channel_num))
+ kp = float(TCP.recv_msg(globalvars.sock))
+ self.widget_obj.kpBox.blockSignals(True)
+ self.widget_obj.kpBox.setValue(kp)
+ self.widget_obj.kpBox.blockSignals(False)
+
+ TCP.send_cmd(globalvars.sock,"CMD:\tGET_KI\t{0}\t".format(self.channel_num))
+ ki = float(TCP.recv_msg(globalvars.sock))
+ self.widget_obj.kiBox.blockSignals(True)
+ self.widget_obj.kiBox.setValue(ki)
+ self.widget_obj.kiBox.blockSignals(False)
+
+ TCP.send_cmd(globalvars.sock,"CMD:\tGET_KD\t{0}\t".format(self.channel_num))
+ kd = float(TCP.recv_msg(globalvars.sock))
+ self.widget_obj.kdBox.blockSignals(True)
+ self.widget_obj.kdBox.setValue(kd)
+ self.widget_obj.kdBox.blockSignals(False)
+
+ TCP.send_cmd(globalvars.sock,"CMD:\tGET_PID_STATUS\t{0}\t".format(self.channel_num))
+ enabled = int(TCP.recv_msg(globalvars.sock))
+ print(enabled)
+ self.widget_obj.PIDenableButton.setChecked(enabled)
+ if self.widget_obj.PIDenableButton.isChecked():
+ self.widget_obj.PIDenableButton.setStyleSheet("background-color: rgb(0,255,0)")
+ self.widget_obj.OutputButton.setEnabled(False)
+ self.enable_field.setStyleSheet("background-color: rgb(0,255,0)")
+ else:
+ self.widget_obj.PIDenableButton.setStyleSheet("background-color: rgb(255,0,0)")
+ self.widget_obj.OutputButton.setEnabled(True)
+ self.enable_field.setStyleSheet("background-color: rgb(255,0,0)")
+
+ else:
+ #Set settings from file and trigger sending to server
+ self.widget_obj.setpointBox.setValue(float(globalvars.settings["ch{0}".format(self.channel_num)]["setpoint"]))
+ self.widget_obj.kpBox.setValue(float(globalvars.settings["ch{0}".format(self.channel_num)]["kp"]))
+ self.widget_obj.kiBox.setValue(float(globalvars.settings["ch{0}".format(self.channel_num)]["ki"]))
+ self.widget_obj.kdBox.setValue(float(globalvars.settings["ch{0}".format(self.channel_num)]["kd"]))
+ self.widget_obj.PIDenableButton.setChecked(bool(globalvars.settings["ch{0}".format(self.channel_num)]["PIDenabled"]))
diff --git a/src/gui-client/channelInfoWidgetClass.py b/src/gui-client/channelInfoWidgetClass.py
new file mode 100644
index 0000000..e799884
--- /dev/null
+++ b/src/gui-client/channelInfoWidgetClass.py
@@ -0,0 +1,24 @@
+from PyQt5.QtWidgets import QWidget
+import pyqtgraph as pg
+
+from channel import Ui_Form
+
+class channelInfoWidget(QWidget, Ui_Form):
+ def __init__(self, parent):
+ QWidget.__init__(self)
+ self.setupUi(self)
+
+ self.temp_plot = pg.PlotWidget()
+ self.temp_layout.addWidget(self.temp_plot)
+ self.temp_plot.setLabel('left', "temp", units='°C')
+ self.temp_plot.setLabel('bottom', "time")#, units='V')
+
+ self.error_plot = pg.PlotWidget()
+ self.error_layout.addWidget(self.error_plot)
+ self.error_plot.setLabel('left', "error", units='°C')
+ self.error_plot.setLabel('bottom', "time")#, units='V')
+
+ self.output_plot = pg.PlotWidget()
+ self.output_layout.addWidget(self.output_plot)
+ self.output_plot.setLabel('left', "U", units='V')
+ self.output_plot.setLabel('bottom', "time")#, units='V')
diff --git a/src/gui-client/client.py b/src/gui-client/client.py
new file mode 100644
index 0000000..bb58001
--- /dev/null
+++ b/src/gui-client/client.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Created on Mon Jan 15 14:30:51 2018
+
+@author: jan
+"""
+
+import sys
+import time
+from PyQt5 import QtCore
+from PyQt5.QtWidgets import QApplication, QMainWindow
+from PyQt5.QtCore import QTimer
+
+import socket
+import os
+import json
+
+import queue
+
+from main_window import Ui_MainWindow
+
+from channelInfoClass import channelInfo
+
+import globalvars
+######################################################################
+#TCP/IP
+HOST = '' #Symbolic name
+PORT = 2000
+
+######################################################################
+
+#Function to convert byte string to int/float
+def str2num(s):
+ try:
+ return int(s)
+ except ValueError:
+ try:
+ return float(s)
+ except ValueError:
+ return 0
+
+class ApplicationWindow(QMainWindow, Ui_MainWindow):
+ def __init__(self, ):
+ QMainWindow.__init__(self)
+ self.setupUi(self)
+
+ self.tcpqueue = queue.Queue(100)
+
+ self.channel = [0]
+
+ temp_field = [self.temp_ch0]
+ out_field = [self.out_ch0]
+ enable_field = [self.enable_ch0]
+
+ #Open json settings file defined in globalvars
+ self.loadSettingsFile(globalvars.settingsFile)
+
+ for ch in range(len(self.channel)):
+ self.channel[ch] = channelInfo(ch,temp_field[ch],out_field[ch],enable_field[ch], ch, self.tabWidget ,self)
+ self.channel[ch].setEnabled(False)
+
+ self.layout_ch0.addWidget(self.channel[0].widget_obj)
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_0),globalvars.settings["ch0"]["name"])
+ #self.layout_ch1.addWidget(self.channel[1].widget_obj)
+ #self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1),globalvars.settings["ch1"]["name"])
+ #self.layout_ch2.addWidget(self.channel[2].widget_obj)
+ #self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2),globalvars.settings["ch2"]["name"])
+ #self.layout_ch3.addWidget(self.channel[3].widget_obj)
+ #self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3),globalvars.settings["ch3"]["name"])
+
+ self.ip_address.setText(globalvars.settings["defaultIP"])
+ self.portField.setText(str(globalvars.settings["defaultPort"]))
+
+ #Connect radio buttons to functions
+ self.remoteRadioButton.toggled.connect(lambda:self.settingsSelection(self.remoteRadioButton))
+ self.localRadioButton.toggled.connect(lambda:self.settingsSelection(self.localRadioButton))
+
+ # Close event to save settings
+ app.aboutToQuit.connect(self.closeEvent)
+ # Button functions
+ self.connectButton.clicked.connect(self.connectButtonChange)
+
+ self.refresh_timer = QTimer()
+ self.refresh_timer.timeout.connect(self.refresh)
+
+ def connectButtonChange(self):
+ if self.connectButton.isChecked():
+ #Open connection to server
+ self.sock = socket.socket()
+ self.address = self.ip_address.text()
+ self.port = self.portField.text()
+
+ try:
+ self.sock.connect((self.address,int(self.port)))
+ except Exception as e:
+ print("something's wrong with %s:%s. Exception is %s" % (self.address, self.port, e))
+ globalvars.conStatus = False
+ self.connectButton.setChecked(False)
+ return
+
+ try:
+ data_recv = self.sock.recv(1024)
+ print(data_recv.decode())
+ except OSError:
+ globalvars.conStatus = False
+ self.connectButton.setChecked(False)
+ print('\nSocket broken')
+ return
+
+ globalvars.conStatus = True
+ globalvars.sock = self.sock
+ print("connect to "+ self.address)
+
+ for ch in range(len(self.channel)):
+ self.channel[ch].loadSettings()
+
+ self.refresh_timer.start(500)
+
+ else:
+ globalvars.conStatus = False
+ self.refresh_timer.stop()
+ time.sleep(0.1)
+ print("disconnect from "+ self.address)
+ self.sock.close()
+
+ for ch in range(len(self.channel)):
+ self.channel[ch].setEnabled(globalvars.conStatus)
+
+ self.remoteRadioButton.setEnabled(not globalvars.conStatus)
+ self.localRadioButton.setEnabled(not globalvars.conStatus)
+
+ def refresh(self):
+ for ch in range(len(self.channel)):
+ pass
+ self.channel[ch].update()
+
+ def settingsSelection(self,b):
+ if b.objectName() == "remoteRadioButton":
+ if b.isChecked() == True:
+ globalvars.remoteSettings = True
+ else:
+ globalvars.remoteSettings = False
+
+ if b.objectName() == "localRadioButton":
+ if b.isChecked() == True:
+ globalvars.remoteSettings = False
+ else:
+ globalvars.remoteSettings = True
+
+ def loadSettingsFile(self,path):
+ with open(path) as c:
+ globalvars.settings = json.load(c)
+
+ def closeEvent(self, event):
+ globalvars.conStatus = False
+
+ print("Saving settings... ")
+ with open(globalvars.settingsFile, 'w') as f:
+ json.dump(globalvars.settings, f, ensure_ascii=True)
+ print('done.')
+ sys.exit(0)
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ main = ApplicationWindow()
+ main.setWindowTitle("Tempertur-Controller V1")
+ main.show()
+ app.exec_()
diff --git a/src/gui-client/globalvars.py b/src/gui-client/globalvars.py
new file mode 100644
index 0000000..45abc74
--- /dev/null
+++ b/src/gui-client/globalvars.py
@@ -0,0 +1,6 @@
+
+conStatus = False
+sock = 0
+settings = 0
+remoteSettings = True
+settingsFile = './settings.json'
diff --git a/src/gui-client/main_window.py b/src/gui-client/main_window.py
new file mode 100644
index 0000000..34f646c
--- /dev/null
+++ b/src/gui-client/main_window.py
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'main_window.ui'
+#
+# Created by: PyQt5 UI code generator 5.14.1
+#
+# WARNING! All changes made in this file will be lost!
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ MainWindow.setObjectName("MainWindow")
+ MainWindow.resize(881, 742)
+ self.centralwidget = QtWidgets.QWidget(MainWindow)
+ self.centralwidget.setObjectName("centralwidget")
+ self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
+ self.groupBox.setSizePolicy(sizePolicy)
+ self.groupBox.setMinimumSize(QtCore.QSize(220, 0))
+ self.groupBox.setMaximumSize(QtCore.QSize(157, 16777215))
+ self.groupBox.setObjectName("groupBox")
+ self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox)
+ self.gridLayout_3.setObjectName("gridLayout_3")
+ self.label_5 = QtWidgets.QLabel(self.groupBox)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout_3.addWidget(self.label_5, 0, 1, 1, 1)
+ self.connectButton = QtWidgets.QPushButton(self.groupBox)
+ self.connectButton.setCheckable(True)
+ self.connectButton.setObjectName("connectButton")
+ self.gridLayout_3.addWidget(self.connectButton, 5, 0, 1, 3)
+ self.localRadioButton = QtWidgets.QRadioButton(self.groupBox)
+ self.localRadioButton.setObjectName("localRadioButton")
+ self.gridLayout_3.addWidget(self.localRadioButton, 4, 0, 1, 3)
+ self.remoteRadioButton = QtWidgets.QRadioButton(self.groupBox)
+ self.remoteRadioButton.setChecked(True)
+ self.remoteRadioButton.setObjectName("remoteRadioButton")
+ self.gridLayout_3.addWidget(self.remoteRadioButton, 3, 0, 1, 3)
+ self.label = QtWidgets.QLabel(self.groupBox)
+ self.label.setObjectName("label")
+ self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1)
+ self.ip_address = QtWidgets.QLineEdit(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.ip_address.sizePolicy().hasHeightForWidth())
+ self.ip_address.setSizePolicy(sizePolicy)
+ self.ip_address.setMaximumSize(QtCore.QSize(16777215, 16777215))
+ self.ip_address.setAlignment(QtCore.Qt.AlignCenter)
+ self.ip_address.setObjectName("ip_address")
+ self.gridLayout_3.addWidget(self.ip_address, 1, 0, 1, 1)
+ self.portField = QtWidgets.QLineEdit(self.groupBox)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.portField.sizePolicy().hasHeightForWidth())
+ self.portField.setSizePolicy(sizePolicy)
+ self.portField.setMaximumSize(QtCore.QSize(50, 16777215))
+ self.portField.setObjectName("portField")
+ self.gridLayout_3.addWidget(self.portField, 1, 1, 1, 2)
+ spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.gridLayout_3.addItem(spacerItem, 6, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.groupBox, 0, 0, 1, 1)
+ self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
+ self.groupBox_2.setObjectName("groupBox_2")
+ self.gridLayout_4 = QtWidgets.QGridLayout(self.groupBox_2)
+ self.gridLayout_4.setObjectName("gridLayout_4")
+ self.label_2 = QtWidgets.QLabel(self.groupBox_2)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout_4.addWidget(self.label_2, 0, 0, 1, 1)
+ self.out_ch1 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.out_ch1.setObjectName("out_ch1")
+ self.gridLayout_4.addWidget(self.out_ch1, 2, 1, 1, 1)
+ self.label_3 = QtWidgets.QLabel(self.groupBox_2)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout_4.addWidget(self.label_3, 0, 2, 1, 1)
+ self.temp_ch2 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.temp_ch2.setObjectName("temp_ch2")
+ self.gridLayout_4.addWidget(self.temp_ch2, 3, 0, 1, 1)
+ self.out_ch2 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.out_ch2.setObjectName("out_ch2")
+ self.gridLayout_4.addWidget(self.out_ch2, 3, 1, 1, 1)
+ self.out_ch3 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.out_ch3.setObjectName("out_ch3")
+ self.gridLayout_4.addWidget(self.out_ch3, 4, 1, 1, 1)
+ self.temp_ch0 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.temp_ch0.setObjectName("temp_ch0")
+ self.gridLayout_4.addWidget(self.temp_ch0, 1, 0, 1, 1)
+ self.enable_ch3 = QtWidgets.QPushButton(self.groupBox_2)
+ self.enable_ch3.setStyleSheet("background-color: rgb(255,0,0)")
+ self.enable_ch3.setText("")
+ self.enable_ch3.setObjectName("enable_ch3")
+ self.gridLayout_4.addWidget(self.enable_ch3, 4, 2, 1, 1)
+ self.temp_ch1 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.temp_ch1.setObjectName("temp_ch1")
+ self.gridLayout_4.addWidget(self.temp_ch1, 2, 0, 1, 1)
+ self.enable_ch2 = QtWidgets.QPushButton(self.groupBox_2)
+ self.enable_ch2.setStyleSheet("background-color: rgb(255,0,0)")
+ self.enable_ch2.setText("")
+ self.enable_ch2.setObjectName("enable_ch2")
+ self.gridLayout_4.addWidget(self.enable_ch2, 3, 2, 1, 1)
+ self.temp_ch3 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.temp_ch3.setObjectName("temp_ch3")
+ self.gridLayout_4.addWidget(self.temp_ch3, 4, 0, 1, 1)
+ self.label_4 = QtWidgets.QLabel(self.groupBox_2)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout_4.addWidget(self.label_4, 0, 1, 1, 1)
+ self.out_ch0 = QtWidgets.QLineEdit(self.groupBox_2)
+ self.out_ch0.setObjectName("out_ch0")
+ self.gridLayout_4.addWidget(self.out_ch0, 1, 1, 1, 1)
+ self.enable_ch0 = QtWidgets.QPushButton(self.groupBox_2)
+ self.enable_ch0.setStyleSheet("background-color: rgb(255,0,0)")
+ self.enable_ch0.setText("")
+ self.enable_ch0.setCheckable(False)
+ self.enable_ch0.setChecked(False)
+ self.enable_ch0.setObjectName("enable_ch0")
+ self.gridLayout_4.addWidget(self.enable_ch0, 1, 2, 1, 1)
+ self.enable_ch1 = QtWidgets.QPushButton(self.groupBox_2)
+ self.enable_ch1.setStyleSheet("background-color: rgb(255,0,0)")
+ self.enable_ch1.setText("")
+ self.enable_ch1.setObjectName("enable_ch1")
+ self.gridLayout_4.addWidget(self.enable_ch1, 2, 2, 1, 1)
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.gridLayout_4.addItem(spacerItem1, 5, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.groupBox_2, 0, 1, 1, 1)
+ self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
+ self.tabWidget.setObjectName("tabWidget")
+ self.tab_0 = QtWidgets.QWidget()
+ self.tab_0.setObjectName("tab_0")
+ self.layout_ch0 = QtWidgets.QHBoxLayout(self.tab_0)
+ self.layout_ch0.setObjectName("layout_ch0")
+ self.tabWidget.addTab(self.tab_0, "")
+ self.tab_1 = QtWidgets.QWidget()
+ self.tab_1.setObjectName("tab_1")
+ self.layout_ch1 = QtWidgets.QHBoxLayout(self.tab_1)
+ self.layout_ch1.setObjectName("layout_ch1")
+ self.tabWidget.addTab(self.tab_1, "")
+ self.tab_2 = QtWidgets.QWidget()
+ self.tab_2.setObjectName("tab_2")
+ self.layout_ch2 = QtWidgets.QHBoxLayout(self.tab_2)
+ self.layout_ch2.setObjectName("layout_ch2")
+ self.tabWidget.addTab(self.tab_2, "")
+ self.tab_3 = QtWidgets.QWidget()
+ self.tab_3.setObjectName("tab_3")
+ self.layout_ch3 = QtWidgets.QHBoxLayout(self.tab_3)
+ self.layout_ch3.setObjectName("layout_ch3")
+ self.tabWidget.addTab(self.tab_3, "")
+ self.gridLayout_2.addWidget(self.tabWidget, 1, 0, 1, 2)
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QtWidgets.QMenuBar(MainWindow)
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 881, 22))
+ self.menubar.setObjectName("menubar")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QtWidgets.QStatusBar(MainWindow)
+ self.statusbar.setObjectName("statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.retranslateUi(MainWindow)
+ self.tabWidget.setCurrentIndex(0)
+ QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+ def retranslateUi(self, MainWindow):
+ _translate = QtCore.QCoreApplication.translate
+ MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
+ self.groupBox.setTitle(_translate("MainWindow", "Connection setup"))
+ self.label_5.setText(_translate("MainWindow", "Port:"))
+ self.connectButton.setText(_translate("MainWindow", "Connect"))
+ self.localRadioButton.setText(_translate("MainWindow", "use local settings"))
+ self.remoteRadioButton.setText(_translate("MainWindow", "use remote settings"))
+ self.label.setText(_translate("MainWindow", "IP:"))
+ self.ip_address.setText(_translate("MainWindow", "134.96.13.239"))
+ self.portField.setText(_translate("MainWindow", "2000"))
+ self.groupBox_2.setTitle(_translate("MainWindow", "Overview"))
+ self.label_2.setText(_translate("MainWindow", "temperatures (°C)"))
+ self.label_3.setText(_translate("MainWindow", "PID enabled"))
+ self.label_4.setText(_translate("MainWindow", "outputs (V)"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_0), _translate("MainWindow", "Channel 0"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), _translate("MainWindow", "Channel 1"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Channel 2"))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Channel 3"))
diff --git a/src/gui-client/main_window.ui b/src/gui-client/main_window.ui
new file mode 100644
index 0000000..ef194ff
--- /dev/null
+++ b/src/gui-client/main_window.ui
@@ -0,0 +1,300 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 881
+ 742
+
+
+
+ MainWindow
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 220
+ 0
+
+
+
+
+ 157
+ 16777215
+
+
+
+ Connection setup
+
+
+
-
+
+
+ Port:
+
+
+
+ -
+
+
+ Connect
+
+
+ true
+
+
+
+ -
+
+
+ use local settings
+
+
+
+ -
+
+
+ use remote settings
+
+
+ true
+
+
+
+ -
+
+
+ IP:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ 134.96.13.239
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ 2000
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+ Overview
+
+
+
-
+
+
+ temperatures (°C)
+
+
+
+ -
+
+
+ -
+
+
+ PID enabled
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ background-color: rgb(255,0,0)
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+ background-color: rgb(255,0,0)
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+ outputs (V)
+
+
+
+ -
+
+
+ -
+
+
+ background-color: rgb(255,0,0)
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+ -
+
+
+ background-color: rgb(255,0,0)
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
+
+ Channel 0
+
+
+
+
+
+ Channel 1
+
+
+
+
+
+ Channel 2
+
+
+
+
+
+ Channel 3
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui-client/pin.py b/src/gui-client/pin.py
new file mode 100644
index 0000000..a124f40
--- /dev/null
+++ b/src/gui-client/pin.py
@@ -0,0 +1,129 @@
+from random import random
+import json
+
+class InputOutputError(Exception):
+ pass
+
+conf={}
+TEST='test'
+conf[TEST]=False
+IN=1
+OUT=0
+HIGH=1
+LOW=0
+BCM=0
+BOARD=1
+pins={}
+out={}
+values={}
+
+def config(path):
+ global conf
+ with open(path) as c:
+ conf = json.load(c)
+ if not conf[TEST]:
+ global RPi
+ global GPIO
+ import RPi.GPIO as GPIO
+
+#TODO check if initial is discarded for input or error is raised
+def setup(channel,in_out,initial=HIGH):
+ global conf
+ if type(channel) is list:
+ for el in channel:
+ _setup_one(el,in_out,initial)
+ else:
+ _setup_one(channel,in_out,initial)
+
+def _setup_one(channel,in_out,initial):
+ if conf[TEST]:
+ pins[channel]=in_out
+ else:
+ if initial == HIGH:
+ initial = GPIO.HIGH
+ else:
+ initial = GPIO.LOW
+ if in_out == IN:
+ GPIO.setup(channel,GPIO.IN) # initial not a valid parameter for input, GPIO error
+ else:
+ GPIO.setup(channel,GPIO.OUT,initial=initial)
+
+def check_in_out(channel,in_out):
+ try:
+ if not pins[channel]==in_out:
+ raise InputOutputError("Wrong confuration for channel {}! You're treating an input channel as output or vice versa.")
+ except KeyError:
+ raise InputOutputError("Wrong confuration for channel {}! setup() not called for this channel before calling input() or output().")
+
+def setmode(mode):
+ if conf[TEST]:
+ pass
+ else:
+ if mode == BCM:
+ GPIO.setmode(GPIO.BCM)
+ else:
+ GPIO.setmode(GPIO.BOARD)
+
+def input(channel):
+ if conf[TEST]:
+ check_in_out(channel,IN)
+ try:
+ values[channel]
+ except KeyError:
+ return random()
+ return values[channel]
+ else:
+ return GPIO.input(channel)
+
+def output(channel,value):
+ if type(channel) is list:
+ for el in channel:
+ _output_one(el,value)
+ else:
+ _output_one(channel,value)
+
+def _output_one(channel,value):
+ if conf[TEST]:
+ check_in_out(channel,OUT)
+ out[channel]=value
+ else:
+ GPIO.output(channel,value)
+
+def set_value(channel,value):
+ values[channel]=value
+
+def get_output(channel):
+ return out[channel]
+
+def cleanup(channel=None):
+ global pins,out,values
+ if channel==None:
+ if conf[TEST]:
+ pins,out,values={},{},{}
+ else:
+ GPIO.cleanup()
+ else:
+ if type(channel) is list or type(channel) is tuple:
+ for el in channel:
+ _cleanup_one(el)
+ else:
+ _cleanup_one(channel)
+
+def _cleanup_one(channel):
+ global pins,out,values
+ if conf[TEST]:
+ if channel in values.keys() and pins[channel]==IN:
+ del values[channel]
+ elif channel in out.keys():
+ del out[channel]
+ if channel in pins.keys():
+ del pins[channel]
+ else:
+ GPIO.cleanup(channel)
+
+def setwarnings(val):
+ if conf[TEST]:
+ pass
+ else:
+ GPIO.setwarnings(val)
+
diff --git a/src/gui-client/runGui.bat b/src/gui-client/runGui.bat
new file mode 100644
index 0000000..afdbfb5
--- /dev/null
+++ b/src/gui-client/runGui.bat
@@ -0,0 +1,11 @@
+:: Anaconda3 path
+set root=C:\ProgramData\Anaconda3
+
+:: Program path
+set guiPath=C:\Users\Jan\Documents\Projekte\TimeBinControl\src\gui-client
+
+call %root%\Scripts\activate.bat %root%
+
+call cd /D %guiPath%
+
+python client.py
\ No newline at end of file
diff --git a/src/gui-client/settings.json b/src/gui-client/settings.json
new file mode 100644
index 0000000..6d62712
--- /dev/null
+++ b/src/gui-client/settings.json
@@ -0,0 +1 @@
+{"defaultIP": "134.96.13.250", "defaultPort": 2000, "ch0": {"name": "Encoder", "setpoint": 26.62, "kp": 12.0, "ki": 4.0, "kd": 0, "PIDenabled": 0, "channelEnabled": 1}, "ch1": {"name": "Decoder", "setpoint": 26.62, "kp": 12.0, "ki": 4.0, "kd": 0, "PIDenabled": 0, "channelEnabled": 1}, "ch2": {"name": "Channel 3", "setpoint": 34.05, "kp": 0.0, "ki": 0.0, "kd": 0.0, "PIDenabled": 0, "channelEnabled": 1}, "ch3": {"name": "Channel 4", "setpoint": 21.5, "kp": 0.0, "ki": 0.0, "kd": 0.0, "PIDenabled": 0, "channelEnabled": 1}}
\ No newline at end of file
diff --git a/src/gui-client/tcptools.py b/src/gui-client/tcptools.py
new file mode 100644
index 0000000..4d343f2
--- /dev/null
+++ b/src/gui-client/tcptools.py
@@ -0,0 +1,31 @@
+import struct
+import socket
+
+def send_msg(sock, msg):
+ # Prefix each message with a 4-byte length (network byte order)
+ msg = str(msg)
+ msg = struct.pack('>I', len(msg)) + msg.encode()
+ sock.sendall(msg)
+
+def send_cmd(sock, msg):
+ msg = msg + ' '*(1024 - len(msg))
+ sock.send((msg.encode()))
+
+def recv_msg(sock):
+ # Read message length and unpack it into an integer
+ raw_msglen = sock.recv(4)
+ if not raw_msglen:
+ return None
+ msglen = struct.unpack('>I', raw_msglen)[0]
+ # Read the message data
+ return recvall(sock, msglen)
+
+def recvall(sock, n):
+ # Helper function to recv n bytes or return None if EOF is hit
+ data = ''
+ while len(data.encode()) < n:
+ packet = sock.recv(n - len(data))
+ if not packet:
+ return None
+ data = packet.decode()
+ return data
diff --git a/src/server.py b/src/server.py
new file mode 100644
index 0000000..e8de77e
--- /dev/null
+++ b/src/server.py
@@ -0,0 +1,147 @@
+import sys
+import time
+import socket
+import threading
+
+import queue
+import zmq
+
+import pin as GPIO
+GPIO.config('./config.json')
+
+import MAX11270 as adc
+import mcp4822 as dac
+import PID as pid
+import tcptools as TCP
+import spithread as SPIThread
+import clientthread as clientThread
+import globalvars
+######################################################################
+#TCP/IP
+HOST = '' #Symbolic name
+PORT = 2000
+
+######################################################################
+# GPIO conf
+GPIO.setmode(GPIO.BCM)
+ADC_CLK_PIN = 21
+ADC_MOSI_PIN = 20
+ADC_MISO_PIN = 19
+ADC_CS_PIN = 17 #ADC 0 -> input 0,1
+ADC_SYNC_PIN = 27
+ADC_RSTB_PIN = 22
+
+DAC_CLK_PIN = 11
+DAC_MOSI_PIN = 10
+DAC_CS_PIN = 8
+DAC_LDAC_PIN = 5
+
+
+#----nonglobals-------
+# this is the array of threads
+t = [] # this is the array of threads
+
+######################################################################
+
+#Workaround for Windows to handle KeyboardInterrupt
+def workaround():
+ try:
+ while True: input()
+ except (KeyboardInterrupt, EOFError):
+ socket.socket().connect((HOST,PORT))
+######################################################################
+#start program
+
+# Setting up server
+lock = threading.Lock() #create lock functionalities
+print('\nCreate socket')
+sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#create socket
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
+print(' ...done')
+print('\nBind socket...')
+#bind socket
+try:
+ sock.bind((HOST,PORT))
+except socket.error as msg:
+ print(' ...Bind failed. Error Code : '+str(msg.errno)+' Message : '+ msg.strerror)
+ sock.close()
+ sys.exit()
+print(' ...done')
+print('\nOpen socket listener...')
+sock.listen(10)
+print(' ...done')
+
+# Setting up I/O
+adc0 = adc.MAX11270(ADC_CLK_PIN, ADC_MOSI_PIN, ADC_MISO_PIN, ADC_CS_PIN, ADC_SYNC_PIN, ADC_RSTB_PIN)
+
+
+dac0 = dac.MCP4822(DAC_CLK_PIN, DAC_MOSI_PIN, DAC_CS_PIN, DAC_LDAC_PIN, nullVoltage=1.29)
+
+
+pid_obj = [pid.PID(0, 0, 0, 1, 0)]
+
+
+#Start spithread
+#new_t = threading.Thread(target=SPIThread.spithread,args=([adc0, adc1], [dac0, dac1], SPIQueue, pidqueue))
+new_t = threading.Thread(target=SPIThread.spithread,args=([adc0], [dac0],))
+new_t.deamon = True
+new_t.start()
+t.append(new_t) #append to array of threads
+
+#Start pid thread
+for i in range(len(pid_obj)):
+ new_t = threading.Thread(target=pid.run,args=(pid_obj[i], 100,i))
+ new_t.deamon = True
+ new_t.start()
+ t.append(new_t) #append to array of threads
+
+#Start workaround only on Windows
+if sys.platform.startswith('win'):
+ new_t = threading.Thread(target=workaround,args=())
+ new_t.deamon = True
+ new_t.start()
+ t.append(new_t) #append to array of threads
+
+print('\nStart waiting for connections...')
+while True:
+ conn = None
+ try:
+ #wait to accept a connection - blocking call
+ print('\nWait for connection...')
+ conn, addr = sock.accept()
+ print('Connected with ' + addr[0] + ':' + str(addr[1]))
+ print('\nOpen client thread...')
+ new_t = threading.Thread(target=clientThread.clientthread,args=(conn,))
+ new_t.deamon = True
+ new_t.start()
+ t.append(new_t) #append to array of threads
+ print(' ...done')
+ print('\nCleanup threads...')
+ t = [this_t for this_t in t if this_t.is_alive()]#cleanup t
+ print(' ...done')
+
+ except (KeyboardInterrupt,SystemExit): #Ctrl + break for keyboard interrupt (windows)
+ print('\nKeyboard interrupt...')
+ print('\nCleanup threads...')
+ t = [this_t for this_t in t if this_t.is_alive()]#cleanup t
+ print(' ...done')
+ print('\nClosing socket...')
+ sock.close()
+ print(' ...done')
+ print('\nWaiting for threads to close...')
+ globalvars.stopall = True
+
+ #Send end command to all threads
+ #SPIQueue.put(['END'])
+ #Connect to SPI thread via zmq tcp socket
+ context = zmq.Context()
+ SPIclient = context.socket(zmq.REQ)
+ SPIclient.connect("tcp://localhost:5555")
+ SPIclient.send("END\t".encode())
+ msg = SPIclient.recv()
+
+ for this_t in t: #wait for threads to close
+ this_t.join()
+ print(' ...done')
+ GPIO.cleanup()
+ sys.exit()
diff --git a/src/spithread.py b/src/spithread.py
new file mode 100644
index 0000000..1776d42
--- /dev/null
+++ b/src/spithread.py
@@ -0,0 +1,112 @@
+import queue
+import zmq
+import math
+import MAX11270 as adc
+import MCP4822 as dac
+
+import globalvars
+
+def adc2T(dU):
+ #constants
+ #VREF = (REF+) – (REF–)
+ #Vref = 2.5
+ #wheatstonebridge
+ # U0_____
+ # |
+ # R0
+ # Uw_____|__
+ # | |
+ # R2 R4
+ # |__dU__|
+ # | |
+ # R1 R3
+ # |______|
+ # Gnd_____|_
+ #
+ # R1 = resistance of thermistor
+ R0 = 0
+ R2 = 5100 #(Ohm)
+ R3 = 97600 #(Ohm)
+ R4 = 5100 #(Ohm)
+ U0 = 3 #(V)
+ #steinhart-Hart Coefficients 10uA
+ # NTC_A = 9.7142e-4;
+ # NTC_B = 2.3268e-4;
+ # NTC_C = 8.0591e-8;
+ #steinhart-Hart Coefficients 100uA
+ NTC_A = 9.6542e-4
+ NTC_B = 2.3356e-4
+ NTC_C = 7.7781e-8
+
+ R1 = -(R0*R2*dU - R2*R3*U0 + R0*R3*dU + R0*R4*dU + R2*R3*dU + R2*R4*dU)/(R4*U0 + R0*dU + R3*dU + R4*dU)
+ #calculate temperature
+ try:
+ T = 1/(NTC_A + NTC_B*math.log(float(R1)) + NTC_C*math.pow(math.log(float(R1)),3)) - 273.15
+ except ValueError:
+ T = 6666
+ return T
+
+#Thread for handling communication with spi devices
+#def spithread(adc, dac,in_queue,out_queue):
+def spithread(adc, dac):
+ context = zmq.Context()
+ server = context.socket(zmq.REP)
+ server.bind("tcp://*:5555")
+
+ stop = False
+ while not globalvars.stopall and not stop:
+ cmd_recv = server.recv() #Blocking call, wait for a new message
+
+ ans = 'NAK'
+ msg = cmd_recv.decode().split('\t')
+ #msg = in_queue.get() #Blocking call, waits for an element
+ try:
+ msg[1] = int(msg[1])
+ msg[2] = int(msg[2])
+ msg[3] = int(msg[3])
+ except (IndexError, ValueError):
+ pass
+
+ if msg[0] == 'ADC':
+ if msg[1] <= 1:
+ if msg[2] <= 1:
+ try:
+ T = adc2T(adc[msg[1]].read_differential())
+ except TimeoutError:
+ print("Conversion takes to long!")
+ T = 100
+ if msg[3] <= 5:
+ ans = str(T)
+ #out_queue[msg[3]].put(T)
+ else:
+ print('Error: Invalid output queue!')
+ else:
+ print('Error: Invalid device channel!')
+ else:
+ print('Error: Invalid device number!')
+
+ elif msg[0] == 'DAC':
+ if msg[1] <= 1:
+ if msg[2] <= 1:
+ if msg[2] == 1:
+ voltage = float(msg[4]) - dac[msg[1]].nullVoltage
+ T = dac[msg[1]].write(voltage,msg[2])
+ else:
+ T = dac[msg[1]].write(float(msg[4]),msg[2])
+ if msg[3] <= 5:
+ #out_queue[msg[3]].put('ACK')
+ ans = 'ACK'
+ else:
+ print('Error: Invalid output queue!')
+ else:
+ print('Error: Invalid device channel!')
+ else:
+ print('Error: Invalid device number!')
+ elif msg[0] == 'END':
+ ans = 'ACK'
+ stop = True
+ else:
+ print('Error: Invalid device!')
+
+ #zmq ensure that the answer is delivered to the right clientthread
+ server.send(ans.encode())
diff --git a/src/tcptools.py b/src/tcptools.py
new file mode 100644
index 0000000..4d343f2
--- /dev/null
+++ b/src/tcptools.py
@@ -0,0 +1,31 @@
+import struct
+import socket
+
+def send_msg(sock, msg):
+ # Prefix each message with a 4-byte length (network byte order)
+ msg = str(msg)
+ msg = struct.pack('>I', len(msg)) + msg.encode()
+ sock.sendall(msg)
+
+def send_cmd(sock, msg):
+ msg = msg + ' '*(1024 - len(msg))
+ sock.send((msg.encode()))
+
+def recv_msg(sock):
+ # Read message length and unpack it into an integer
+ raw_msglen = sock.recv(4)
+ if not raw_msglen:
+ return None
+ msglen = struct.unpack('>I', raw_msglen)[0]
+ # Read the message data
+ return recvall(sock, msglen)
+
+def recvall(sock, n):
+ # Helper function to recv n bytes or return None if EOF is hit
+ data = ''
+ while len(data.encode()) < n:
+ packet = sock.recv(n - len(data))
+ if not packet:
+ return None
+ data = packet.decode()
+ return data