First version for server&client

This commit is contained in:
Elena Arenskötter 2023-11-04 13:51:51 +01:00
parent d55027d422
commit 53774cae46
19 changed files with 2079 additions and 2 deletions

View File

@ -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]

219
src/PID.py Normal file
View File

@ -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

210
src/clientthread.py Normal file
View File

@ -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

4
src/globalvars.py Normal file
View File

@ -0,0 +1,4 @@
stopall = False
pidrun = [False, False, False, False]
output_volt = [0,0,0,0]

116
src/gui-client/channel.py Normal file
View File

@ -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"))

208
src/gui-client/channel.ui Normal file
View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>846</width>
<height>423</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>kp</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>ki</string>
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QPushButton" name="PIDenableButton">
<property name="styleSheet">
<string notr="true">background-color: rgb(255,0,0)</string>
</property>
<property name="text">
<string>PID enable</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="setpointBox">
<property name="decimals">
<number>4</number>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>22.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="kdBox">
<property name="minimum">
<double>-99.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QDoubleSpinBox" name="outputBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Setpoint (°C)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="kpBox">
<property name="minimum">
<double>-99.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>kd</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="OutputButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Set output</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="kiBox">
<property name="minimum">
<double>-99.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="2" rowspan="8">
<widget class="QWidget" name="widget" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="temp_layout"/>
</widget>
</item>
<item row="0" column="1">
<widget class="QWidget" name="widget_3" native="true">
<layout class="QHBoxLayout" name="error_layout"/>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QWidget" name="widget_4" native="true">
<layout class="QHBoxLayout" name="output_layout"/>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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"]))

View File

@ -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')

170
src/gui-client/client.py Normal file
View File

@ -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_()

View File

@ -0,0 +1,6 @@
conStatus = False
sock = 0
settings = 0
remoteSettings = True
settingsFile = './settings.json'

View File

@ -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"))

View File

@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>881</width>
<height>742</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>157</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Connection setup</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QPushButton" name="connectButton">
<property name="text">
<string>Connect</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QRadioButton" name="localRadioButton">
<property name="text">
<string>use local settings</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="QRadioButton" name="remoteRadioButton">
<property name="text">
<string>use remote settings</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>IP:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="ip_address">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>134.96.13.239</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="portField">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>2000</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Overview</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>temperatures (°C)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="out_ch1"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>PID enabled</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLineEdit" name="temp_ch2"/>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="out_ch2"/>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="out_ch3"/>
</item>
<item row="1" column="0">
<widget class="QLineEdit" name="temp_ch0"/>
</item>
<item row="4" column="2">
<widget class="QPushButton" name="enable_ch3">
<property name="styleSheet">
<string notr="true">background-color: rgb(255,0,0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLineEdit" name="temp_ch1"/>
</item>
<item row="3" column="2">
<widget class="QPushButton" name="enable_ch2">
<property name="styleSheet">
<string notr="true">background-color: rgb(255,0,0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLineEdit" name="temp_ch3"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>outputs (V)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="out_ch0"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="enable_ch0">
<property name="styleSheet">
<string notr="true">background-color: rgb(255,0,0)</string>
</property>
<property name="text">
<string/>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="enable_ch1">
<property name="styleSheet">
<string notr="true">background-color: rgb(255,0,0)</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_0">
<attribute name="title">
<string>Channel 0</string>
</attribute>
<layout class="QHBoxLayout" name="layout_ch0"/>
</widget>
<widget class="QWidget" name="tab_1">
<attribute name="title">
<string>Channel 1</string>
</attribute>
<layout class="QHBoxLayout" name="layout_ch1"/>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Channel 2</string>
</attribute>
<layout class="QHBoxLayout" name="layout_ch2"/>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Channel 3</string>
</attribute>
<layout class="QHBoxLayout" name="layout_ch3"/>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>881</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

129
src/gui-client/pin.py Normal file
View File

@ -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)

11
src/gui-client/runGui.bat Normal file
View File

@ -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

View File

@ -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}}

View File

@ -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

147
src/server.py Normal file
View File

@ -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()

112
src/spithread.py Normal file
View File

@ -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())

31
src/tcptools.py Normal file
View File

@ -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