import csv
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import os
import pandas as pd
import RPi.GPIO as GPIO
from simple_pid import PID
import smbus
import string
import sys
import threading
import time
import tkinter as tk
from tkinter import ttk
sys.path.insert(0,'/home/pi/.local/lib/python3.7/site-packages')
'''FILE & DATA HANDLING FUNCTIONS'''
#file processing
def x_axis_range():
global x_axis_sec
if x_axis == '6 hours': x_axis_sec = 21600
elif x_axis == '12 hours': x_axis_sec = 43200
elif x_axis == '24 hours': x_axis_sec = 86400
elif x_axis == '48 hours': x_axis_sec = 172800
else: x_axis_sec = 259200
return
#reads settings from file
def settings_read():
global content, ec_kp, ec_ki, ec_kd, ec_setpoint, ec_trigger, ph_kp, ph_ki, ph_kd, ph_setpoint, ph_trigger, vol_1, vol_2, vol_3, vol_4, reservoir_1, reservoir_2, reservoir_3, reservoir_4, ec, ph, temp, ec_pid_chk, ph_pid_chk, x_axis
file = open('hydro_settings.txt', 'r')
content = file.readlines() # read the content of the file opened
ec_kp = float(content[1])
ec_ki = float(content[3])
ec_kd = float(content[5])
ec_setpoint = float(content[7])
ec_trigger = float(content[9])
ph_kp = float(content[11])
ph_ki = float(content[13])
ph_kd = float(content[15])
ph_setpoint = float(content[17])
ph_trigger = float(content[19])
vol_1 = float(content[21])
vol_2 = float(content[23])
vol_3 = float(content[25])
vol_4 = float(content[27])
reservoir_1 = float(content[29])
reservoir_2 = float(content[31])
reservoir_3 = float(content[33])
reservoir_4 = float(content[35])
ec = float(content[37])
ph = float(content[39])
temp = float(content[41])
ec_pid_chk = int(content[43])
ph_pid_chk = int(content[45])
x_axis = str(content[47])
file.close()
return
def data_log_read():
global data_list, data_list_resized
# import full contents of csv file into list and remove header
with open('hydro_data_log.csv', newline='') as file:
reader = csv.reader(file)
data_list = list(reader)
data_list.pop(0)
data_list_length = len(data_list)
data_list_resized = []
# resize list to 72 hours
for i in range(data_list_length):
difference = time.time() - float(data_list[i][0])
if difference < x_axis_sec:
data_list_resized.append(data_list[i])
return
#writes settings to file
def settings_write():
ec_setpoint = current_ec_setpoint.get()
ph_setpoint = current_ph_setpoint.get()
ec_pid_chk = current_ec_chk.get()
ph_pid_chk = current_ph_chk.get()
x_axis = current_x_axis.get()
ec_kp = current_ec_kp.get()
ec_ki = current_ec_ki.get()
ec_kd = current_ec_kd.get()
ph_kp = current_ph_kp.get()
ph_ki = current_ph_ki.get()
ph_kd = current_ph_kd.get()
file = open('hydro_settings.txt', 'w')
content[1] = str(ec_kp)+'\n'
content[3] = str(ec_ki)+'\n'
content[5] = str(ec_kd)+'\n'
content[7] = str(ec_setpoint)+'\n'
content[9] = str(ec_trigger)+'\n'
content[11] = str(ph_kp)+'\n'
content[13] = str(ph_ki)+'\n'
content[15] = str(ph_kd)+'\n'
content[17] = str(ph_setpoint)+'\n'
content[19] = str(ph_trigger)+'\n'
content[21] = str(vol_1)+'\n'
content[23] = str(vol_2)+'\n'
content[25] = str(vol_3)+'\n'
content[27] = str(vol_4)+'\n'
content[29] = str(reservoir_1)+'\n'
content[31] = str(reservoir_2)+'\n'
content[33] = str(reservoir_3)+'\n'
content[35] = str(reservoir_4)+'\n'
content[37] = str(ec)+'\n'
content[39] = str(ph)+'\n'
content[41] = str(temp)+'\n'
content[43] = str(ec_pid_chk)+'\n'
content[45] = str(ph_pid_chk)+'\n'
content[47] = str(x_axis)
file.writelines(content)
file.close()
return
def settings_write_with_event(event):
x_axis_range()
ec_setpoint = current_ec_setpoint.get()
ph_setpoint = current_ph_setpoint.get()
ec_pid_chk = current_ec_chk.get()
ph_pid_chk = current_ph_chk.get()
x_axis = current_x_axis.get()
ec_kp = current_ec_kp.get()
ec_ki = current_ec_ki.get()
ec_kd = current_ec_kd.get()
ph_kp = current_ph_kp.get()
ph_ki = current_ph_ki.get()
ph_kd = current_ph_kd.get()
file = open('hydro_settings.txt', 'w')
content[1] = str(ec_kp)+'\n'
content[3] = str(ec_ki)+'\n'
content[5] = str(ec_kd)+'\n'
content[7] = str(ec_setpoint)+'\n'
content[9] = str(ec_trigger)+'\n'
content[11] = str(ph_kp)+'\n'
content[13] = str(ph_ki)+'\n'
content[15] = str(ph_kd)+'\n'
content[17] = str(ph_setpoint)+'\n'
content[19] = str(ph_trigger)+'\n'
content[21] = str(vol_1)+'\n'
content[23] = str(vol_2)+'\n'
content[25] = str(vol_3)+'\n'
content[27] = str(vol_4)+'\n'
content[29] = str(reservoir_1)+'\n'
content[31] = str(reservoir_2)+'\n'
content[33] = str(reservoir_3)+'\n'
content[35] = str(reservoir_4)+'\n'
content[37] = str(ec)+'\n'
content[39] = str(ph)+'\n'
content[41] = str(temp)+'\n'
content[43] = str(ec_pid_chk)+'\n'
content[45] = str(ph_pid_chk)+'\n'
content[47] = str(x_axis)
file.writelines(content)
file.close()
return
'''def data_log_write():
data = [[time.time(), ec, ph, temp]]
with open('hydro_data_log.csv', 'w', encoding='UTF8', newline='') as f:
writer = csv.writer(f)
writer.writerows(data) #write multiple rows
return
'''
settings_read()
x_axis_range()
data_log_read()
'''GUI VARIABLES & FUNCTIONS'''
#variables GUI
bg_color = '#000000'
widget_bg_color = '#1e1e1e'
widget_fg_color = '#ffffff'
ec_color = '#fdb813'
ph_color = '#0cb04b'
temp_color = '#ffffff'
parameters_color = '#a020f0'
box_border_color = '#ffffff'
dotted_line_color = '#ffffff'
right_pane_width = 200
left_pane_width = 600
section_height = 66
padding_right = 10
#frame function
def General_Box(root_frame, w, h, s, border_thickness, border_color):
boxframe = tk.Frame(root_frame, width = w, height = h, bg = bg_color, highlightthickness = border_thickness, highlightbackground = border_color)
boxframe.pack(side = s, fill = tk.BOTH, expand = True)
boxframe.pack_propagate(False)
return boxframe
def Initial_Box(root_frame, border_thickness, border_color):
boxframe = General_Box(root_frame, 800, section_height, tk.TOP, border_thickness, border_color)
return boxframe
def Initial_Box_4(root_frame, border_thickness, border_color):
boxframe = General_Box(root_frame, 800, 102, tk.TOP, border_thickness, border_color)
return boxframe
def Graph_Box(root_frame):
boxframe = General_Box(root_frame, left_pane_width, section_height, tk.LEFT, 0, box_border_color)
return boxframe
def Readout_Box(root_frame):
boxframe = General_Box(root_frame, right_pane_width, section_height, tk.RIGHT, 0, box_border_color)
return boxframe
def Section_Box_Med(root_frame):
boxframe = General_Box(root_frame, right_pane_width, 60, tk.TOP, 0, box_border_color)
return boxframe
def Section_Box_Small(root_frame):
boxframe = General_Box(root_frame, right_pane_width, 64, tk.TOP, 0, box_border_color)
return boxframe
def Section_Box_Small_Half(root_frame):
boxframe = General_Box(root_frame, right_pane_width*0.5, 64, tk.RIGHT, 0, box_border_color)
return boxframe
def Section_Box_XS(root_frame):
boxframe = General_Box(root_frame, right_pane_width, 32, tk.TOP, 0, box_border_color)
return boxframe
def Section_Box_XXS(root_frame):
boxframe = General_Box(root_frame, right_pane_width, 120/5, tk.TOP, 0, box_border_color)
return boxframe
def Section_Box_XXS_Half(root_frame, width):
boxframe = General_Box(root_frame, width, 120/5, tk.LEFT, 0, box_border_color)
return boxframe
#canvas functions
def Dotted_Border(root_frame, c):
f = General_Box(root_frame, 800, 1, tk.TOP, 0, box_border_color)
f.pack(fill=None, expand=False)
canvas = tk.Canvas(f, width = 800, height = 1, bg = bg_color, highlightthickness = 0)
canvas.create_line(0, 0, 800, 0, dash = (1,10), fill = c)
canvas.pack(fill = tk.BOTH, expand = True)
return canvas
#label widget functions
def Header_Label(root_frame, txt, text_color):
label = tk.Label(root_frame, text = txt, font = ('Arial Bold', 14), fg = text_color, bg = bg_color)
label.pack(side = tk.LEFT, anchor = tk.NW)
return label
def Readout_Label(root_frame, variable, text_color):
label = tk.Label(root_frame, text = str(variable), font = ('Arial Bold', 30), fg = text_color, bg = bg_color)
label.pack(side = tk.TOP, anchor = tk.W, padx = padding_right)
return label
def para_header_label(root_frame, variable):
label = tk.Label(root_frame, text = str(variable), font = ('Arial Bold', 12), fg = parameters_color, bg = bg_color)
label.pack(side = tk.TOP)
return label
def para_label(root_frame, variable):
label = tk.Label(root_frame, text = str(variable), font = ('Arial Bold', 10), fg = parameters_color, bg = bg_color)
label.pack(side = tk.LEFT)
return label
def para_vol_label(root_frame, variable1, variable2):
label = tk.Label(root_frame, text = str(variable1)+'% ('+str(variable2)+' mL) ', font = ('Arial', 10), fg = vol1_color, bg = bg_color)
label.pack(side = tk.LEFT)
return label
#spin-box widget functions
def volume_spinbox(root_frame):
spinbox = tk.Spinbox(root_frame, from_=0, to=10000, increment = 1, font = ('Arial', 12), bg = widget_bg_color, fg = widget_fg_color, width = 6, highlightthickness = 0, relief = tk.FLAT)
spinbox.pack(side = tk.RIGHT)
return spinbox
#checkbox widget functions
def parameter_checkbox(root_frame, chk_text, chk_var):
checkbox = tk.Checkbutton(root_frame, onvalue = 1, offvalue = 0, variable = chk_var, command = settings_write, text = str(chk_text), font = ('Arial Bold', 10), borderwidth = 0, bg = bg_color, fg = parameters_color, selectcolor = widget_bg_color, highlightbackground = bg_color)
checkbox.pack(side = tk.TOP, anchor = tk.NW)
return checkbox
#reservoir volume label color
def volume_color():
def volume_color_set(volume_value):
if volume_value >= 40: vol_color = '#0cb04b'
if volume_value < 40 and volume_value > 10: vol_color = '#fdb813'
if volume_value <= 10: vol_color = '#f05334'
return vol_color
global vol1_color, vol2_color, vol3_color, vol4_color
vol1_color = volume_color_set(vol1_percent)
vol2_color = volume_color_set(vol2_percent)
vol3_color = volume_color_set(vol3_percent)
vol4_color = volume_color_set(vol4_percent)
return
#graph functions
def Create_Graph(root_frame, use_colmn, _color):
if use_colmn == 'ec': use_col_num = 1
elif use_colmn == 'ph': use_col_num = 2
else: use_col_num = 3
used_col = [sub[use_col_num] for sub in data_list_resized]
used_col_int = list(map(int, used_col))
data = pd.DataFrame({use_colmn:used_col_int})
df = pd.DataFrame(data)
#figure = plt.Figure(figsize=(left_pane_width, section_height), dpi=100)
figure, ax = plt.subplots()
canvas = FigureCanvasTkAgg(figure, root_frame)
canvas.draw()
canvas.get_tk_widget().pack(expand=True, fill=tk.BOTH)
df.plot(kind = 'line', ax = ax, legend = False, color = _color, fontsize = 8, antialiased = True, linewidth = 1)
figure.patch.set_facecolor(bg_color)
ax.get_xaxis().set_ticks([])
ax.spines['left'].set_color(_color)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.tick_params(axis='y', color = _color, labelcolor = _color)
ax.set_facecolor(bg_color)
return
#button functions
def add_vol_button(root_frame):
button = tk.Button(root_frame, text = 'ADD', font = ('Arial', 8), borderwidth = 0, fg = 'white', bg = widget_bg_color, activebackground = parameters_color, highlightbackground = bg_color)
button.pack(side = tk.LEFT)
return
'''CONTROL VARIABLES & FUNCTIONS'''
#GPIO configuration
def GPIO_configuration():
global relay_magstir,relay_main_pump,relay_circulator
GPIO.setmode(GPIO.BOARD)
relay_magstir = 40
relay_main_pump = 38
relay_circulator = 36
GPIO.setup(relay_magstir, GPIO.OUT)
GPIO.setup(relay_main_pump, GPIO.OUT)
GPIO.setup(relay_circulator, GPIO.OUT)
return
#ascii value to a float
def ascii_to_float(an_ascii):
a_string = ''.join([chr(value) for value in an_ascii])
printable = set(string.printable)
a_string = ''.join(filter(lambda x: x in printable, a_string))
a_float = float(a_string)
return a_float
#variable to a list of ascii values
def variable_to_ascii(a_var):
a_string = str(a_var)
a_list = list(a_string)
l = len(a_list)
for i in range(0,l):
an_ascii_list = a_list
an_ascii_list[i] = ord(a_list[i])
return an_ascii_list
#read device from adress and convert to float
def device_read(i2c_address):
bus.write_byte(i2c_address,82)
time.sleep(0.9)
x = bus.read_i2c_block_data(i2c_address,82)
x_float = ascii_to_float(x)
return x_float
#EC temperature compensation
def ec_temp_compensated(temp):
cmd = variable_to_ascii(temp)
cmd.insert(0,44)
bus.write_i2c_block_data(ec_address,84,cmd)
ec = device_read(ec_address)
return ec
#read cycle on all sensors
def read_cycle():
global temp
global ph
global ec
temp = device_read(temp_address)
ph = device_read(ph_address)
ec = device_read(ec_address)
return
#dosing pump command: D,[mL]
def dose_volume(i2c_address,volume):
if volume < 0.5: volume = 0.5 #sets value to minimum dispense volume
cmd = variable_to_ascii(volume)
cmd.insert(0,44)
bus.write_i2c_block_data(i2c_address,68,cmd)
#pre-dosing procedure with pump reverse/bubbling
def dose_procedure():
GPIO_configuration()
os.system("xset dpms force on") #screen on
GPIO.output(relay_main_pump, GPIO.HIGH) #reservoir main pump off
GPIO.output(relay_circulator, GPIO.HIGH) #reservoir circulating pump on
cmd = variable_to_ascii(-abs(tube_volume)) #sets tube volume negative for reverse pump
cmd.insert(0,44)
bus.write_i2c_block_data(doser1,68,cmd)
bus.write_i2c_block_data(doser2,68,cmd)
bus.write_i2c_block_data(doser3,68,cmd)
bus.write_i2c_block_data(doser4,68,cmd)
#restarts hydroponic main pumping system
def run_procedure():
GPIO_configuration()
GPIO.output(relay_main_pump, GPIO.LOW) #reservoir main pump on
GPIO.output(relay_circulator, GPIO.LOW) #reservoir circulating pump off
GPIO.cleanup()
#adjusts EC
def ec_adjust_cycle():
dose_procedure()
while True:
read_cycle()
error = ec_setpoint - ec
if error < 10:
ec_pid.reset()
break
else:
output_dose = ec_pid(ec)*0.03
print('\nFert A&B: ' + '%.5s' % str(output_dose) + ' mL\n')
dose_volume(doser1,output_dose)
dose_volume(doser2,output_dose)
time.sleep(120)
#adjusts pH up
def ph_adjust_up_cycle():
dose_procedure()
while True:
read_cycle()
error = ph_setpoint - ph
if error < 0.2:
ph_pid.reset()
break
else:
output_dose = ph_pid(ph)
print('\npH up dose: ' + '%.5s' % str(output_dose) + '\n')
dose_volume(doser3,output_dose)
time.sleep(120)
#adjusts pH down
def ph_adjust_down_cycle():
dose_procedure()
while True:
read_cycle()
error = ph - ph_setpoint
if error < 0.2:
ph_pid.reset()
break
else:
output_dose = ph_pid(ph)
print('\npH down dose: ' + '%.5s' % str(output_dose) + '\n')
dose_volume(doser3,output_dose)
time.sleep(120)
#reservoir volume percent
def volume_percent():
global vol1_percent, vol2_percent, vol3_percent, vol4_percent
vol1_percent = vol_1/reservoir_1
vol2_percent = vol_2/reservoir_2
vol3_percent = vol_3/reservoir_3
vol4_percent = vol_4/reservoir_4
return
volume_percent()
volume_color()
def control():
#initialize variables
global tube_volume
tube_volume = 5 #peristaltic pump tube volume
#initialize I2C bus and addresses using hexidecimal
global bus, ph_address, ec_address, temp_address, doser1, doser2, doser3, doser4
bus = smbus.SMBus(1)
ph_address = 0x63
ec_address = 0x64
temp_address = 0x66
doser1 = 0xA
doser2 = 0xB
doser3 = 0xC
doser4 = 0xD
#PID parameters
ec_pid = PID(ec_kp, ec_ki, ec_kd, setpoint = ec_setpoint)
ec_pid_sample_time = 120
ec_pid.sample_time = ec_pid_sample_time
ec_pid.proportional_on_measurement = True
ec_pid.output_limits = (0, 160)
ph_pid = PID(ph_kp, ph_ki, ph_kd, setpoint = ph_setpoint)
ph_pid_sample_time = 120
ph_pid.sample_time = ph_pid_sample_time
ph_pid.proportional_on_measurement = True
ph_pid.output_limits = (0, 80)
#running - measuring and dosing
while True:
read_cycle()
volume_percent()
volume_color()
ec_adjust = False
ph_adjust_up = False
ph_adjust_down = False
if ec < (ec_setpoint - ec_trigger): ec_adjust = True
elif ph < (ph_setpoint - ph_trigger): ph_adjust_up = True
elif ph > (ph_setpoint + ph_trigger): ph_adjust_down = True
if ec_adjust == True:
ec_adjust_cycle()
ph_adjust_up_cycle()
run_procedure()
elif ec_adjust == False and ph_adjust_up == True:
ph_adjust_up_cycle()
run_procedure()
elif ec_adjust == False and ph_adjust_down == True:
ph_adjust_down_cycle()
run_procedure()
return
def GUI():
# window from tkinter package
window = tk.Tk()
window.title('HYDRO CONTROL')
window.geometry('800x400+0+0')
#window.attributes('-fullscreen',True)
# root frame
frame = tk.Frame(window)
frame.pack(fill = tk.BOTH, expand = True)
# frame structure
box1 = Initial_Box(frame, 0, '#000000')
border1 = Dotted_Border(frame, dotted_line_color)
box2 = Initial_Box(frame, 0, '#000000')
border2 = Dotted_Border(frame, dotted_line_color)
box3 = Initial_Box(frame, 0, '#000000')
border3 = Dotted_Border(frame, dotted_line_color)
box4 = Initial_Box_4(frame, 0, '#000000')
border4 = Dotted_Border(frame, '#000000')
box1_L = Graph_Box(box1)
box2_L = Graph_Box(box2)
box3_L = Graph_Box(box3)
box1_R = Readout_Box(box1)
box2_R = Readout_Box(box2)
box3_R = Readout_Box(box3)
box1_R_Top = Section_Box_XS(box1_R)
box2_R_Top = Section_Box_XS(box2_R)
box3_R_Top = Section_Box_XS(box3_R)
box1_R_Bottom = Section_Box_Small(box1_R)
box1_R_Bottom_R = Section_Box_Small_Half(box1_R_Bottom)
box2_R_Bottom = Section_Box_Small(box2_R)
box2_R_Bottom_R = Section_Box_Small_Half(box2_R_Bottom)
box3_R_Bottom = Section_Box_Small(box3_R)
box4_R_2 = Readout_Box(box4)
box4_R_1 = Readout_Box(box4)
box4_L_2 = Readout_Box(box4)
box4_L_1 = Readout_Box(box4)
box4_L_1_Top = Section_Box_XXS(box4_L_1)
box4_L_1_Mid1 = Section_Box_XXS(box4_L_1)
box4_L_1_Mid2 = Section_Box_XXS(box4_L_1)
box4_L_1_Mid3 = Section_Box_XXS(box4_L_1)
box4_L_1_Bottom = Section_Box_XXS(box4_L_1)
box4_L_2_Top = Section_Box_Med(box4_L_2)
box4_L_2_Bottom = Section_Box_Med(box4_L_2)
box4_R_1_Top = Section_Box_XXS(box4_R_1)
box4_R_1_Mid1 = Section_Box_XXS(box4_R_1)
box4_R_1_Mid2 = Section_Box_XXS(box4_R_1)
box4_R_1_Mid3 = Section_Box_XXS(box4_R_1)
box4_R_1_Bottom = Section_Box_XXS(box4_R_1)
box4_R_2_Top = Section_Box_XXS(box4_R_2)
box4_R_2_Mid1 = Section_Box_XXS(box4_R_2)
box4_R_2_Mid2 = Section_Box_XXS(box4_R_2)
box4_R_2_Mid3 = Section_Box_XXS(box4_R_2)
box4_R_2_Bottom = Section_Box_XXS(box4_R_2)
box4_L_1_Mid1_L = Section_Box_XXS_Half(box4_L_1_Mid1, 100)
box4_L_1_Mid1_R = Section_Box_XXS_Half(box4_L_1_Mid1, 100)
box4_L_1_Mid2_L = Section_Box_XXS_Half(box4_L_1_Mid2, 70)
box4_L_1_Mid2_R = Section_Box_XXS_Half(box4_L_1_Mid2, 130)
box4_L_1_Mid3_L = Section_Box_XXS_Half(box4_L_1_Mid3, 100)
box4_L_1_Mid3_R = Section_Box_XXS_Half(box4_L_1_Mid3, 100)
box4_L_1_Bottom_L = Section_Box_XXS_Half(box4_L_1_Bottom, 100)
box4_L_1_Bottom_R = Section_Box_XXS_Half(box4_L_1_Bottom, 100)
box4_R_1_Mid1_L = Section_Box_XXS_Half(box4_R_1_Mid1, 70)
box4_R_1_Mid1_R = Section_Box_XXS_Half(box4_R_1_Mid1, 130)
box4_R_1_Mid2_L = Section_Box_XXS_Half(box4_R_1_Mid2, 70)
box4_R_1_Mid2_R = Section_Box_XXS_Half(box4_R_1_Mid2, 130)
box4_R_1_Mid3_L = Section_Box_XXS_Half(box4_R_1_Mid3, 70)
box4_R_1_Mid3_R = Section_Box_XXS_Half(box4_R_1_Mid3, 130)
box4_R_1_Bottom_L = Section_Box_XXS_Half(box4_R_1_Bottom, 70)
box4_R_1_Bottom_R = Section_Box_XXS_Half(box4_R_1_Bottom, 130)
box4_R_2_Mid1_L = Section_Box_XXS_Half(box4_R_2_Mid1, 100)
box4_R_2_Mid1_R = Section_Box_XXS_Half(box4_R_2_Mid1, 100)
box4_R_2_Mid2_L = Section_Box_XXS_Half(box4_R_2_Mid2, 100)
box4_R_2_Mid2_R = Section_Box_XXS_Half(box4_R_2_Mid2, 100)
box4_R_2_Mid3_L = Section_Box_XXS_Half(box4_R_2_Mid3, 100)
box4_R_2_Mid3_R = Section_Box_XXS_Half(box4_R_2_Mid3, 100)
box4_R_2_Bottom_L = Section_Box_XXS_Half(box4_R_2_Bottom, 100)
box4_R_2_Bottom_R = Section_Box_XXS_Half(box4_R_2_Bottom, 100)
# checkbuttons
global current_ec_chk, current_ph_chk
current_ec_chk = tk.IntVar(value = ec_pid_chk)
current_ph_chk = tk.IntVar(value = ph_pid_chk)
chk_pid_ec = parameter_checkbox(box4_L_2_Top, 'EC PID | COEFFICIENTS:', current_ec_chk)
chk_pid_ph = parameter_checkbox(box4_L_2_Bottom, 'pH PID | COEFFICIENTS:', current_ph_chk)
# reservoir volume label color
volume_color()
# labels
ec_lbl_header = Header_Label(box1_R_Top, 'EC/TDS (μS/cm)', ec_color)
ph_lbl_header = Header_Label(box2_R_Top, 'Acid/Base (pH)', ph_color)
temp_lbl_header = Header_Label(box3_R_Top, 'Temperature (℉)', temp_color)
parameters_lbl_header = Header_Label(box4_L_1_Top, ' Parameters ⚛', parameters_color).pack(side = tk.TOP, anchor = tk.NW)
ec_lbl_readout = Readout_Label(box1_R_Bottom, "{:.2f}".format(ec), ec_color)
ph_lbl_readout = Readout_Label(box2_R_Bottom, "{:.2f}".format(ph), ph_color)
temp_lbl_readout = Readout_Label(box3_R_Bottom, "{:.1f}".format(temp), temp_color)
para_xaxis_lbl_header = tk.Label(box4_L_1_Mid2_L, text = ' X AXES:', font = ('Arial Bold', 10), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
para_vol_lbl_header = para_header_label(box4_R_1_Top, 'REMAINING VOLUMES')
para_vol_lbl_1 = para_label(box4_R_1_Mid1_L, 'PART A')
para_vol_lbl_1_p = para_vol_label(box4_R_1_Mid1_R, vol1_percent, vol_1)
para_vol_lbl_2 = para_label(box4_R_1_Mid2_L, 'PART B')
para_vol_lbl_2_p = para_vol_label(box4_R_1_Mid2_R, vol2_percent, vol_2)
para_vol_lbl_3 = para_label(box4_R_1_Mid3_L, 'pH UP')
para_vol_lbl_3_p = para_vol_label(box4_R_1_Mid3_R, vol3_percent, vol_3)
para_vol_lbl_4 = para_label(box4_R_1_Bottom_L, 'pH DOWN')
para_vol_lbl_4_p = para_vol_label(box4_R_1_Bottom_R, vol4_percent, vol_4)
para_vol_lbl_header = para_header_label(box4_R_2_Top,'ADD VOLUME (mL)')
# buttons
vol1_reset = add_vol_button(box4_R_2_Mid1_R)
vol2_reset = add_vol_button(box4_R_2_Mid2_R)
vol3_reset = add_vol_button(box4_R_2_Mid3_R)
vol4_reset = add_vol_button(box4_R_2_Bottom_R)
# spin-boxes
global current_ec_setpoint, current_ph_setpoint
current_ec_setpoint = tk.DoubleVar(value = ec_setpoint)
current_ph_setpoint = tk.DoubleVar(value = ph_setpoint)
ec_input = tk.Spinbox(box1_R_Bottom_R, from_=0, to=2, increment = 0.1, wrap = False, font = ('Arial', 18), bg = bg_color, fg = ec_color, width = 3, highlightthickness = 0, relief = tk.FLAT, textvariable = current_ec_setpoint, command = settings_write)
ec_input.pack(side = tk.RIGHT, anchor = tk.W, padx = padding_right+4)
ph_input = tk.Spinbox(box2_R_Bottom_R, from_=4, to=8, increment = 0.1, wrap = False, font = ('Arial', 18), bg = bg_color, fg = ph_color, width = 3, highlightthickness = 0, relief = tk.FLAT, textvariable = current_ph_setpoint, command = settings_write)
ph_input.pack(side = tk.RIGHT, anchor = tk.W, padx = padding_right+4)
vol1_input = volume_spinbox(box4_R_2_Mid1_L)
vol2_input = volume_spinbox(box4_R_2_Mid2_L)
vol3_input = volume_spinbox(box4_R_2_Mid3_L)
vol4_input = volume_spinbox(box4_R_2_Bottom_L)
# Define the style for combobox widget
window.option_add("*TCombobox*Listbox*Background", widget_bg_color)
window.option_add('*TCombobox*Listbox*Foreground', widget_fg_color)
# window.option_add('*TCombobox*Listbox*selectBackground', widget_bg_color)
# window.option_add('*TCombobox*Listbox*selectForeground', widget_fg_color)
style= ttk.Style()
style.map('TCombobox', fieldbackground=[('readonly', widget_bg_color)])
style.map('TCombobox', selectbackground=[('readonly', widget_bg_color)])
style.map('TCombobox', selectforeground=[('readonly', widget_fg_color)])
style.map('TCombobox', background=[('readonly', widget_bg_color)])
style.map('TCombobox', foreground=[('readonly', widget_fg_color)])
style.map('TCombobox', bordercolor=[('readonly', widget_bg_color)])
style.map('TCombobox', arrowcolor=[('readonly', widget_fg_color)])
# comboboxes
global current_x_axis
current_x_axis = tk.StringVar(value = x_axis)
combobox1 = ttk.Combobox(box4_L_1_Mid2_R, textvariable = current_x_axis, width = 7)
combobox1.bind('<>', settings_write_with_event)
combobox1['state'] = 'readonly'
combobox1['values'] = ('6 hours', '12 hours', '24 hours', '48 hours', '72 hours')
combobox1.pack(side = tk.LEFT)
# entries
global current_ec_kp, current_ec_ki, current_ec_kd, current_ph_kp, current_ph_ki, current_ph_kd
current_ec_kp = tk.StringVar(value = ec_kp)
current_ec_ki = tk.StringVar(value = ec_ki)
current_ec_kd = tk.StringVar(value = ec_kd)
current_ph_kp = tk.StringVar(value = ph_kp)
current_ph_ki = tk.StringVar(value = ph_ki)
current_ph_kd = tk.StringVar(value = ph_kd)
ec_kp_lbl = tk.Label(box4_L_2_Top, text = 'p ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ec_kp_input = tk.Entry(box4_L_2_Top, textvariable = current_ec_kp, width = 4, bg = widget_bg_color, fg = ec_color, highlightthickness = 0)
ec_kp_input.pack(side = tk.LEFT)
ec_kp_input.bind("", settings_write_with_event)
ec_ki_lbl = tk.Label(box4_L_2_Top, text = ' i ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ec_ki_input = tk.Entry(box4_L_2_Top, textvariable = current_ec_ki, width = 4, bg = widget_bg_color, fg = ec_color, highlightthickness = 0)
ec_ki_input.pack(side = tk.LEFT)
ec_kp_input.bind("", settings_write_with_event)
ec_kd_lbl = tk.Label(box4_L_2_Top, text = ' d ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ec_kd_input = tk.Entry(box4_L_2_Top, textvariable = current_ec_kd, width = 4, bg = widget_bg_color, fg = ec_color, highlightthickness = 0)
ec_kd_input.pack(side = tk.LEFT)
ec_kp_input.bind("", settings_write_with_event)
ph_kp_lbl = tk.Label(box4_L_2_Bottom, text = 'p ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ph_kp_input = tk.Entry(box4_L_2_Bottom, textvariable = current_ph_kp, width = 4, bg = widget_bg_color, fg = ph_color, highlightthickness = 0)
ph_kp_input.pack(side = tk.LEFT)
ph_kp_input.bind("", settings_write_with_event)
ph_ki_lbl = tk.Label(box4_L_2_Bottom, text = ' i ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ph_ki_input = tk.Entry(box4_L_2_Bottom, textvariable = current_ph_ki, width = 4, bg = widget_bg_color, fg = ph_color, highlightthickness = 0)
ph_ki_input.pack(side = tk.LEFT)
ph_kp_input.bind("", settings_write_with_event)
ph_kd_lbl = tk.Label(box4_L_2_Bottom, text = ' d ', font = ('Arial', 12), fg = parameters_color, bg = bg_color).pack(side = tk.LEFT)
ph_kd_input = tk.Entry(box4_L_2_Bottom, textvariable = current_ph_kd, width = 4, bg = widget_bg_color, fg = ph_color, highlightthickness = 0)
ph_kd_input.pack(side = tk.LEFT)
ph_kp_input.bind("", settings_write_with_event)
# graphs
Graph1 = Create_Graph(box1_L, 'ec', ec_color)
Graph2 = Create_Graph(box2_L, 'ph', ph_color)
Graph3 = Create_Graph(box3_L, 'temp', temp_color)
window.mainloop()
return
threading.Thread(target=control, daemon=True).start()
threading.Thread(target=GUI, daemon=False).start()