Skip to content

Commit 92272ed

Browse files
committed
add pre-flight board check, use for loop, surface specific failure messages to UI
1 parent 85082b3 commit 92272ed

2 files changed

Lines changed: 39 additions & 24 deletions

File tree

Software/web-server/strobe_calibration_manager.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class StrobeCalibrationManager:
5050
# Safe fallback DAC value if calibration fails
5151
SAFE_DAC_VALUE = 0x96
5252

53+
# If ADC CH0 reads above this with strobe off, something is wrong (blown MOSFET, shorted gate driver)
54+
PREFLIGHT_CURRENT_THRESHOLD = 6
55+
5356
# LDO voltage bounds
5457
LDO_MIN_V = 4.5
5558
LDO_MAX_V = 11.0
@@ -205,67 +208,69 @@ def _calibrate(self, target_current: float):
205208
Returns:
206209
(success, final_dac, led_current)
207210
"""
211+
# Pre-flight: check for current with strobe off — indicates blown MOSFET or gate driver
212+
idle_adc = self._read_adc(self.ADC_CH0_CMD)
213+
if idle_adc > self.PREFLIGHT_CURRENT_THRESHOLD:
214+
self.status["message"] = f"Current detected with strobe off (ADC CH0={idle_adc}). Likely blown MOSFET or gate driver — check V3 Connector Board."
215+
return False, -1, -1
216+
208217
# Phase 1: find safe starting DAC
209218
dac_start, ldo = self._find_dac_start()
210219

211220
if dac_start < 0:
212-
logger.debug(f"DAC 0 already below LDO minimum ({ldo:.2f}V)")
221+
self.status["message"] = f"DAC value of 0 is below minimum LDO voltage ({self.LDO_MIN_V:.2f}V): {ldo:.2f}V. This indicates a problem with the controller board."
213222
return False, -1, -1
214223

215224
logger.debug(f"Calibrating: target={target_current}A, dac_start={dac_start:#04x}")
216225

217226
# Phase 2: sweep from dac_start downward, looking for target crossing
218-
current_dac = dac_start
219227
final_dac = self.DAC_MIN
220-
total_steps = dac_start - self.DAC_MIN + 1
228+
crossed = False
221229

222-
while current_dac >= self.DAC_MIN:
230+
for dac in range(dac_start, self.DAC_MIN - 1, -1):
223231
if self._cancel_requested:
224232
logger.info("Calibration cancelled by user")
225233
return False, -1, -1
226234

227-
self._set_dac(current_dac)
235+
self._set_dac(dac)
228236
time.sleep(0.1)
229237

230-
# Update progress for UI polling
231-
steps_done = dac_start - current_dac
238+
steps_done = dac_start - dac
239+
total_steps = dac_start - self.DAC_MIN + 1
232240
if total_steps > 0:
233241
self.status["progress"] = int(20 + (steps_done / total_steps) * 60)
234242

235243
ldo = self.get_ldo_voltage()
236244

237245
if ldo < self.LDO_MIN_V:
238-
logger.debug(f"LDO {ldo:.2f}V below min at DAC={current_dac:#04x}, skipping")
239-
final_dac = current_dac
240-
current_dac -= 1
246+
logger.debug(f"LDO {ldo:.2f}V below min at DAC={dac:#04x}, skipping")
247+
final_dac = dac
241248
continue
242249

243250
if ldo > self.LDO_MAX_V:
244-
logger.debug(f"LDO {ldo:.2f}V above max — something is wrong")
251+
self.status["message"] = f"LDO voltage ({ldo:.2f}V) above maximum ({self.LDO_MAX_V}V). Stopping calibration, as something is wrong."
245252
return False, -1, -1
246253

247254
led_current = self.get_led_current()
248-
logger.debug(f"DAC={current_dac:#04x}, current={led_current:.2f}A")
255+
logger.debug(f"DAC={dac:#04x}, current={led_current:.2f}A")
249256

250-
# Hard safety cap (bug fix over original script)
251257
if led_current > self.HARD_CAP_CURRENT:
252-
logger.error(f"LED current {led_current:.2f}A exceeds hard cap {self.HARD_CAP_CURRENT}A")
258+
self.status["message"] = f"LED current ({led_current:.2f}A) exceeds hard cap ({self.HARD_CAP_CURRENT}A). This strongly indicates the LED is shorted."
253259
return False, -1, -1
254260

255261
if led_current > target_current:
256-
logger.debug(f"Crossed target at DAC={current_dac:#04x} ({led_current:.2f}A)")
257-
final_dac = current_dac + 1
262+
logger.debug(f"Crossed target at DAC={dac:#04x} ({led_current:.2f}A)")
263+
final_dac = dac + 1
264+
crossed = True
258265
break
259266

260-
final_dac = current_dac
261-
current_dac -= 1
267+
final_dac = dac
262268

263-
# Edge cases — sweep ran off either end
264-
if current_dac <= self.DAC_MIN:
265-
logger.debug(f"Reached DAC_MIN without crossing target")
269+
if not crossed:
270+
self.status["message"] = f"Reached MIN_DAC without reaching target ({target_current}A). This generally indicates a problem."
266271
return False, -1, -1
267-
if current_dac >= self.DAC_MAX:
268-
logger.debug(f"DAC_MAX still above target — hardware problem")
272+
if final_dac >= self.DAC_MAX:
273+
self.status["message"] = "MAX_DAC resulted in current above target. This generally indicates a problem."
269274
return False, -1, -1
270275

271276
# Phase 3: average readings at the final setting to refine
@@ -370,8 +375,9 @@ def _run_calibration_sync(self, target: float) -> Dict[str, Any]:
370375
return self.status
371376
else:
372377
self._set_dac(self.SAFE_DAC_VALUE)
378+
reason = self.status.get("message", "Calibration failed")
373379
self.status = {"state": "failed", "progress": 0,
374-
"message": "Calibration failed — DAC set to safe fallback"}
380+
"message": f"{reason} DAC set to safe fallback."}
375381
return self.status
376382

377383
except Exception as e:

Software/web-server/tests/test_strobe_calibration_manager.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ def _make_mgr_with_hw():
295295
mgr = StrobeCalibrationManager(Mock())
296296
mgr._spi_dac = MagicMock()
297297
mgr._spi_adc = MagicMock()
298+
mgr._spi_adc.xfer2.return_value = [0x00, 0x00, 0x00]
298299
mgr._diag_pin = MagicMock()
299300
return mgr
300301

@@ -354,6 +355,14 @@ def test_sets_dac_at_each_step(self, _sleep):
354355
class TestCalibrate:
355356
"""_calibrate runs all 3 phases: find_start, main sweep, averaging"""
356357

358+
@patch("strobe_calibration_manager.time.sleep")
359+
def test_preflight_fails_when_current_detected_with_strobe_off(self, _sleep):
360+
mgr = _make_mgr_with_hw()
361+
mgr._spi_adc.xfer2.return_value = [0x00, 0x00, 0x10] # ADC=16, above threshold of 6
362+
363+
success, dac, current = mgr._calibrate(10.0)
364+
assert success is False
365+
357366
@patch("strobe_calibration_manager.time.sleep")
358367
def test_succeeds_with_realistic_data(self, _sleep):
359368
mgr = _make_mgr_with_hw()

0 commit comments

Comments
 (0)