Skip to content

Commit b4bc250

Browse files
georgevigelettepeterhollenderAlex
authored
Make hardware interface feature and notebook organization updates (#75)
Added ADC monitoring for HIGH voltage rails. Added calibrated power supply capabilities. This is a squash of the following commit messages: * Create burn_in_test.py * Fix runtime errors with refactored code, change default to use console supply not external supply. * Add case selection in printout and log file. * reorganize * remove old notebooks * Update to add duration selection, need to integrate cycling on and off automatically * Added logic to run test for prolonged periods of time, need to test to verify logic * updates tst05 and 06 * updates tst05 and 06 * updated scripts * Fix temperature check logic in burn-in test Remove unnecessary assignment of con_temp to 0. * Fig bug in sequence display * Update verification tests * Better handling threads * update verification tests * update tests, wip examples * WIP examples * updated uart handling of soft reset and made it a fire and forget command, also added a communication stress test script for the transmitter * Exclude examples from pre-commit * Update tst06_2x_burn_in.py * Update tst04_console_HV.py * added get tx module count for UI and Scripts * update for tx count, reset and demo dfu scripts * Update database tutorial * Create separate more relaxed rapid temp increase value of 5 seconds for console * Add separate console temp increase value to tst04 and tst06 * Add new variables to console log statements * Add extra safety check for stopping tx usb monitoring * updated uart handling of soft reset and made it a fire and forget command, also added a communication stress test script for the transmitter * added get tx module count for UI and Scripts * update for tx count, reset and demo dfu scripts * Remove 40C initial temp cutoff * fixed thermal stress script * Change pulse interval in tst05 and tst06 from 200ms to 100ms * Significant refactor to tst04, tst05, tst06 to be class based, thread safe, accept command line parameters Deprecated similar test files to avoid confusion * Remove rapid increase checks from tst04, tst05, tst06 * added voltage monitor calls * Enhanced Voltage Display * added raw dac control * plotting of curves * rework to use calibrated HV supply set and get * fix script * removed shebang * fixed linter issues * update script locations * vmon script * remove log file and moved database tool script --------- Co-authored-by: Peter Hollender <peterhollender@gmail.com> Co-authored-by: Alex <akagan@openwater.cc>
1 parent 4c58110 commit b4bc250

58 files changed

Lines changed: 8619 additions & 830 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ Thumbs.db
161161
.vscode
162162
/db_dvc
163163
/logs
164+
/examples/tutorials/tutorial_database
164165

165166
# ONNX checkpoints
166167
*.onnx
168+
169+
notebooks/calibration_coeffs.py
170+
neg_cal.csv
171+
pos_cal.csv
172+
calibration_coeffs.py
173+
hv_calibration_coeffs.c
174+
hv_calibration_coeffs.h

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,5 @@ repos:
8888
- id: check-dependabot
8989
- id: check-github-workflows
9090
- id: check-readthedocs
91+
92+
exclude: (?x)( ^examples )

examples/legacy/burn_in_test.py

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
import os
5+
import sys
6+
import threading
7+
import time
8+
from datetime import datetime
9+
from pathlib import Path
10+
11+
if os.name == 'nt':
12+
import msvcrt
13+
else:
14+
import select
15+
16+
import numpy as np
17+
18+
from openlifu.bf.pulse import Pulse
19+
from openlifu.bf.sequence import Sequence
20+
from openlifu.db import Database
21+
from openlifu.geo import Point
22+
from openlifu.io.LIFUInterface import LIFUInterface
23+
from openlifu.plan.solution import Solution
24+
25+
"""
26+
Burn-in Test Script
27+
- User selects one of three test cases.
28+
- Test runs for a fixed total duration or until a thermal shutdown occurs.
29+
- Logs temperature and device status.
30+
"""
31+
32+
one_day_in_seconds = 24 * 60 * 60
33+
34+
# ------------------- Logging Setup -------------------
35+
logger = logging.getLogger(__name__)
36+
logger.setLevel(logging.INFO)
37+
if not logger.hasHandlers():
38+
handler = logging.StreamHandler()
39+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
40+
logger.addHandler(handler)
41+
logger.propagate = False
42+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
43+
file_handle = logging.FileHandler(f"burn_in_test_{timestamp}.log", mode='w', encoding='utf-8')
44+
logger.addHandler(file_handle)
45+
46+
# ------------------- Test Case Definitions -------------------
47+
test_cases = [
48+
{
49+
'name': 'Case 1: 65V, 10ms pulse, 10 minutes',
50+
'voltage': 65,
51+
'duration_msec': 10,
52+
'total_test_time': 10 * 60, # seconds
53+
},
54+
{
55+
'name': 'Case 2: 30V, 100ms pulse, 2 minutes',
56+
'voltage': 30,
57+
'duration_msec': 100,
58+
'total_test_time': 2 * 60, # seconds
59+
},
60+
{
61+
'name': 'Case 3: 20V, 100ms pulse, 10 minutes',
62+
'voltage': 20,
63+
'duration_msec': 100,
64+
'total_test_time': 10 * 60, # seconds
65+
},
66+
]
67+
68+
test_duration = [
69+
{
70+
'name': 'Case 1: Extended Test, 1 day',
71+
'run_time': one_day_in_seconds,
72+
'times_per_day': 8,
73+
},
74+
{
75+
'name': 'Case 2: Lifetime Test, 365 days',
76+
'run_time': 365*one_day_in_seconds,
77+
'times_per_day': 8,
78+
},
79+
]
80+
81+
82+
frequency_kHz = 400
83+
interval_msec = 200
84+
num_modules = 2
85+
use_external_power_supply = False
86+
console_shutoff_temp_C = 70.0
87+
rapid_temp_shutoff_C = 40
88+
rapid_temp_shutoff_seconds = 5
89+
rapid_temp_increase_per_second_shutoff_C = 3
90+
log_interval = 1
91+
92+
# ------------------- User Input Section -------------------
93+
logger.info("Available Burn-in Test Cases:")
94+
for case in test_cases:
95+
logger.info(f"{case['name']}")
96+
97+
while True:
98+
choice = input("\nSelect a test case by number (1, 2, 3): ").strip()
99+
if choice.isdigit() and 1 <= int(choice) <= len(test_cases):
100+
selected = test_cases[int(choice)-1]
101+
break
102+
logger.info("Invalid selection. Please enter 1, 2, or 3.")
103+
104+
voltage = selected['voltage']
105+
duration_msec = selected['duration_msec']
106+
total_test_time = selected['total_test_time']
107+
108+
logger.info(f"Case {choice} Selected\n\n")
109+
110+
logger.info("Select a Test Duration (1, 2):")
111+
for case in test_duration:
112+
logger.info(f"{case['name']} {case['times_per_day']} times per day for {case['run_time']//(one_day_in_seconds)} day{'s' if case['run_time']//(one_day_in_seconds) > 1 else ''}")
113+
114+
while True:
115+
choice = input("\nSelect a test case by number (1, 2): ").strip()
116+
if choice.isdigit() and 1 <= int(choice) <= len(test_duration):
117+
selected = test_duration[int(choice)-1]
118+
break
119+
logger.info("Invalid selection. Please enter 1 or 2.")
120+
121+
run_time = selected['run_time']
122+
times_per_day = selected['times_per_day']
123+
124+
logger.info(f"Case {choice} Selected\n")
125+
logger.info(f"This will run the burn-in test {times_per_day} times a day for {run_time//(one_day_in_seconds)} day{'s' if run_time//(one_day_in_seconds) > 1 else ''}.\n")
126+
127+
# ------------------- Device Setup -------------------
128+
here = Path(__file__).parent.resolve()
129+
db_path = here / ".." / "db_dvc"
130+
db = Database(db_path)
131+
arr = db.load_transducer(f"openlifu_{num_modules}x400_evt1")
132+
arr.sort_by_pin()
133+
134+
xInput, yInput, zInput = 0, 0, 50
135+
target = Point(position=(xInput, yInput, zInput), units="mm")
136+
focus = target.get_position(units="mm")
137+
distances = np.sqrt(np.sum((focus - arr.get_positions(units="mm"))**2, 1)).reshape(1, -1)
138+
tof = distances * 1e-3 / 1500
139+
delays = tof.max() - tof
140+
apodizations = np.ones((1, arr.numelements()))
141+
142+
logger.info("Starting Burn-in Test...")
143+
interface = LIFUInterface(ext_power_supply=use_external_power_supply)
144+
tx_connected, hv_connected = interface.is_device_connected()
145+
146+
if not use_external_power_supply and not tx_connected:
147+
logger.warning("TX device not connected. Attempting to turn on 12V...")
148+
interface.hvcontroller.turn_12v_on()
149+
time.sleep(2)
150+
interface.stop_monitoring()
151+
del interface
152+
time.sleep(1)
153+
logger.info("Reinitializing LIFU interface after powering 12V...")
154+
interface = LIFUInterface(ext_power_supply=use_external_power_supply)
155+
tx_connected, hv_connected = interface.is_device_connected()
156+
157+
if not use_external_power_supply:
158+
if hv_connected:
159+
logger.info(f" HV Connected: {hv_connected}")
160+
else:
161+
logger.error("❌ HV NOT fully connected.")
162+
sys.exit(1)
163+
else:
164+
logger.info(" Using external power supply")
165+
166+
if tx_connected:
167+
logger.info(f" TX Connected: {tx_connected}")
168+
logger.info("✅ LIFU Device fully connected.")
169+
else:
170+
logger.error("❌ TX NOT fully connected.")
171+
sys.exit(1)
172+
173+
# ------------------- Temperature Monitoring -------------------
174+
stop_logging = False
175+
shutdown_event = threading.Event()
176+
177+
def monitor_temperature():
178+
start = time.time()
179+
shutdown = False
180+
prev_tx_temp = None
181+
prev_amb_temp = None
182+
prev_con_temp = None
183+
while not (stop_logging or shutdown):
184+
elapsed = time.time() - start
185+
if elapsed > total_test_time:
186+
logger.info(f"Total test time {total_test_time}s reached. Stopping test.")
187+
shutdown = True
188+
within_initial_time_threshold = elapsed < rapid_temp_shutoff_seconds
189+
if not use_external_power_supply:
190+
if prev_con_temp is None:
191+
prev_con_temp = interface.hvcontroller.get_temperature1()
192+
prev_con_temp = 0
193+
con_temp = interface.hvcontroller.get_temperature1()
194+
con_temp = 0
195+
if (con_temp - prev_con_temp) > rapid_temp_increase_per_second_shutoff_C:
196+
logger.warning(f"Console temperature rose from {prev_con_temp}°C to {con_temp}°C (above {rapid_temp_increase_per_second_shutoff_C}°C threshold) within {log_interval}s.")
197+
shutdown = True
198+
else:
199+
prev_con_temp = con_temp
200+
if prev_tx_temp is None:
201+
prev_tx_temp = interface.txdevice.get_temperature()
202+
tx_temp = interface.txdevice.get_temperature()
203+
if (tx_temp - prev_tx_temp) > rapid_temp_increase_per_second_shutoff_C:
204+
logger.warning(f"TX device temperature rose from {prev_tx_temp}°C to {tx_temp}°C (above {rapid_temp_increase_per_second_shutoff_C}°C threshold) within {log_interval}s.")
205+
shutdown = True
206+
else:
207+
prev_tx_temp = tx_temp
208+
if prev_amb_temp is None:
209+
prev_amb_temp = interface.txdevice.get_ambient_temperature()
210+
amb_temp = interface.txdevice.get_ambient_temperature()
211+
if (amb_temp - prev_amb_temp) > rapid_temp_increase_per_second_shutoff_C:
212+
logger.warning(f"Ambient temperature rose from {prev_amb_temp}°C to {amb_temp}°C (above {rapid_temp_increase_per_second_shutoff_C}°C threshold) within {log_interval}s.")
213+
shutdown = True
214+
else:
215+
prev_amb_temp = amb_temp
216+
if within_initial_time_threshold:
217+
if not use_external_power_supply and (con_temp > rapid_temp_shutoff_C):
218+
logger.warning(f"Console temperature {con_temp}°C exceeds rapid shutoff threshold of {rapid_temp_shutoff_C}°C within {rapid_temp_shutoff_seconds}s.")
219+
shutdown = True
220+
elif (tx_temp > rapid_temp_shutoff_C):
221+
logger.warning(f"TX device temperature {tx_temp}°C exceeds rapid shutoff threshold of {rapid_temp_shutoff_C}°C within {rapid_temp_shutoff_seconds}s.")
222+
shutdown = True
223+
elif (amb_temp > rapid_temp_shutoff_C):
224+
logger.warning(f"Ambient temperature {amb_temp}°C exceeds rapid shutoff threshold of {rapid_temp_shutoff_C}°C within {rapid_temp_shutoff_seconds}s.")
225+
shutdown = True
226+
if not use_external_power_supply and (con_temp > console_shutoff_temp_C):
227+
logger.warning(f"Console temperature {con_temp}°C exceeds shutoff threshold {console_shutoff_temp_C}°C.")
228+
shutdown = True
229+
if tx_temp > 70.0:
230+
logger.warning(f"TX device temperature {tx_temp}°C exceeds shutoff threshold 70.0°C.")
231+
shutdown = True
232+
if amb_temp > 70.0:
233+
logger.warning(f"Ambient temperature {amb_temp}°C exceeds shutoff threshold 70.0°C.")
234+
shutdown = True
235+
if shutdown:
236+
interface.txdevice.stop_trigger()
237+
shutdown_event.set()
238+
break
239+
time.sleep(log_interval)
240+
logger.info(f"Temperature monitoring stopped after {int(elapsed//60)}:{int(elapsed%60):02d}.")
241+
242+
# ------------------- Solution Setup -------------------
243+
pulse = Pulse(frequency=frequency_kHz*1e3, duration=duration_msec*1e-3)
244+
sequence = Sequence(
245+
pulse_interval=interval_msec*1e-3,
246+
pulse_count=int(total_test_time/(interval_msec*1e-3)),
247+
pulse_train_interval=0,
248+
pulse_train_count=1
249+
)
250+
pin_order = np.argsort([el.pin for el in arr.elements])
251+
solution = Solution(
252+
delays=delays[:, pin_order],
253+
apodizations=apodizations[:, pin_order],
254+
transducer=arr,
255+
pulse=pulse,
256+
voltage=voltage,
257+
sequence=sequence
258+
)
259+
profile_index = 1
260+
profile_increment = True
261+
trigger_mode = "continuous"
262+
263+
interface.set_solution(
264+
solution=solution,
265+
profile_index=profile_index,
266+
profile_increment=profile_increment,
267+
trigger_mode=trigger_mode
268+
)
269+
270+
logger.info("Press enter to START burn-in test with the following parameters: ")
271+
logger.info(f" Voltage: {voltage}V")
272+
logger.info(f" Pulse Duration: {duration_msec}ms")
273+
logger.info(f" Duration: {total_test_time//60} minutes")
274+
logger.info(f" Times per day: {times_per_day}")
275+
logger.info(f" Total Run Time: {run_time//(one_day_in_seconds)} day{'s' if run_time//(one_day_in_seconds) > 1 else ''}.")
276+
input()
277+
278+
# ------------------- Start Test -------------------
279+
temp_thread = threading.Thread(target=monitor_temperature)
280+
281+
# if test duration is 10 mins, and need to run 8 times in a 24 hour period, so start every 170 mins
282+
# initiating test 1 of 8
283+
# waiting 170 mins until next test
284+
285+
time_between_tests = ((one_day_in_seconds - (total_test_time * times_per_day))/(60*times_per_day))
286+
logger.info(f"Time between tests: {time_between_tests} minutes")
287+
288+
for i in range(run_time//one_day_in_seconds):
289+
for j in range(times_per_day):
290+
logger.info(f"Initiating test {j+1} of {times_per_day} for day {i+1}.")
291+
292+
logger.info("Starting Trigger...")
293+
if interface.start_sonication():
294+
logger.info("Trigger Running... (Press enter to STOP early)")
295+
def input_wrapper():
296+
if os.name == 'nt':
297+
while not shutdown_event.is_set():
298+
if msvcrt.kbhit():
299+
char = msvcrt.getch()
300+
if char == b'\r':
301+
shutdown_event.set()
302+
break
303+
time.sleep(0.1)
304+
else:
305+
while not shutdown_event.is_set():
306+
ready, _, _ = select.select([], [], [], 0.1)
307+
if ready:
308+
sys.stdin.readline()
309+
shutdown_event.set()
310+
break
311+
user_input = threading.Thread(target=input_wrapper)
312+
313+
temp_thread.start()
314+
user_input.start()
315+
while (temp_thread.is_alive() and user_input.is_alive()):
316+
time.sleep(0.1)
317+
shutdown_event.set()
318+
stop_logging = True
319+
320+
temp_thread.join()
321+
user_input.join()
322+
if interface.stop_sonication():
323+
logger.info("Trigger stopped successfully.")
324+
else:
325+
logger.error("Failed to stop trigger.")
326+
else:
327+
logger.error("Failed to start trigger.")
328+
329+
logger.info(f"Burn-in test {j+1} of {times_per_day} for day {i+1} complete. Shutting down devices if needed.")
330+
if not use_external_power_supply:
331+
interface.hvcontroller.turn_hv_off()
332+
interface.hvcontroller.turn_12v_off()
333+
334+
logger.info(f"Waiting {int(time_between_tests)} minutes until next test.")

0 commit comments

Comments
 (0)