-
Notifications
You must be signed in to change notification settings - Fork 2
/
GUI.py
962 lines (873 loc) · 47.9 KB
/
GUI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
from alicat_flowmeter_control import flow_control_basic, flow_control
from alicat import FlowController
flow_controller_O2 = FlowController(port='COM3')
flow_controller_Ar = FlowController(port='COM5')
flow_controller_O2.set_gas('O2')
flow_controller_Ar.set_gas('Ar')
import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog
import customtkinter as ctk
import numpy as np
import os
import matplotlib.pyplot as plt
# plt.ioff()
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib
import time
import datetime
from oxygen_sensor import read_O2_sensor
from pynput.keyboard import Key, Controller
from simple_pid import PID
import logging
keyboard = Controller()
#extract entered values from the respective entry_lists
#Variables for oxygen concentration mode
Number_of_point = 1
Point_label_list = []
Point_equal_label_list = []
Point_entry_list = []
Point_unit_label_list = []
Duration_label_list = []
Duration_equal_label_list = []
Duration_entry_list = []
Duration_unit_label_list =[]
##storing lists for the check functions
check_val_total_flow = [1]
check_val_point = [1]
check_val_duration=[1]
#Varialbes for flow rate mode
Number_of_flow = 1
Flow_label_list = []
Flow_equal_label_list = []
Flow_entry_list = []
Flow_unit_label_list = []
Flow_Duration_label_list = []
Flow_Duration_equal_label_list = []
Flow_Duration_entry_list = []
Flow_Duration_unit_label_list =[]
##storing lists for the check functions
check_val_flow_total_flow = [1]
check_val_flow = [1]
check_val_flow_duration=[1]
# list of message functions
message_list = []
#Current mode the user is interacting with
Mode="conc"
loop = True
stop_val = False
extrema_list = []
# filename="lmao"
# GUI function
def create_gui():
global Number_of_point
global Point_entry_list, Point_equal_label_list, Point_label_list, Point_unit_label_list, Duration_entry_list, Duration_equal_label_list, Duration_label_list, Duration_unit_label_list
global Flow_label_list, Flow_equal_label_list, Flow_entry_list, Flow_unit_label_list, Flow_Duration_label_list, Flow_Duration_equal_label_list, Flow_Duration_entry_list, Flow_Duration_unit_label_list
global Mode
"""
Creates the GUI
"""
ctk.set_appearance_mode("Dark")
ctk.set_default_color_theme("blue")
# check functions of the concentration mode
## check total flow value
def flow_warning():
if messagebox.askyesno("Oxygen Control", "Total flow is exceeding 60 sccm but under 100 sccm. Are your stage type and temperature setting compatible with this flow rate?"):
total_flow_entry.configure(border_color = "white")
check_val_total_flow[0]=True
else:
total_flow_entry.configure(border_color = "red")
check_val_total_flow[0]=False
def check_total_flow(text):
global Mode
text = total_flow_entry.get()
if Mode == "conc":
if text: #if there is text input
try:
value = float(text) #try converting text into a float
if 0 < value <= 60: #restrict value range
total_flow_entry.configure(border_color = "white") #configure border color of the corresponding entry cell
check_val_total_flow[0]=True #stores True/False value at the corresponding index along the list
elif 60 < value <= 100: #if the value is out of range
flow_warning()
else:
check_val_total_flow[0]=False
except ValueError: #if the entry is not a number
total_flow_entry.configure(border_color = "red")
check_val_total_flow[0]=False
else: #if the entry is submitted without an input
check_val_total_flow[0]=False
#hide error message only if all entries for total flow rate, concentratoin setpoints, and durations are accepted
if sum(check_val_total_flow) == len(check_val_total_flow) and sum(check_val_point) == len(check_val_point) and sum(check_val_duration) == len(check_val_duration):
if Mode == "conc":
app.input_alert_label.configure(text="")
else:
if Mode == "conc":
app.input_alert_label.configure(text="Input error")
##check function for concentration setpoint
def check_point(text):
global Mode
for pos,i in enumerate(Point_entry_list): #checks all concentration setpoint entries everytime a binidng event occurs in one of them
text = i.get()
if text:
try:
value = float(text)
if 0 <= value <= 30:
print(value)
try:
check_val_point[pos] = True
except IndexError: #append instead,if the index does not exist in the check list yet
check_val_point.append(True)
else:
try:
check_val_point[pos] = False
except IndexError:
check_val_point.append(False)
except ValueError:
try:
check_val_point[pos] = False
except IndexError:
check_val_point.append(False)
else:
try:
check_val_point[pos] = False
except IndexError:
check_val_point.append(False)
for i,val in enumerate(check_val_point): #go through the check list and configure the entry cell's border colour correspondingly
if check_val_point[i]==True:
Point_entry_list[i].configure(border_color="white")
elif check_val_point[i]==False:
Point_entry_list[i].configure(border_color="red")
if sum(check_val_total_flow) == len(check_val_total_flow) and sum(check_val_point) == len(check_val_point) and sum(check_val_duration) == len(check_val_duration):
if Mode == "conc":
app.input_alert_label.configure(text="")
else:
if Mode == "conc":
app.input_alert_label.configure(text="Input error")
return
##check function for duration
def check_duration(text):
global Mode
for pos,i in enumerate(Duration_entry_list):
text = i.get()
if text:
try:
value = float(text)
if value > 0:
try:
check_val_duration[pos] = True
except IndexError:
check_val_duration.append(True)
elif value == None:
try:
check_val_duration[pos] = False
except IndexError:
check_val_duration.append(False)
else:
try:
check_val_duration[pos] = False
except IndexError:
check_val_duration.append(False)
except ValueError:
try:
check_val_duration[pos] = False
except IndexError:
check_val_duration.append(False)
else:
try:
check_val_duration[pos] = False
except IndexError:
check_val_duration.append(False)
for i,val in enumerate(check_val_duration):
if check_val_duration[i]==True:
Duration_entry_list[i].configure(border_color="white")
elif check_val_duration[i]==False:
Duration_entry_list[i].configure(border_color="red")
if sum(check_val_total_flow) == len(check_val_total_flow) and sum(check_val_point) == len(check_val_point) and sum(check_val_duration) == len(check_val_duration):
if Mode == "conc":
app.input_alert_label.configure(text="")
else:
if Mode == "conc":
app.input_alert_label.configure(text="Input error")
return
# check functions of the flow rate mode
## check total flow value in the flow rate mode
def check_total_flow_flow(text):
global Mode
text = flow_total_flow_entry.get()
if Mode == "conc":
if text: #if there is text input
try:
value = float(text) #try converting text into a float
if 0 < value <= 60: #restrict value range
flow_total_flow_entry.configure(border_color = "white") #configure border color of the corresponding entry cell
check_val_flow_total_flow[0]=True #stores True/False value at the corresponding index along the list
elif 60 < value <= 100: #if the value is out of range
flow_warning()
else: #if the value is out of range
flow_total_flow_entry.configure(border_color = "red")
check_val_flow_total_flow[0]=False
except ValueError: #if the entry is not a number
flow_total_flow_entry.configure(border_color = "red")
check_val_flow_total_flow[0]=False
else: #if the entry is submitted without an input
check_val_flow_total_flow[0]=False
#hide error message only if all entries for total flow rate, concentratoin setpoints, and durations are accepted
if sum(check_val_flow_total_flow) == len(check_val_flow_total_flow) and sum(check_val_flow) == len(check_val_flow) and sum(check_val_flow_duration) == len(check_val_flow_duration):
if Mode == "conc":
app.input_alert_label.configure(text="")
else:
if Mode == "conc":
app.input_alert_label.configure(text="Input error")
## check function for flow rate
def check_flow(text):
global Mode
for pos,i in enumerate(Flow_entry_list):
text = i.get()
if text:
try:
value = float(text)
if 0 <= value <= float(flow_total_flow_entry.get()):
try:
check_val_flow[pos] = True
except IndexError:
check_val_flow.append(True)
else:
try:
check_val_flow[pos] = False
except IndexError:
check_val_flow.append(False)
except ValueError:
try:
check_val_flow[pos] = False
except IndexError:
check_val_flow.append(False)
else:
try:
check_val_flow[pos] = False
except IndexError:
check_val_flow.append(False)
for i,val in enumerate(check_val_flow):
if check_val_flow[i]==True:
Flow_entry_list[i].configure(border_color="white")
elif check_val_flow[i]==False:
Flow_entry_list[i].configure(border_color="red")
if sum(check_val_flow_total_flow) == len(check_val_flow_total_flow) and sum(check_val_flow) == len(check_val_flow) and sum(check_val_flow_duration) == len(check_val_flow_duration):
if Mode == "flow":
app.input_alert_label.configure(text="")
else:
if Mode == "flow":
app.input_alert_label.configure(text="Input error")
return
##check function for duration (in flow rate mode)
def check_flow_duration(text):
global Mode
for pos,i in enumerate(Flow_Duration_entry_list):
text = i.get()
if text:
try:
value = float(text)
if value > 0:
try:
check_val_flow_duration[pos] = True
except IndexError:
check_val_flow_duration.append(True)
else:
try:
check_val_flow_duration[pos] = False
except IndexError:
check_val_flow_duration.append(False)
except ValueError:
try:
check_val_flow_duration[pos] = False
except IndexError:
check_val_flow_duration.append(False)
else:
try:
check_val_flow_duration[pos] = False
except IndexError:
check_val_flow_duration.append(False)
for i,val in enumerate(check_val_flow_duration):
if check_val_flow_duration[i]==True:
Flow_Duration_entry_list[i].configure(border_color="white")
elif check_val_flow_duration[i]==False:
Flow_Duration_entry_list[i].configure(border_color="red")
if sum(check_val_flow_total_flow) == len(check_val_flow_total_flow) and sum(check_val_flow) == len(check_val_flow) and sum(check_val_flow_duration) == len(check_val_flow_duration):
if Mode == "flow":
app.input_alert_label.configure(text="")
else:
if Mode == "flow":
app.input_alert_label.configure(text="Input error")
return
# master app setup
app = ctk.CTk()
app.geometry("900x600")
app.title("Oxygen Control")
app.rowconfigure(0,weight=1)
app.columnconfigure(0,weight=3)
app.columnconfigure(1,weight=2)
app.columnconfigure(2,weight=4)
GUIfont = ctk.CTkFont(family="Arial", size=16, weight="normal")
titlefont = ctk.CTkFont(family="Arial", size=16, weight="bold")
save_file_path = filedialog.asksaveasfilename(initialdir = os.path.expanduser('~'),title = "Select file",filetypes = (("txt files","*.txt"),("all files","*.*")), defaultextension=".txt")
# set frames
## frame for concentration mode
FrameConc = ctk.CTkScrollableFrame(app)
FrameConc.grid(row=0, column=0, ipadx=28, sticky="news")
FrameConc.grid_columnconfigure(0,weight=1)
FrameConc.grid_columnconfigure(1,weight=1)
FrameConc.grid_columnconfigure(2,weight=1)
FrameConc.grid_columnconfigure(3,weight=1)
FrameConc.grid(row=0, column=0, ipadx=28, sticky="news")
## frame for flow rate mode
FrameFlowRate = ctk.CTkScrollableFrame(app)
FrameFlowRate.grid_columnconfigure(0,weight=1)
FrameFlowRate.grid_columnconfigure(1,weight=1)
FrameFlowRate.grid_columnconfigure(2,weight=1)
FrameFlowRate.grid_columnconfigure(3,weight=1)
# Configure logging
logging.basicConfig(level=logging.INFO, filename='system.log')
log_handler = logging.FileHandler('system.log')
log_handler.setLevel(logging.INFO)
logger = logging.getLogger('system_log')
logger.addHandler(log_handler)
# Function to update the system log GUI
def update_system_log(text_widget, message):
text_widget.configure(state="normal") # Enable the text widget for editing
text_widget.insert("end", message + "\n") # Append the new message
text_widget.configure(state="disabled") # Disable the text widget to prevent editing
text_widget.see("end") # Scroll to the bottom of the text widget
# Custom log handler to update the system log GUI
class SystemLogHandler(logging.Handler):
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
def emit(self, record):
log_message = self.format(record)
update_system_log(self.text_widget, log_message)
FrameLog = ctk.CTkFrame(app)
FrameLog.grid(row=0, column=2, sticky="nsew")
FrameLog.columnconfigure(0,weight=1)
title_label = ctk.CTkLabel(FrameLog, text="System Log", font=titlefont)
title_label.grid(row=0, column=0, padx=5, pady=(5,15), sticky="w")
message = ctk.CTkTextbox(FrameLog, font=GUIfont)
message.grid(row=1, column=0, rowspan=3, sticky = "nsew")
message.configure(state="disabled")
# Set up the first variables in the concentraton mode frame
## row for total flow rate input
total_flow_label = ctk.CTkLabel(FrameConc,text="Total flow", font = GUIfont)
total_flow_label.grid(row=0,column=0,padx=(20, 5), pady=(5,15), sticky="ew")
total_flow_equal_label = ctk.CTkLabel(FrameConc,text="=", font = GUIfont)
total_flow_equal_label.grid(row=0,column=1,padx=5, pady=(5,15), sticky="ew")
total_flow_entry = ctk.CTkEntry(FrameConc, placeholder_text="0 to 60", border_color="white", validate="key")
total_flow_entry.grid(row=0, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
#total_flow_entry.bind('<FocusOut>', check_total_flow)
total_flow_entry.bind('<Return>',check_total_flow)
total_flow_unit_label = ctk.CTkLabel(FrameConc,text="sccm", font = GUIfont)
total_flow_unit_label.grid(row=0, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
total_flow_entry.insert(tk.END, "60")
# row for concentration setpoint
Point_label = ctk.CTkLabel(FrameConc, text="Point 1", font=GUIfont)
Point_label.grid(row=1, column=0, padx=(20, 5), pady=(15,5), sticky="ew")
Point_label_list.append(Point_label)
Point_equal_label = ctk.CTkLabel(FrameConc, text="=", font=GUIfont)
Point_equal_label.grid(row=1, column=1, padx=5, pady=(15,5), sticky="ew")
Point_equal_label_list.append(Point_equal_label)
Point_entry = ctk.CTkEntry(FrameConc, placeholder_text="0 to 30", border_color="white", validate="key")
Point_entry.grid(row=1, column=2, columnspan=1, padx=5, pady=(15,5), sticky="ew")
Point_entry.bind('<FocusOut>', check_point)
Point_entry.bind('<Return>', check_point)
Point_entry_list.append(Point_entry)
Point_unit_label = ctk.CTkLabel(FrameConc, text="%", font=GUIfont)
Point_unit_label.grid(row=1, column=3, padx=(5, 20), pady=(15,5), sticky="ew")
Point_unit_label_list.append(Point_unit_label)
# row for duration time setting
Duration_label = ctk.CTkLabel(FrameConc, text="Duration", font=GUIfont)
Duration_label.grid(row=2, column=0, padx=(20, 5), pady=(5,15), sticky="ew")
Duration_label_list.append(Duration_label)
Duration_equal_label = ctk.CTkLabel(FrameConc, text="=", font=GUIfont)
Duration_equal_label.grid(row=2, column=1, padx=5, pady=(5,15), sticky="ew")
Duration_equal_label_list.append(Duration_equal_label)
Duration_entry = ctk.CTkEntry(FrameConc, placeholder_text="any + value", border_color="white", validate="key")
Duration_entry.grid(row=2, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
Duration_entry_list.append(Duration_entry)
Duration_entry.bind('<FocusOut>', check_duration)
Duration_entry.bind('<Return>', check_duration)
Duration_unit_label = ctk.CTkLabel(FrameConc, text="min", font=GUIfont)
Duration_unit_label.grid(row=2, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
Duration_unit_label_list.append(Duration_unit_label)
# add subscripts
SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
O2_string = "O2 Concentration"
O2_flow_string = "O2 Flow Rate"
# function for adding a new row of setpoint and duration time everytime its button is pressed
def addpoint():
global Number_of_point
global Point_entry_list, Point_equal_label_list, Point_label_list, Point_unit_label_list, Duration_entry_list, Duration_equal_label_list, Duration_label_list, Duration_unit_label_list
Number_of_point += 1 #increment the total number of setpoints by 1
if Number_of_point >= 1: #almost a repeat of what it is above
Point_label_new = ctk.CTkLabel(FrameConc, text="Point {}".format(Number_of_point), font=GUIfont)
Point_label_new.grid(row=Number_of_point*2+1, column=0, padx=(20, 5), pady=(15,5), sticky="ew")
Point_label_list.append(Point_label_new)
Point_equal_label_new = ctk.CTkLabel(FrameConc, text="=", font=GUIfont)
Point_equal_label_new.grid(row=Number_of_point*2+1, column=1, padx=5, pady=(15,5), sticky="ew")
Point_equal_label_list.append(Point_equal_label_new)
Point_entry_new = ctk.CTkEntry(FrameConc, placeholder_text="0 to 30", border_color="white", validate="key")
Point_entry_new.grid(row=Number_of_point*2+1, column=2, columnspan=1, padx=5, pady=(15,5), sticky="ew")
Point_entry_new.bind('<FocusOut>', check_point)
Point_entry_new.bind('<Return>', check_point)
Point_entry_list.append(Point_entry_new)
Point_unit_label_new = ctk.CTkLabel(FrameConc, text="%", font=GUIfont)
Point_unit_label_new.grid(row=Number_of_point*2+1, column=3, padx=(5, 20), pady=(15,5), sticky="ew")
Point_unit_label_list.append(Point_unit_label_new)
Duration_label_new = ctk.CTkLabel(FrameConc, text="Duration", font=GUIfont)
Duration_label_new.grid(row=Number_of_point*2+2, column=0, padx=(20, 5), pady=(5,15), sticky="ew")
Duration_label_list.append(Duration_label_new)
Duration_equal_label_new = ctk.CTkLabel(FrameConc, text="=", font=GUIfont)
Duration_equal_label_new.grid(row=Number_of_point*2+2, column=1, padx=5, pady=(5,15), sticky="ew")
Duration_equal_label_list.append(Duration_equal_label_new)
Duration_entry_new = ctk.CTkEntry(FrameConc, placeholder_text="any + value", border_color="white", validate="key")
Duration_entry_new.grid(row=Number_of_point*2+2, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
Duration_entry_list.append(Duration_entry_new)
Duration_entry_new.bind('<FocusOut>', check_duration)
Duration_entry_new.bind('<Return>', check_duration)
Duration_unit_label_new = ctk.CTkLabel(FrameConc, text="min", font=GUIfont)
Duration_unit_label_new.grid(row=Number_of_point*2+2, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
Duration_unit_label_list.append(Duration_unit_label_new)
return Number_of_point
# fuction for removing the last row of setpoint and duration time
def RemovePoint():
global Number_of_point
if Number_of_point > 1: #permit removal only if the number of points is greater than 1
Number_of_point -= 1 #decrease the total number of point by 1
#destroy all widgets in this row and delete them from their storing lists and checking lists
Point_label_list[-1].destroy()
del Point_label_list[-1]
Point_equal_label_list[-1].destroy()
del Point_equal_label_list[-1]
Point_entry_list[-1].destroy()
del Point_entry_list[-1]
Point_unit_label_list[-1].destroy()
del Point_unit_label_list[-1]
Duration_label_list[-1].destroy()
del Duration_label_list[-1]
Duration_equal_label_list[-1].destroy()
del Duration_equal_label_list[-1]
Duration_entry_list[-1].destroy()
del Duration_entry_list[-1]
Duration_unit_label_list[-1].destroy()
del Duration_unit_label_list[-1]
try:
del check_val_point[-1]
except IndexError:
pass
try:
del check_val_duration[-1]
except IndexError:
pass
check_point(Point_entry)
check_duration(Duration_entry)
return Number_of_point
# Variables and functions in the Flow Rate Mode, everything is the same as in the concentration mode,
# just different variable names and text
## row for total flow rate input
flow_total_flow_label = ctk.CTkLabel(FrameFlowRate,text="Total flow", font = GUIfont)
flow_total_flow_label.grid(row=0,column=0,padx=(20, 5), pady=(5,15), sticky="ew")
flow_total_flow_equal_label = ctk.CTkLabel(FrameFlowRate,text="=", font = GUIfont)
flow_total_flow_equal_label.grid(row=0,column=1,padx=5, pady=(5,15), sticky="ew")
flow_total_flow_entry = ctk.CTkEntry(FrameFlowRate, placeholder_text="0 to 60", border_color="white", validate="key")
flow_total_flow_entry.grid(row=0, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
#flow_total_flow_entry.bind('<FocusOut>', check_total_flow_flow)
flow_total_flow_entry.bind('<Return>',check_total_flow_flow)
flow_total_flow_unit_label = ctk.CTkLabel(FrameFlowRate,text="sccm", font = GUIfont)
flow_total_flow_unit_label.grid(row=0, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
Flow_label = ctk.CTkLabel(FrameFlowRate, text=O2_flow_string.translate(SUB)+" 1", font=GUIfont)
Flow_label.grid(row=1, column=0, padx=(20, 5), pady=(15, 5), sticky="ew")
Flow_label_list.append(Flow_label)
Flow_equal_label = ctk.CTkLabel(FrameFlowRate, text="=", font=GUIfont)
Flow_equal_label.grid(row=1, column=1, padx=5, pady=(15, 5), sticky="ew")
Flow_equal_label_list.append(Flow_equal_label)
Flow_entry = ctk.CTkEntry(FrameFlowRate, placeholder_text="less than total flow rate", border_color="white", validate="key")
Flow_entry.grid(row=1, column=2, columnspan=1, padx=5, pady=(15, 5), sticky="ew")
Flow_entry.bind('<FocusOut>', check_flow)
Flow_entry.bind('<Return>', check_flow)
Flow_entry_list.append(Flow_entry)
Flow_unit_label = ctk.CTkLabel(FrameFlowRate, text="sccm", font=GUIfont)
Flow_unit_label.grid(row=1, column=3, padx=(5, 20), pady=(15, 5), sticky="ew")
Flow_unit_label_list.append(Flow_unit_label)
# Duration
Flow_Duration_label = ctk.CTkLabel(FrameFlowRate, text="Duration", font=GUIfont)
Flow_Duration_label.grid(row=2, column=0, padx=(20, 5), pady=(5,15), sticky="ew")
Flow_Duration_label_list.append(Flow_Duration_label)
Flow_Duration_equal_label = ctk.CTkLabel(FrameFlowRate, text="=", font=GUIfont)
Flow_Duration_equal_label.grid(row=2, column=1, padx=5, pady=(5,15), sticky="ew")
Flow_Duration_equal_label_list.append(Flow_Duration_equal_label)
Flow_Duration_entry = ctk.CTkEntry(FrameFlowRate, placeholder_text="any + value", border_color="white", validate="key")
Flow_Duration_entry.grid(row=2, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
Flow_Duration_entry_list.append(Flow_Duration_entry)
Flow_Duration_entry.bind('<FocusOut>', check_flow_duration)
Flow_Duration_entry.bind('<Return>', check_flow_duration)
Flow_Duration_unit_label = ctk.CTkLabel(FrameFlowRate, text="min", font=GUIfont)
Flow_Duration_unit_label.grid(row=2, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
Flow_Duration_unit_label_list.append(Flow_Duration_unit_label)
def addflow():
global Number_of_flow
global Flow_entry_list, Flow_equal_label_list, Flow_label_list, Flow_unit_label_list, Flow_Duration_entry_list, Flow_Duration_equal_label_list, Flow_Duration_label_list, Flow_Duration_unit_label_list
Number_of_flow += 1
if Number_of_flow >= 1:
#for Number_of_point in Number_of_widgets:
#Number_of_widgets.append(Number_of_point)
Flow_label_new = ctk.CTkLabel(FrameFlowRate, text= O2_flow_string.translate(SUB) +" {}".format(Number_of_flow), font=GUIfont)
Flow_label_new.grid(row=Number_of_flow*2+1, column=0, padx=(20, 5), pady=(15,5), sticky="ew")
Flow_label_list.append(Flow_label_new)
Flow_equal_label_new = ctk.CTkLabel(FrameFlowRate, text="=", font=GUIfont)
Flow_equal_label_new.grid(row=Number_of_flow*2+1, column=1, padx=5, pady=(15,5), sticky="ew")
Flow_equal_label_list.append(Flow_equal_label_new)
Flow_entry_new = ctk.CTkEntry(FrameFlowRate, placeholder_text="less than total flow rate", border_color="white", validate="key")
Flow_entry_new.grid(row=Number_of_flow*2+1, column=2, columnspan=1, padx=5, pady=(15,5), sticky="ew")
Flow_entry_new.bind('<FocusOut>', check_flow)
Flow_entry_new.bind('<Return>', check_flow)
Flow_entry_list.append(Flow_entry_new)
Flow_unit_label_new = ctk.CTkLabel(FrameFlowRate, text="sccm", font=GUIfont)
Flow_unit_label_new.grid(row=Number_of_flow*2+1, column=3, padx=(5, 20), pady=(15,5), sticky="ew")
Flow_unit_label_list.append(Flow_unit_label_new)
# Duration
Flow_Duration_label_new = ctk.CTkLabel(FrameFlowRate, text="Duration", font=GUIfont)
Flow_Duration_label_new.grid(row=Number_of_flow*2+2, column=0, padx=(20, 5), pady=(5,15), sticky="ew")
Flow_Duration_label_list.append(Flow_Duration_label_new)
Flow_Duration_equal_label_new = ctk.CTkLabel(FrameFlowRate, text="=", font=GUIfont)
Flow_Duration_equal_label_new.grid(row=Number_of_flow*2+2, column=1, padx=5, pady=(5,15), sticky="ew")
Flow_Duration_equal_label_list.append(Flow_Duration_equal_label_new)
Flow_Duration_entry_new = ctk.CTkEntry(FrameFlowRate, placeholder_text="any + value", border_color="white", validate="key")
Flow_Duration_entry_new.grid(row=Number_of_flow*2+2, column=2, columnspan=1, padx=5, pady=(5,15), sticky="ew")
Flow_Duration_entry_list.append(Flow_Duration_entry_new)
Flow_Duration_entry_new.bind('<FocusOut>', check_flow_duration)
Flow_Duration_entry_new.bind('<Return>', check_flow_duration)
Flow_Duration_unit_label_new = ctk.CTkLabel(FrameFlowRate, text="min", font=GUIfont)
Flow_Duration_unit_label_new.grid(row=Number_of_flow*2+2, column=3, padx=(5, 20), pady=(5,15), sticky="ew")
Flow_Duration_unit_label_list.append(Flow_Duration_unit_label_new)
return Number_of_flow
def RemoveFlow():
global Number_of_flow
if Number_of_flow > 1:
Number_of_flow -= 1
Flow_label_list[-1].destroy()
del Flow_label_list[-1]
Flow_equal_label_list[-1].destroy()
del Flow_equal_label_list[-1]
Flow_entry_list[-1].destroy()
del Flow_entry_list[-1]
Flow_unit_label_list[-1].destroy()
del Flow_unit_label_list[-1]
Flow_Duration_label_list[-1].destroy()
del Flow_Duration_label_list[-1]
Flow_Duration_equal_label_list[-1].destroy()
del Flow_Duration_equal_label_list[-1]
Flow_Duration_entry_list[-1].destroy()
del Flow_Duration_entry_list[-1]
Flow_Duration_unit_label_list[-1].destroy()
del Flow_Duration_unit_label_list[-1]
try:
del check_val_flow[-1]
except IndexError:
pass
try:
del check_val_flow_duration[-1]
except IndexError:
pass
check_flow(Flow_entry)
check_flow(Flow_Duration_entry)
return Number_of_flow
# function for changing from concentration mode to flow rate mode
def change_to_flow():
global Mode
FrameConc.grid_forget() #hide the concentration mode frame
FrameFlowRate.grid(row=0, column=0, ipadx=28, sticky="news") #show the flow rate mode frame
add_conc_button.grid_forget() #hide the button for adding concentration setpoints
add_flow_button.grid(row=1,column=0,columnspan=1,padx=20,pady=5) #show the button for adding flow rate setpoints
remove_conc_button.grid_forget() #hide the button for removing concentration setpoints
remove_flow_button.grid(row=2,column=0,columnspan=1,padx=20,pady=5) #show the button for removing flow rate setpoints
change_to_flow_button.grid_forget() #hide the button for this change function
change_to_conc_button.grid(row=3,column=0,columnspan=1,padx=20,pady=5) #show the button for changing from flow rate to concentration mode
app.input_alert_label.configure(text="") #re-initialise the error message for the new mode
if sum(check_val_flow) == len(check_val_flow) and sum(check_val_flow_duration) == len(check_val_flow_duration):
app.input_alert_label.configure(text="")
else:
app.input_alert_label.configure(text="Input error")
Mode = "flow"
return Mode
#vice versa of the change_to_flow function
def change_to_conc():
global Mode
FrameFlowRate.grid_forget()
FrameConc.grid(row=0, column=0, ipadx=28, sticky="news")
add_flow_button.grid_forget()
add_conc_button.grid(row=1,column=0,columnspan=1,padx=20,pady=5)
remove_flow_button.grid_forget()
remove_conc_button.grid(row=2,column=0,columnspan=1,padx=20,pady=5)
change_to_conc_button.grid_forget()
change_to_flow_button.grid(row=3,column=0,columnspan=1,padx=20,pady=5)
app.input_alert_label.configure(text="")
if sum(check_val_point) == len(check_val_point) and sum(check_val_duration) == len(check_val_duration):
app.input_alert_label.configure(text="")
else:
app.input_alert_label.configure(text="Input error")
Mode = "conc"
return Mode
# add subscripts
SUB = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉")
O2_string = "O2 Concentration"
#widgets locating in the master app frame
## buttons
add_conc_button = ctk.CTkButton(app, text="Add Point",border_color="dark-blue", command=lambda:addpoint(),font=('Arial',16)) #button to add concentration setpoint
add_conc_button.grid(row=1,column=0,columnspan=1,padx=20,pady=5) #show the add_conc_button as the mode is defaulted as concentration mode
add_flow_button = ctk.CTkButton(app, text="Add Point",border_color="dark-blue", command=lambda:addflow(),font=('Arial',16)) #button to add flow rate setpoint, not shown until the app is in flow rate mode
remove_conc_button = ctk.CTkButton(app, text="Remove Point",border_color="dark-blue", command=lambda:RemovePoint(),font=('Arial',16)) #button to remove concentration setpoint
remove_conc_button.grid(row=Number_of_point+1,column=0,columnspan=1,padx=20,pady=5)
remove_flow_button = ctk.CTkButton(app, text="Remove Point",border_color="dark-blue", command=lambda:RemoveFlow(),font=('Arial',16)) #button to remove flow rate setpoint, not shown yet
change_to_flow_button = ctk.CTkButton(app, text="Flow Rate",border_color="dark-blue", command=lambda:change_to_flow(),font=('Arial',16)) #button to change concentration mode into flow rate mode
change_to_flow_button.grid(row=3,column=0,columnspan=1,padx=20,pady=5)
change_to_conc_button = ctk.CTkButton(app, text=O2_string.translate(SUB),border_color="dark-blue", command=lambda:change_to_conc(),font=('Arial',16)) #button to change flow rate mode into concentration mode, not shown yet
# error message
app.input_alert_label = ctk.CTkLabel(app, text="", font=GUIfont, text_color=("red"))
app.input_alert_label.grid(row=4, column=0, columnspan=1, padx=10, pady=(0, 5), sticky="ew")
# function to submit all input variables
# incorporate command line scheduling and flow control here
def runGUI():
global start_time, stop_val
start_time = time.time()
setpoint.set(float(Point_entry_list[0].get()))
oxygen_plotting()
stop_now.set(False)
print(Mode)
#record the time when the run starts
keyboard.press(Key.enter) #simulate pressing and release of the enter key in case the user didn't bind the last entry they made before pressing run
keyboard.release(Key.enter)
if Mode == "conc": #detect mode and submit the corresponding entries
check_point(Point_entry) #run the check functions again, assuming the user didn't trigger them before submitting for some reason
check_duration(Duration_entry)
check_total_flow(total_flow_entry)
if sum(check_val_total_flow) == len(check_val_total_flow) and sum(check_val_point) == len(check_val_point) and sum(check_val_duration) == len(check_val_duration): #allow variable inputs only if there's no error in all inputs
print("run")
for pos,i in enumerate(Point_entry_list): #checks all concentration setpoint entries everytime a binidng event occurs in one of them
print(i.get())
setpoint.set(float(i.get())) #set the setpoint to the value in the entry
start_time_of_point_entry = time.time()
setpoint_counter = 0
pid = PID(0.9,0.01,0.03, sample_time = 1, output_limits = (0,100), setpoint = float(i.get()), starting_output= float(i.get())) #PID controller with the setpoint being the concentration setpoint
def controlled_system(total_flow, O2_set_point, current_O2_percent):
flow_controller_O2.set_flow_rate(total_flow*O2_set_point/100)
flow_controller_Ar.set_flow_rate(total_flow-(total_flow*O2_set_point/100))
# print(current_O2_percent)
return current_O2_percent
while time.time()< start_time_of_point_entry + float(Duration_entry_list[pos].get())*60:
oxygen_percent = float(read_O2_sensor())*10e-5
if abs(oxygen_percent-float(i.get())) > 0.5:
start_time_of_point_entry = time.time()
setpoint_counter = 0
else: #setpoint is reached
#if ():
setpoint_counter = setpoint_counter + 1 #just shows message the first time
if setpoint_counter == 1:
logger.addHandler(SystemLogHandler(message))
logger.info("Setpoint " + str(pos+1) + " is reached at: " + str("{:.2f}".format(time.time()-start_time)) + " seconds")
print('O2 concentration:', oxygen_percent, 'setpoint', float(i.get()))
if abs(oxygen_percent-float(i.get())) < 4:
PID_setpoint = pid(oxygen_percent) #this is the setpoint required the PID controller
#we need to get current value and feed back into the PID controller
controlled_system(float(total_flow_entry.get()),PID_setpoint,oxygen_percent)
else:
controlled_system(float(total_flow_entry.get()),float(i.get()),oxygen_percent)
oxygen_plotting()
time.sleep(0.5)
if stop_now.get()==True:
break
if stop_now.get()==True:
break
message.insert(index="end", text="Now on Point {}".format(pos+1)+"\n")
elif sum(check_val_total_flow) != len(check_val_total_flow) or sum(check_val_point) != len(check_val_point) or sum(check_val_duration) != len(check_val_duration):
print("NO WAY")
elif Mode == "flow":
check_flow(Flow_entry)
check_flow_duration(Flow_Duration_entry)
check_total_flow_flow(flow_total_flow_entry)
if sum(check_val_flow_total_flow) == len(check_val_flow_total_flow) and sum(check_val_flow) == len(check_val_flow) and sum(check_val_flow_duration) == len(check_val_flow_duration):
print("run")
for pos,i in enumerate(Flow_entry_list): #checks all concentration setpoint entries everytime a binidng event occurs in one of them
print(i.get())
flow_control_basic(float(flow_total_flow_entry.get()),float(i.get()))
start_time_of_point_entry = time.time()
while time.time()< start_time_of_point_entry + float(Flow_Duration_entry_list[pos].get())*60 :
oxygen_plotting()
time.sleep(0.5)
if stop_now.get()==True:
break
if stop_now.get()==True:
break
else:
print("Bruh")
#add run button
run_button = ctk.CTkButton(app, text="Run",border_color="dark-blue", command=lambda: runGUI(),font=('Arial',16))
run_button.grid(row=5,column=0,columnspan=1,padx=20,pady=(5,20))
#liveplot (some .grid positioning variables are changed to fit things into the same GUI )
# bv1=ctk.BooleanVar(value=False)
setpoint=ctk.StringVar(value='0')
stop_now=ctk.BooleanVar(value=False)
# def stopplotting():
# bv1.set(0)
# print(bv1.get())
# def plotting():
# bv1.set(1)
# oxygen_plotting()
# print(bv1.get())
# start_plot_button=ctk.CTkButton(app, text="Start Plotting",border_color="dark-blue", command=plotting, font=('Arial',16))
# start_plot_button.grid(row=1,column=1, columnspan=1, padx=20, pady=5)
# stop_plot_button=ctk.CTkButton(app, text="Stop Plotting",border_color="dark-blue", command=stopplotting,font=('Arial',16))
# stop_plot_button.grid(row=2,column=1, columnspan=1, padx=20, pady=5)
# generate the figure and plot object which will be linked to the root element
from oxygen_sensor import read_O2_sensor
# opens the COM3 port which is what the O2 sensor was when I plugged it in
# check to see if it is COM3 before running
# Will try optimise so it selects automatically soon
fig, ax = plt.subplots()
ax.set_xlabel('Time (s)')
ax.set_ylabel('O$_2$ conc. (%)')
line, = ax.plot([],[])
line2, = ax.plot([],[])
line3, = ax.plot([],[])
line4, = ax.plot([],[])
#make fig dark mode
fig.patch.set_facecolor('#2b2b2b')
fig.patch.set_alpha(0.9)
ax.set_facecolor('#2b2b2b')
canvas = FigureCanvasTkAgg(fig,master=app)
canvas.get_tk_widget().grid(row=0, column=1, sticky="nsew")
#make canvas transparent
canvas.get_tk_widget().config(highlightthickness=0, background='black')
# make plot match style of gui
ax.tick_params(axis='x', colors='white')
ax.tick_params(axis='y', colors='white')
ax.spines['bottom'].set_color('white')
ax.spines['top'].set_color('white')
ax.spines['left'].set_color('white')
ax.spines['right'].set_color('white')
ax.set_title('Oxygen Concentration', color='white')
ax.set_xlabel('Time (s)', color='white')
ax.set_ylabel('O$_2$ conc. (%)', color='white')
#remove grid
ax.grid(False)
def oxygen_plotting(filename=save_file_path):
global extrema_list
# global filename
"""
Reads the oxygen sensor, records the data over time in a text file
and plots the data in real time
Args:
filename (str, optional): Relative or absolute path to the desired
file location of oxygen sensor data. Defaults to 'oxygen_conc.txt'.
"""
# check if the file exists, if not create it
if not os.path.isfile(filename):
with open(filename, 'w') as f:
f.write('Start time=\t{}\n'.format(datetime.datetime.now()))
f.write('Time (s)\tO2 conc. (ppm)\tO2 setpoint (ppm)\n')
else:
# if file exists, check if it has the correct headers
# if it doesn't, create them
with open(filename,'r') as f:
lines = f.readlines()
if len(lines) > 1:
try:
if lines[0].split('\t')[0] == 'Start time=':
pass
except IndexError:
with open(filename, 'w') as f:
f.write('Start time=\t{}\n'.format(datetime.datetime.now()))
f.write('Time (s)\tO2 conc. (ppm)\tO2 setpoint (ppm)\n')
else:
with open(filename, 'w') as f:
f.write('Start time=\t{}\n'.format(datetime.datetime.now()))
f.write('Time (s)\tO2 conc. (ppm)\tO2 setpoint (ppm)\n')
# it will now have the correct head from the above code and only append the data
with open(filename, 'a') as f:
oxygen_ppm = read_O2_sensor()
current_time = time.time()-start_time
data_line = str("{:.2f}".format(current_time))+'\t'+str(oxygen_ppm)+'\t'+str(float(setpoint.get())*10**4)
if stop_now.get() == True:
#reset the arrays
xdata = np.array([])
ydata = np.array([])
xdata2 = np.array([])
ydata2 = np.array([])
xdata3 = np.array([])
ydata3 = np.array([])
xdata4 = np.array([])
ydata4 = np.array([])
#clear the plot
line.set_data(xdata,ydata)
line2.set_data(xdata2,ydata2)
line3.set_data(xdata3,ydata3)
line4.set_data(xdata4,ydata4)
xdata, ydata = line.get_xdata(),line.get_ydata()
xdata = np.append(xdata,current_time)
ydata = np.append(ydata,float(oxygen_ppm)/10e3)
xdata2, ydata2 = line2.get_xdata(),line2.get_ydata()
xdata2 = np.append(xdata2,current_time)
ydata2 = np.append(ydata2,float(setpoint.get())+0.5)
xdata3, ydata3 = line3.get_xdata(),line3.get_ydata()
xdata3 = np.append(xdata3,current_time)
ydata3 = np.append(ydata3,float(setpoint.get())-0.5)
xdata4, ydata4 = line4.get_xdata(),line4.get_ydata()
xdata4 = np.append(xdata4,current_time)
ydata4 = np.append(ydata4,float(setpoint.get()))
line2.set_data(xdata2,ydata2)
line3.set_data(xdata3,ydata3)
line4.set_data(xdata4,ydata4)
line2.set_linestyle('--')
line3.set_linestyle('--')
#make line 2 and 3 less opaque
line2.set_alpha(0.5)
line3.set_alpha(0.5)
line4.set_alpha(0.8)
line.set_data(xdata,ydata)
if abs(float(oxygen_ppm)/10e3-float(setpoint.get())) < 0.5:
line2.set_color('g')
line3.set_color('g')
line4.set_color('g')
else:
line2.set_color('r')
line3.set_color('r')
line4.set_color('r')
ax.relim()
ax.autoscale_view()
fig.canvas.flush_events()
# canvas = FigureCanvasTkAgg(fig,master=app)
# canvas.get_tk_widget().grid(row=0, column=1, sticky="nsw")
canvas.draw()
f.write(data_line)
f.write('\n')
# if bv1.get() == True:
# plottingqueue = app.after(oxygen_plotting())
# else:
# app.after_cancel(plottingqueue)
# Stop button and termination function
def panic():
global stop_val
if messagebox.askokcancel("Oxygen Control", "WARNING: All gas flow will be zeroed."):
stop_now.set(True)
flow_controller_O2.set_flow_rate(0)
flow_controller_Ar.set_flow_rate(0)
messagebox.showwarning("Oxygen Control", "Turn shutters of both oxygen and argon gas lines to full right, then press 'ok'.")
messagebox.showwarning("Oxygen Control", "When the pressure gauges reache 5 psi, twist the valves CLOCKWISE until they are fully closed. Press 'ok' to continue.")
stop_button = ctk.CTkButton(app, text="STOP",border_color="red",fg_color="red", font=('Arial',18,"bold"), command=lambda:panic())
stop_button.grid(row=2, column=2, rowspan=3, columnspan=1, padx=20, pady=5, ipady=50)
def on_closing():
total_flow_entry.unbind()
flow_total_flow_entry.unbind()
if messagebox.askokcancel("Oxygen Control", "Do you want to quit?"):
loop == False
app.destroy()
app.protocol("WM_DELETE_WINDOW", on_closing)
app.mainloop()
if __name__ == "__main__":
create_gui() #creates the gui for testing when run