Skip to content

Commit 1ad82e8

Browse files
authored
refactor(abr-testing): Adding additional functionality to ABR data collection (#18956)
<!-- Thanks for taking the time to open a Pull Request (PR)! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests GitHub provides robust markdown to format your PR. Links, diagrams, pictures, and videos along with text formatting make it possible to create a rich and informative PR. For more information on GitHub markdown, see: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview Including HEPA/UV to Data Collection Script ## Test Plan and Hands on Testing - Ran test script ## Changelog - Added timestamping of hepa / uv turning on and off - Changed the ABR Temperature humidity sensors to reference storage directory in config file - changed batch_delete_cells to include sheet id as an input ## Review requests <!-- - What do you need from reviewers to feel confident this PR is ready to merge? - Ask questions. --> ## Risk assessment - hard coded google sheet ids, but not really a problem because these scripts are used by ABR
1 parent 297c4fa commit 1ad82e8

File tree

5 files changed

+266
-31
lines changed

5 files changed

+266
-31
lines changed

abr-testing/abr_testing/automation/google_sheets_tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,21 @@ def delete_row(self, row_index: int) -> None:
9595
"""Delete Row from google sheet."""
9696
self.worksheet.delete_rows(row_index)
9797

98-
def batch_delete_rows(self, row_indices: List[int]) -> None:
98+
def batch_delete_rows(self, row_indices: List[int], sheet_id: str) -> None:
9999
"""Batch delete rows in list of indices."""
100100
delete_body = {
101101
"requests": [
102102
{
103103
"deleteDimension": {
104104
"range": {
105-
"sheetId": 0,
105+
"sheetId": sheet_id,
106106
"dimension": "ROWS",
107107
"startIndex": index,
108108
"endIndex": index + 1,
109109
}
110110
}
111111
}
112-
for index in row_indices
112+
for index in sorted(row_indices, reverse=True)
113113
]
114114
}
115115
self.spread_sheet.batch_update(body=delete_body)
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
"""Record HEPA UV Testing."""
2+
import datetime
3+
import argparse
4+
from typing import List, Any
5+
from abr_testing.automation import google_sheets_tool
6+
import os
7+
import sys
8+
import json
9+
10+
11+
def get_hepa_serials(storage_directory: str) -> List[List[Any]]:
12+
"""Get HEPA / UV Serial Number."""
13+
try:
14+
ips_path = os.path.join(storage_directory, "IPs.json")
15+
ip_file = json.load(open(ips_path))
16+
robot_dict = ip_file.get("ip_address_list")
17+
except FileNotFoundError:
18+
print(f"Add IPs.json file to: {storage_directory}.")
19+
sys.exit()
20+
robot_name_and_hepa_serial = list(map(list, (zip(*robot_dict.values()))))
21+
return robot_name_and_hepa_serial
22+
23+
24+
def turning_uv_and_hepa_on(
25+
google_sheet: google_sheets_tool.google_sheet, all_robots: List[List[Any]]
26+
) -> None:
27+
"""Create set of lines to add to google sheet."""
28+
today = datetime.date.today()
29+
formatted_date = today.strftime("%m/%d/%Y")
30+
timestamp = datetime.datetime.now()
31+
formatted_timestamp = timestamp.strftime("%m/%d/%Y %H:%M:%S")
32+
num_robots = len(all_robots[0])
33+
blank_column = [""] * num_robots
34+
date_column = [formatted_date] * num_robots
35+
fan_start_column = [formatted_timestamp] * num_robots
36+
uv_cycles_column = [1] * num_robots
37+
uv_duration_column = [15] * num_robots
38+
39+
all_columns = [
40+
date_column,
41+
all_robots[0],
42+
all_robots[1],
43+
fan_start_column,
44+
blank_column,
45+
blank_column,
46+
blank_column,
47+
uv_cycles_column,
48+
uv_duration_column,
49+
]
50+
51+
start_row = google_sheet.get_index_row() + 1
52+
google_sheet.batch_update_cells(all_columns, "A", start_row, "1790601450")
53+
54+
55+
def turning_hepa_off_and_uv_on(
56+
google_sheet: google_sheets_tool.google_sheet, all_robots: List[List[str]]
57+
) -> None:
58+
"""Records when Hepa is Turned Off and UV on."""
59+
# List of Expected Headers
60+
expected_headers = {
61+
"Date",
62+
"Robot",
63+
"Hepa Filter Serial",
64+
"Fan Start Time",
65+
"Fan Stop Time",
66+
"Fan On Duration (timestamp)",
67+
"Fan On Duration (min)",
68+
"UV # of Cycles",
69+
"UV Duration (min)",
70+
"Errors",
71+
"Level",
72+
"Description",
73+
}
74+
all_data = google_sheet.get_all_data(expected_headers)
75+
todays_rows = []
76+
77+
# Initialize column-wise lists
78+
dates, robots, hepa_serials = [], [], []
79+
fan_start_times, fan_stop_times, durations, durations_min = [], [], [], []
80+
uv_cycles, uv_durations, errors, levels, descriptions = [], [], [], [], []
81+
82+
# Timestamp formatting
83+
today = datetime.date.today()
84+
formatted_date = today.strftime("%m/%d/%Y")
85+
timestamp = datetime.datetime.now()
86+
formatted_timestamp = timestamp.strftime("%m/%d/%Y %H:%M:%S")
87+
88+
# Process matching rows
89+
for row in all_data:
90+
if (row["Date"] == formatted_date) and (row["Robot"] in all_robots[0]):
91+
start_time_str = row["Fan Start Time"]
92+
start_time = datetime.datetime.strptime(start_time_str, "%m/%d/%Y %H:%M:%S")
93+
94+
duration = timestamp - start_time
95+
duration_str = str(duration)
96+
duration_min = duration.total_seconds() / 60
97+
98+
# Update row with new info
99+
row["Fan Stop Time"] = formatted_timestamp
100+
row["Fan On Duration (timestamp)"] = duration_str
101+
row["Fan On Duration (min)"] = duration_min
102+
row["UV # of Cycles"] = 2
103+
row["UV Duration (min)"] = 30
104+
105+
todays_rows.append(row)
106+
107+
# Populate columns
108+
dates.append(row["Date"])
109+
robots.append(row["Robot"])
110+
hepa_serials.append(row["Hepa Filter Serial"])
111+
fan_start_times.append(row["Fan Start Time"])
112+
fan_stop_times.append(row["Fan Stop Time"])
113+
durations.append(duration_str)
114+
durations_min.append(duration_min)
115+
uv_cycles.append(2)
116+
uv_durations.append(30)
117+
errors.append(row.get("Errors", ""))
118+
levels.append(row.get("Level", ""))
119+
descriptions.append(row.get("Description", ""))
120+
121+
# Combine all columns into a nested list
122+
nested_list = [
123+
dates,
124+
robots,
125+
hepa_serials,
126+
fan_start_times,
127+
fan_stop_times,
128+
durations,
129+
durations_min,
130+
uv_cycles,
131+
uv_durations,
132+
errors,
133+
levels,
134+
descriptions,
135+
]
136+
last_row_index = google_sheet.get_index_row() + 1
137+
starting_row_index = last_row_index - len(all_robots[0])
138+
row_nums = list(range(starting_row_index, last_row_index))
139+
row_indices = [i - 1 for i in row_nums] # API needs 0-based
140+
google_sheet.batch_delete_rows(row_indices, "1790601450")
141+
google_sheet.update_row_index()
142+
google_sheet.batch_update_cells(nested_list, "A", starting_row_index, "1790601450")
143+
144+
145+
def run(
146+
turning_hepa_fan: str,
147+
google_sheet_name: str,
148+
storage_directory: str,
149+
) -> None:
150+
"""Main control function."""
151+
try:
152+
credentials_path = os.path.join(storage_directory, "credentials.json")
153+
except FileNotFoundError:
154+
print(f"Add credentials.json file to: {storage_directory}.")
155+
sys.exit()
156+
hepa_uv_sheet = google_sheets_tool.google_sheet(
157+
credentials_path, google_sheet_name, 5
158+
)
159+
robot_names_and_hepa_serials = get_hepa_serials(storage_directory)
160+
if turning_hepa_fan == "on":
161+
print("𖣘 TIME STAMPING START OF HEPA FANS & UV LIGHT.")
162+
else:
163+
print("𖣘 TIME STAMPING END OF HEPA FANS & UV LIGHT.")
164+
165+
answer = input("Do you want to exclude any robots (y/n)?")
166+
if answer.lower == "y":
167+
robots_to_exclude = input(
168+
"Enter robots you want to exclude in a comma separated list: "
169+
)
170+
exclude_list = [r.strip() for r in robots_to_exclude.split(",")]
171+
robots = robot_names_and_hepa_serials[0]
172+
serials = robot_names_and_hepa_serials[1]
173+
# Zip robots and serials into pairs, filter, then unzip back
174+
filtered_pairs = [
175+
(r, s) for r, s in zip(robots, serials) if r not in exclude_list
176+
]
177+
# Unzip into separate lists again
178+
filtered_robots, filtered_serials = (
179+
zip(*filtered_pairs) if filtered_pairs else ([], [])
180+
)
181+
robot_names_and_hepa_serials = [list(filtered_robots), list(filtered_serials)]
182+
else:
183+
robot_names_and_hepa_serials = robot_names_and_hepa_serials
184+
if turning_hepa_fan == "on":
185+
turning_uv_and_hepa_on(hepa_uv_sheet, robot_names_and_hepa_serials)
186+
else:
187+
turning_hepa_off_and_uv_on(hepa_uv_sheet, robot_names_and_hepa_serials)
188+
189+
190+
if __name__ == "__main__":
191+
parser = argparse.ArgumentParser(description="Record HEPA/UV Actions")
192+
parser.add_argument(
193+
"turning_hepa_fan",
194+
metavar="-hepa",
195+
type=str,
196+
nargs=1,
197+
help="'on' or 'off'",
198+
)
199+
parser.add_argument(
200+
"google_sheet_name",
201+
metavar="-google_sheet",
202+
type=str,
203+
nargs=1,
204+
help="Google sheet name.",
205+
)
206+
parser.add_argument(
207+
"storage_directory",
208+
metavar="-storage_directory",
209+
type=str,
210+
nargs=1,
211+
help="Path to long term storage directory for run logs.",
212+
)
213+
args = parser.parse_args()
214+
turning_hepa_fan = args.turning_hepa_fan[0]
215+
google_sheet_name = args.google_sheet_name[0]
216+
storage_directory = args.storage_directory[0]
217+
run(turning_hepa_fan, google_sheet_name, storage_directory)

abr-testing/abr_testing/tools/abr_lpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def remove_duplicate_data() -> None:
3131
else:
3232
row_indices.append(i)
3333
if len(row_indices) > 0:
34-
google_sheet_lpc.batch_delete_rows(row_indices)
34+
google_sheet_lpc.batch_delete_rows(row_indices, "0")
3535

3636

3737
if __name__ == "__main__":

abr-testing/abr_testing/tools/abr_setup.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_run_logs,
1313
abr_google_drive,
1414
abr_calibration_logs,
15+
abr_hepauv,
1516
)
1617
from abr_testing.tools import sync_abr_sheet
1718

