valve_control_window
This module defines the ValveControlWindow class, a Toplevel window in the GUI designed for direct manual control of individual valves and the door motor on the experimental rig.
It reads valve configuration (number of valves) from a TOML file, creates buttons for each valve,
allows toggling their state (Open/Closed), and provides a button to move the motor
(Up/Down). Valve state changes and motor commands are sent to Arduino via controllers.arduino_control.ArduinoManager
.
1""" 2This module defines the ValveControlWindow class, a Toplevel window in the GUI 3designed for direct manual control of individual valves and the door motor 4on the experimental rig. 5 6It reads valve configuration (number of valves) from a TOML file, creates buttons for each valve, 7allows toggling their state (Open/Closed), and provides a button to move the motor 8(Up/Down). Valve state changes and motor commands are sent to Arduino via `controllers.arduino_control.ArduinoManager`. 9""" 10 11import tkinter as tk 12import toml 13import numpy as np 14from controllers.arduino_control import ArduinoManager 15import system_config 16 17from views.gui_common import GUIUtils 18 19rig_config = system_config.get_rig_config() 20 21with open(rig_config, "r") as f: 22 VALVE_CONFIG = toml.load(f)["valve_config"] 23 24# pull total valves constant from toml config 25TOTAL_CURRENT_VALVES = VALVE_CONFIG["TOTAL_CURRENT_VALVES"] 26VALVES_PER_SIDE = TOTAL_CURRENT_VALVES // 2 27 28 29class ValveControlWindow(tk.Toplevel): 30 """ 31 A Toplevel window providing manual control over individual valves and the main motor. 32 33 This window displays buttons for each configured valve, allowing users to toggle them 34 between 'Open' and 'Closed' states. It also includes a button to command the 35 main motor to move 'Up' or 'Down'. State changes and commands are sent to the 36 connected Arduino controller. 37 38 Attributes 39 ---------- 40 - **arduino_controller** (*ArduinoManager*): An instance of the controller class 41 responsible for communicating with the Arduino board. 42 - **valve_selections** (*np.ndarray*): A NumPy array storing the current state (0 for Closed, 1 for Open) 43 of each valve. The index corresponds to the valve number (0-indexed). 44 - **buttons** (*List[tk.Button OR None]*): A list holding references to the `tk.Button` widgets 45 for each valve, allowing individual access for state updates. 46 - **motor_button** (*tk.Button*): The `tk.Button` widget used to control the motor's position. 47 - **motor_down** (*bool*): A flag indicating the current assumed state of the motor 48 (True if commanded Down, False if commanded Up). 49 - **valves_frame** (*tk.Frame*): The main container frame within the Toplevel window holding the control buttons. 50 51 Methods 52 ------- 53 - `show()` 54 Makes the Valve Control window visible (`deiconify`). 55 - `create_interface()` 56 Sets up the main frame (`valves_frame`) and calls `create_buttons` to populate it. 57 - `toggle_motor()` 58 Callback for the motor control button. Toggles the `motor_down` state, updates the button 59 text/color, and sends the corresponding "UP" or "DOWN" command to the Arduino. 60 - `create_buttons()` 61 Creates and arranges the `tk.Button` widgets for each valve based on 62 `TOTAL_CURRENT_VALVES` and `VALVES_PER_SIDE`. Creates the motor control button. 63 - `toggle_valve_button(valve_num)` 64 Callback for individual valve buttons. Toggles the state of the specified valve 65 in `valve_selections`, updates the button's text/color, and sends the "OPEN SPECIFIC" 66 command followed by the current state of all valves (`valve_selections`) to the Arduino. 67 """ 68 69 def __init__(self, arduino_controller: ArduinoManager) -> None: 70 super().__init__() 71 self.title("Valve Control") 72 self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) 73 self.bind("<Control-w>", lambda event: self.withdraw()) 74 75 for i in range(3): 76 self.grid_columnconfigure(i, weight=1) 77 for i in range(2): 78 self.grid_rowconfigure(i, weight=1) 79 80 self.resizable(False, False) 81 82 self.arduino_controller = arduino_controller 83 84 window_icon_path = GUIUtils.get_window_icon_path() 85 GUIUtils.set_program_icon(self, window_icon_path) 86 87 self.valve_selections = np.zeros((TOTAL_CURRENT_VALVES,), dtype=np.int8) 88 89 self.create_interface() 90 self.motor_down = False 91 92 # we don't want to show this window until the user decides to click the corresponding button, 93 # withdraw (hide) it for now 94 self.withdraw() 95 96 def show(self): 97 """Makes the window visible by calling `deiconify`.""" 98 self.deiconify() 99 100 def create_interface(self): 101 """Creates the main frame for holding the valve and motor buttons.""" 102 self.valves_frame = tk.Frame(self) 103 104 self.valves_frame.grid(row=0, column=0, pady=10, sticky="nsew") 105 self.valves_frame.grid_columnconfigure(0, weight=1) 106 self.valves_frame.grid_rowconfigure(0, weight=1) 107 108 self.create_buttons() 109 110 def toggle_motor(self): 111 """ 112 Toggles the motor's position between 'Up' and 'Down'. 113 114 Updates the motor control button's appearance (text and color) to reflect the 115 new target state and sends the appropriate command ("UP\n" or "DOWN\n") 116 to the Arduino controller. Also updates the internal `motor_down` flag. 117 """ 118 # if motor is not down, command it down 119 if self.motor_down is False: 120 self.motor_button.configure(text="Move Motor Up", bg="blue", fg="white") 121 122 motor_command = "DOWN\n".encode("utf-8") 123 self.motor_down = True 124 else: 125 self.motor_button.configure(text="Move motor down", bg="green", fg="black") 126 127 motor_command = "UP\n".encode("utf-8") 128 self.motor_down = False 129 130 self.arduino_controller.send_command(command=motor_command) 131 132 def create_buttons(self): 133 """ 134 Creates and arranges the buttons for valve and motor control. 135 136 Populates the `self.buttons` list with `tk.Button` widgets for each valve, 137 placing them in two columns ("Side One" and "Side Two"). Creates the 138 motor control button and places it in the center column. 139 """ 140 # Create list of buttons to be able to access each one easily later 141 self.buttons = [None] * TOTAL_CURRENT_VALVES 142 143 # Side one valves (1-4) 144 for i in range(VALVES_PER_SIDE): # This runs 0, 1, 2, 3 145 btn_text = f"Valve {i + 1}: Closed" 146 button = GUIUtils.create_button( 147 self.valves_frame, 148 btn_text, 149 lambda i=i: self.toggle_valve_button(i), 150 "firebrick1", 151 row=i, 152 column=0, 153 )[1] 154 self.buttons[i] = button 155 156 # This runs 4, 5, 6, 7 157 btn_text = f"Valve {i + VALVES_PER_SIDE + 1}: Closed" 158 button = GUIUtils.create_button( 159 self.valves_frame, 160 btn_text, 161 lambda i=i + VALVES_PER_SIDE: self.toggle_valve_button(i), 162 "firebrick1", 163 row=i, 164 column=2, 165 )[1] 166 self.buttons[i + VALVES_PER_SIDE] = button 167 168 self.motor_button = GUIUtils.create_button( 169 self.valves_frame, 170 "Move Motor Down", 171 command=lambda: self.toggle_motor(), 172 bg="green", 173 row=TOTAL_CURRENT_VALVES, 174 column=1, 175 )[1] 176 177 def toggle_valve_button(self, valve_num: int): 178 """ 179 Updates the state of the clicked valve (`valve_num`) in the `valve_selections` array. 180 Changes the button's text and background color to reflect the new state (Open/Green or Closed/Red). 181 Sends an "OPEN SPECIFIC" command to the Arduino, followed by the byte representation 182 of the entire `valve_selections` array, instructing the Arduino which valves should be open or closed. 183 184 Parameters 185 ---------- 186 - **valve_num** (*int*): The 0-based index of the valve whose button was clicked. 187 """ 188 # if a valve is currently a zero (off), make it a 1, and turn the button green 189 if self.valve_selections[valve_num] == 0: 190 self.buttons[valve_num].configure( 191 text=f"Valve {valve_num + 1}: Open", bg="green" 192 ) 193 self.valve_selections[valve_num] = 1 194 195 # if a valve is currently a one (on), make it a zero (off), and turn the button red 196 else: 197 self.buttons[valve_num].configure( 198 text=f"Valve {valve_num + 1}: Closed", bg="firebrick1" 199 ) 200 self.valve_selections[valve_num] = 0 201 202 open_command = "OPEN SPECIFIC\n".encode("utf-8") 203 self.arduino_controller.send_command(command=open_command) 204 205 # send desired valve states to the arduino 206 selections = self.valve_selections.tobytes() 207 self.arduino_controller.send_command(command=selections)
30class ValveControlWindow(tk.Toplevel): 31 """ 32 A Toplevel window providing manual control over individual valves and the main motor. 33 34 This window displays buttons for each configured valve, allowing users to toggle them 35 between 'Open' and 'Closed' states. It also includes a button to command the 36 main motor to move 'Up' or 'Down'. State changes and commands are sent to the 37 connected Arduino controller. 38 39 Attributes 40 ---------- 41 - **arduino_controller** (*ArduinoManager*): An instance of the controller class 42 responsible for communicating with the Arduino board. 43 - **valve_selections** (*np.ndarray*): A NumPy array storing the current state (0 for Closed, 1 for Open) 44 of each valve. The index corresponds to the valve number (0-indexed). 45 - **buttons** (*List[tk.Button OR None]*): A list holding references to the `tk.Button` widgets 46 for each valve, allowing individual access for state updates. 47 - **motor_button** (*tk.Button*): The `tk.Button` widget used to control the motor's position. 48 - **motor_down** (*bool*): A flag indicating the current assumed state of the motor 49 (True if commanded Down, False if commanded Up). 50 - **valves_frame** (*tk.Frame*): The main container frame within the Toplevel window holding the control buttons. 51 52 Methods 53 ------- 54 - `show()` 55 Makes the Valve Control window visible (`deiconify`). 56 - `create_interface()` 57 Sets up the main frame (`valves_frame`) and calls `create_buttons` to populate it. 58 - `toggle_motor()` 59 Callback for the motor control button. Toggles the `motor_down` state, updates the button 60 text/color, and sends the corresponding "UP" or "DOWN" command to the Arduino. 61 - `create_buttons()` 62 Creates and arranges the `tk.Button` widgets for each valve based on 63 `TOTAL_CURRENT_VALVES` and `VALVES_PER_SIDE`. Creates the motor control button. 64 - `toggle_valve_button(valve_num)` 65 Callback for individual valve buttons. Toggles the state of the specified valve 66 in `valve_selections`, updates the button's text/color, and sends the "OPEN SPECIFIC" 67 command followed by the current state of all valves (`valve_selections`) to the Arduino. 68 """ 69 70 def __init__(self, arduino_controller: ArduinoManager) -> None: 71 super().__init__() 72 self.title("Valve Control") 73 self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) 74 self.bind("<Control-w>", lambda event: self.withdraw()) 75 76 for i in range(3): 77 self.grid_columnconfigure(i, weight=1) 78 for i in range(2): 79 self.grid_rowconfigure(i, weight=1) 80 81 self.resizable(False, False) 82 83 self.arduino_controller = arduino_controller 84 85 window_icon_path = GUIUtils.get_window_icon_path() 86 GUIUtils.set_program_icon(self, window_icon_path) 87 88 self.valve_selections = np.zeros((TOTAL_CURRENT_VALVES,), dtype=np.int8) 89 90 self.create_interface() 91 self.motor_down = False 92 93 # we don't want to show this window until the user decides to click the corresponding button, 94 # withdraw (hide) it for now 95 self.withdraw() 96 97 def show(self): 98 """Makes the window visible by calling `deiconify`.""" 99 self.deiconify() 100 101 def create_interface(self): 102 """Creates the main frame for holding the valve and motor buttons.""" 103 self.valves_frame = tk.Frame(self) 104 105 self.valves_frame.grid(row=0, column=0, pady=10, sticky="nsew") 106 self.valves_frame.grid_columnconfigure(0, weight=1) 107 self.valves_frame.grid_rowconfigure(0, weight=1) 108 109 self.create_buttons() 110 111 def toggle_motor(self): 112 """ 113 Toggles the motor's position between 'Up' and 'Down'. 114 115 Updates the motor control button's appearance (text and color) to reflect the 116 new target state and sends the appropriate command ("UP\n" or "DOWN\n") 117 to the Arduino controller. Also updates the internal `motor_down` flag. 118 """ 119 # if motor is not down, command it down 120 if self.motor_down is False: 121 self.motor_button.configure(text="Move Motor Up", bg="blue", fg="white") 122 123 motor_command = "DOWN\n".encode("utf-8") 124 self.motor_down = True 125 else: 126 self.motor_button.configure(text="Move motor down", bg="green", fg="black") 127 128 motor_command = "UP\n".encode("utf-8") 129 self.motor_down = False 130 131 self.arduino_controller.send_command(command=motor_command) 132 133 def create_buttons(self): 134 """ 135 Creates and arranges the buttons for valve and motor control. 136 137 Populates the `self.buttons` list with `tk.Button` widgets for each valve, 138 placing them in two columns ("Side One" and "Side Two"). Creates the 139 motor control button and places it in the center column. 140 """ 141 # Create list of buttons to be able to access each one easily later 142 self.buttons = [None] * TOTAL_CURRENT_VALVES 143 144 # Side one valves (1-4) 145 for i in range(VALVES_PER_SIDE): # This runs 0, 1, 2, 3 146 btn_text = f"Valve {i + 1}: Closed" 147 button = GUIUtils.create_button( 148 self.valves_frame, 149 btn_text, 150 lambda i=i: self.toggle_valve_button(i), 151 "firebrick1", 152 row=i, 153 column=0, 154 )[1] 155 self.buttons[i] = button 156 157 # This runs 4, 5, 6, 7 158 btn_text = f"Valve {i + VALVES_PER_SIDE + 1}: Closed" 159 button = GUIUtils.create_button( 160 self.valves_frame, 161 btn_text, 162 lambda i=i + VALVES_PER_SIDE: self.toggle_valve_button(i), 163 "firebrick1", 164 row=i, 165 column=2, 166 )[1] 167 self.buttons[i + VALVES_PER_SIDE] = button 168 169 self.motor_button = GUIUtils.create_button( 170 self.valves_frame, 171 "Move Motor Down", 172 command=lambda: self.toggle_motor(), 173 bg="green", 174 row=TOTAL_CURRENT_VALVES, 175 column=1, 176 )[1] 177 178 def toggle_valve_button(self, valve_num: int): 179 """ 180 Updates the state of the clicked valve (`valve_num`) in the `valve_selections` array. 181 Changes the button's text and background color to reflect the new state (Open/Green or Closed/Red). 182 Sends an "OPEN SPECIFIC" command to the Arduino, followed by the byte representation 183 of the entire `valve_selections` array, instructing the Arduino which valves should be open or closed. 184 185 Parameters 186 ---------- 187 - **valve_num** (*int*): The 0-based index of the valve whose button was clicked. 188 """ 189 # if a valve is currently a zero (off), make it a 1, and turn the button green 190 if self.valve_selections[valve_num] == 0: 191 self.buttons[valve_num].configure( 192 text=f"Valve {valve_num + 1}: Open", bg="green" 193 ) 194 self.valve_selections[valve_num] = 1 195 196 # if a valve is currently a one (on), make it a zero (off), and turn the button red 197 else: 198 self.buttons[valve_num].configure( 199 text=f"Valve {valve_num + 1}: Closed", bg="firebrick1" 200 ) 201 self.valve_selections[valve_num] = 0 202 203 open_command = "OPEN SPECIFIC\n".encode("utf-8") 204 self.arduino_controller.send_command(command=open_command) 205 206 # send desired valve states to the arduino 207 selections = self.valve_selections.tobytes() 208 self.arduino_controller.send_command(command=selections)
A Toplevel window providing manual control over individual valves and the main motor.
This window displays buttons for each configured valve, allowing users to toggle them between 'Open' and 'Closed' states. It also includes a button to command the main motor to move 'Up' or 'Down'. State changes and commands are sent to the connected Arduino controller.
Attributes
- arduino_controller (ArduinoManager): An instance of the controller class responsible for communicating with the Arduino board.
- valve_selections (np.ndarray): A NumPy array storing the current state (0 for Closed, 1 for Open) of each valve. The index corresponds to the valve number (0-indexed).
- buttons (List[tk.Button OR None]): A list holding references to the
tk.Button
widgets for each valve, allowing individual access for state updates. - motor_button (tk.Button): The
tk.Button
widget used to control the motor's position. - motor_down (bool): A flag indicating the current assumed state of the motor (True if commanded Down, False if commanded Up).
- valves_frame (tk.Frame): The main container frame within the Toplevel window holding the control buttons.
Methods
show()
Makes the Valve Control window visible (deiconify
).create_interface()
Sets up the main frame (valves_frame
) and callscreate_buttons
to populate it.toggle_motor()
Callback for the motor control button. Toggles themotor_down
state, updates the button text/color, and sends the corresponding "UP" or "DOWN" command to the Arduino.create_buttons()
Creates and arranges thetk.Button
widgets for each valve based onTOTAL_CURRENT_VALVES
andVALVES_PER_SIDE
. Creates the motor control button.toggle_valve_button(valve_num)
Callback for individual valve buttons. Toggles the state of the specified valve invalve_selections
, updates the button's text/color, and sends the "OPEN SPECIFIC" command followed by the current state of all valves (valve_selections
) to the Arduino.
70 def __init__(self, arduino_controller: ArduinoManager) -> None: 71 super().__init__() 72 self.title("Valve Control") 73 self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) 74 self.bind("<Control-w>", lambda event: self.withdraw()) 75 76 for i in range(3): 77 self.grid_columnconfigure(i, weight=1) 78 for i in range(2): 79 self.grid_rowconfigure(i, weight=1) 80 81 self.resizable(False, False) 82 83 self.arduino_controller = arduino_controller 84 85 window_icon_path = GUIUtils.get_window_icon_path() 86 GUIUtils.set_program_icon(self, window_icon_path) 87 88 self.valve_selections = np.zeros((TOTAL_CURRENT_VALVES,), dtype=np.int8) 89 90 self.create_interface() 91 self.motor_down = False 92 93 # we don't want to show this window until the user decides to click the corresponding button, 94 # withdraw (hide) it for now 95 self.withdraw()
Construct a toplevel widget with the parent MASTER.
Valid resource names: background, bd, bg, borderwidth, class, colormap, container, cursor, height, highlightbackground, highlightcolor, highlightthickness, menu, relief, screen, takefocus, use, visual, width.
101 def create_interface(self): 102 """Creates the main frame for holding the valve and motor buttons.""" 103 self.valves_frame = tk.Frame(self) 104 105 self.valves_frame.grid(row=0, column=0, pady=10, sticky="nsew") 106 self.valves_frame.grid_columnconfigure(0, weight=1) 107 self.valves_frame.grid_rowconfigure(0, weight=1) 108 109 self.create_buttons()
Creates the main frame for holding the valve and motor buttons.
111 def toggle_motor(self): 112 """ 113 Toggles the motor's position between 'Up' and 'Down'. 114 115 Updates the motor control button's appearance (text and color) to reflect the 116 new target state and sends the appropriate command ("UP\n" or "DOWN\n") 117 to the Arduino controller. Also updates the internal `motor_down` flag. 118 """ 119 # if motor is not down, command it down 120 if self.motor_down is False: 121 self.motor_button.configure(text="Move Motor Up", bg="blue", fg="white") 122 123 motor_command = "DOWN\n".encode("utf-8") 124 self.motor_down = True 125 else: 126 self.motor_button.configure(text="Move motor down", bg="green", fg="black") 127 128 motor_command = "UP\n".encode("utf-8") 129 self.motor_down = False 130 131 self.arduino_controller.send_command(command=motor_command)
Toggles the motor's position between 'Up' and 'Down'.
Updates the motor control button's appearance (text and color) to reflect the
new target state and sends the appropriate command ("UP
" or "DOWN
")
to the Arduino controller. Also updates the internal motor_down
flag.