@@ -50,7 +51,7 @@ def clean_sheet(sheet_name: str, credentials: str) -> Any:
5051
return
5152
print(f"Rows to be removed: {rem_rows}")
5253
try:
53-
sheet.batch_delete_rows(rem_rows)
54+
sheet.batch_delete_rows(rem_rows, "0")
5455
print("deleted rows")
5556
except Exception:
5657
print("could not delete rows")
@@ -66,11 +67,22 @@ def run_sync_abr_sheet(
6667
sync_abr_sheet.run(storage_directory, abr_data_sheet, room_conditions_sheet)
6768

6869

69-
def run_temp_sensor(ambient_conditions_sheet: str, credentials: str) -> None:
70+
def run_hepa_uv(
71+
turning_hepa_fan: str,
72+
google_sheet_name: str,
73+
storage_directory: str,
74+
) -> None:
75+
"""Record HEPA UVs."""
76+
abr_hepauv.run(turning_hepa_fan, google_sheet_name, storage_directory)
77+
78+
79+
def run_temp_sensor(
80+
ambient_conditions_sheet: str, credentials: str, storage_directory: str
81+
) -> None:
7082
"""Run temperature sensors on all robots."""
7183
# Remove entries > 60 days
7284
clean_sheet(ambient_conditions_sheet, credentials)
73-
processes = ABRAsairScript.run()
85+
processes = ABRAsairScript.run(storage_directory)
7486
for process in processes:
7587
process.start()
7688
time.sleep(20)
@@ -127,26 +139,36 @@ def main(configurations: configparser.ConfigParser) -> None:
127139
credentials = default["Credentials"]
128140
except KeyError as e:
129141
print("Cannot read config file\n" + str(e))
130-
142+
# Record HEPA/UV Tracking
143+
storage_directory = configurations["RUN-LOG"]["Storage"]
144+
sheet_name = configurations["RUN-LOG"]["Sheet_Name"]
145+
hepa_on = input("Are you turning HEPA Fans on or off? ")
146+
run_hepa_uv(hepa_on.lower(), sheet_name, storage_directory)
147+
if hepa_on == "off":
148+
continue_str = input(
149+
"Type 'continue' to continue with the rest of the set up script."
150+
)
151+
if continue_str == "continue":
152+
print("Script will continue.")
153+
else:
154+
sys.exit()
131155
# Run Temperature Sensors
132156
ambient_conditions_sheet = configurations["TEMP-SENSOR"]["Sheet_Url"]
133157
ambient_conditions_sheet_name = configurations["TEMP-SENSOR"]["Sheet_Name"]
134-
print("Starting temp sensors...")
135-
run_temp_sensor(ambient_conditions_sheet_name, credentials)
136-
print("Temp Sensors Started")
158+
print("Starting 🌡️ temp sensors 🌡️...")
159+
run_temp_sensor(ambient_conditions_sheet_name, credentials, storage_directory)
137160
# Get Run Logs and Record
138-
storage_directory = configurations["RUN-LOG"]["Storage"]
139161
email = configurations["RUN-LOG"]["Email"]
140162
drive_folder = configurations["RUN-LOG"]["Drive_Folder"]
141163
sheet_name = configurations["RUN-LOG"]["Sheet_Name"]
142164
sheet_url = configurations["RUN-LOG"]["Sheet_Url"]
143165
print(sheet_name)
144166
if storage_directory and drive_folder and sheet_name and email:
145-
print("Retrieving robot run logs...")
167+
print("🤖 Retrieving robot run logs...")
146168
get_abr_logs(storage_directory, drive_folder, email)
147-
print("Recording robot run logs...")
169+
print("📄 Recording robot run logs...")
148170
record_abr_logs(storage_directory, drive_folder, sheet_name, email)
149-
print("Run logs updated")
171+
print("Run logs updated")
150172
else:
151173
print("Storage, Email, or Drive Folder is missing, please fix configs")
152174
sys.exit(1)

hardware-testing/hardware_testing/scripts/ABRAsairScript.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import time
44
import json
55
import multiprocessing
6+
import os
67
from typing import Optional, List, Any
78

89

@@ -56,32 +57,26 @@ def run_command_on_ip(
5657
ssh = connect_ssh(curr_ip)
5758
status = execute(ssh, cd + cmd, [robot_names[index], "540", "5"])
5859
if status == 0:
59-
print(f"Envrironmental sensors for {curr_ip}, are now running")
60+
print(f"Environmental sensors for {curr_ip}, are now running")
6061
except Exception as e:
6162
print(f"Error running command on {curr_ip}: {e}")
6263

6364

64-
def run() -> List[Any]:
65+
def run(storage_directory: str) -> List[Any]:
6566
"""Run asair script module."""
6667
# Load Robot IPs
6768
cmd = "nohup python3 -m hardware_testing.scripts.abr_asair_sensor {name} {duration} {frequency}"
6869
cd = "cd /opt/opentrons-robot-server && "
6970
robot_ips = []
7071
robot_names = []
7172

72-
robot = input("Enter IP of robot (type 'all' to run on all robots): ")
73-
if robot.lower() == "all":
74-
ip_file = input("Path of IPs.json: ")
75-
with open(ip_file) as file:
76-
file_dict = json.load(file)
77-
robot_dict = file_dict.get("ip_address_list")
78-
robot_ips = list(robot_dict.keys())
79-
robot_names = list(robot_dict.values())
80-
else:
81-
robot_name = input("What is the name of the robot? ")
82-
robot_ips.append(robot)
83-
robot_names.append(robot_name)
84-
print("Executing Script on Robot(s):")
73+
ip_file = os.path.join(storage_directory, "IPs.json")
74+
with open(ip_file) as file:
75+
file_dict = json.load(file)
76+
robot_dict = file_dict.get("ip_address_list")
77+
robot_ips = list(robot_dict.keys())
78+
robot_names = list(map(list, (zip(*robot_dict.values()))))[0]
79+
print("Executing Script on Robots:")
8580
# Launch the processes for each robot.
8681
processes = []
8782
for index in range(len(robot_ips)):
@@ -94,7 +89,8 @@ def run() -> List[Any]:
9489

9590
if __name__ == "__main__":
9691
# Wait for all processes to finish.
97-
processes = run()
92+
storage_directory = ""
93+
processes = run(storage_directory)
9894
for process in processes:
9995
process.start()
10096
time.sleep(20)

0 commit comments

Comments
 (0)