""" timelapse_sweep.py — 3× CW/CCW sweep with burst-pause thermal management. Half-step mode: 14400 steps/rev, 0.025°/step Thermal strategy: step 5° quickly, release coils, cooldown, repeat. Timing per revolution: 72 bursts × (200 steps × 5ms + 3s cooldown) = ~5 min/rev 3 cycles × 2 passes × 5 min = ~30 min total Run: python3 timelapse_sweep.py Stop: Ctrl-C (releases coils cleanly) """ import serial, time, sys PORT = '/dev/ttyACM0' BAUD = 115200 STEPS_PER_REV = 14400 # half-step: double full-step count CYCLES = 3 # CW+CCW pairs BURST_DEG = 5.0 # degrees per burst BURST_STEPS = round(BURST_DEG * STEPS_PER_REV / 360) # 200 BURST_DELAY = 5 # ms/step during burst (fast, minimise energised time) COOLDOWN_SEC = 3.0 # seconds with coils released between bursts BATCH = 20 # steps per serial command within a burst BURSTS_PER_REV = STEPS_PER_REV // BURST_STEPS # 72 BURST_DURATION = BURST_STEPS * BURST_DELAY / 1000.0 # ~1 s PASS_DURATION = BURSTS_PER_REV * (BURST_DURATION + COOLDOWN_SEC) # ~288 s TOTAL_PASSES = CYCLES * 2 ETA_MIN = TOTAL_PASSES * PASS_DURATION / 60.0 def connect(): s = serial.Serial(PORT, BAUD, timeout=2) s.setDTR(False); s.setRTS(False) time.sleep(1) s.reset_input_buffer() return s def send_step(s, n, delay_ms): s.write(f'STEP {n} {delay_ms}\n'.encode()) deadline = time.time() + abs(n) * delay_ms / 1000.0 + 5 while time.time() < deadline: line = s.readline().decode(errors='replace').strip() if '[STEP]' in line: return line return None def read_enc(s): s.write(b'ENC\n') deadline = time.time() + 3 while time.time() < deadline: line = s.readline().decode(errors='replace').strip() if '[ENC]' in line: counts = int(line.split('counts=')[1].split()[0]) deg = float(line.split('deg=')[1]) return counts, deg return None, None def release(s): s.write(b'RELEASE\n') time.sleep(0.1) def fmt(sec): m, s = divmod(int(sec), 60) h, m = divmod(m, 60) return f'{h:02d}:{m:02d}:{s:02d}' def main(): print('=' * 58) print(' TIMELAPSE SWEEP — HALF-STEP + BURST-PAUSE') print('=' * 58) print(f' Steps/rev : {STEPS_PER_REV} (half-step)') print(f' Burst size : {BURST_DEG}° ({BURST_STEPS} steps @ {BURST_DELAY}ms each)') print(f' Cooldown : {COOLDOWN_SEC}s coils released between bursts') print(f' Bursts/rev : {BURSTS_PER_REV}') print(f' Time/pass : ~{PASS_DURATION/60:.1f} min') print(f' Cycles : {CYCLES}× CW+CCW') print(f' ETA total : ~{ETA_MIN:.0f} min') print('=' * 58) input(' Press Enter when plugged in and ready...') s = connect() s.write(b'ZERO\n') time.sleep(0.3) print(' [ZERO] encoder zeroed — starting\n') wall_start = time.time() pass_log = [] try: for cycle in range(1, CYCLES + 1): for leg_idx, (leg, direction) in enumerate([('CW', 1), ('CCW', -1)]): pass_num = (cycle - 1) * 2 + leg_idx + 1 pass_start = time.time() print(f' Pass {pass_num}/{TOTAL_PASSES} — Cycle {cycle} {leg}') total_sent = 0 for burst_num in range(1, BURSTS_PER_REV + 1): # --- burst --- b_sent = 0 while b_sent < BURST_STEPS: this = min(BATCH, BURST_STEPS - b_sent) send_step(s, this * direction, BURST_DELAY) b_sent += this total_sent += this # --- release and cool --- release(s) pct = total_sent / STEPS_PER_REV * 100 elapsed = time.time() - pass_start remain = (BURSTS_PER_REV - burst_num) * (BURST_DURATION + COOLDOWN_SEC) sys.stdout.write( f'\r burst {burst_num:3d}/{BURSTS_PER_REV}' f' {pct:5.1f}%' f' pass={fmt(elapsed)}' f' remain~{fmt(remain)}' f' [cooling {COOLDOWN_SEC:.0f}s] ' ) sys.stdout.flush() time.sleep(COOLDOWN_SEC) counts, deg = read_enc(s) pass_time = time.time() - pass_start pass_log.append({ 'pass': pass_num, 'cycle': cycle, 'dir': direction, 'enc_counts': counts, 'enc_deg': deg, 'time_s': pass_time, }) print(f'\n Pass {pass_num} done — ' f'enc={counts} ({deg}°) time={fmt(pass_time)}\n') except KeyboardInterrupt: print('\n\n [INTERRUPTED — releasing coils]') release(s) s.close() total_elapsed = time.time() - wall_start print('=' * 58) print(' RUN SUMMARY') print('=' * 58) for p in pass_log: print(f" Pass {p['pass']:2d} Cycle {p['cycle']} {p['dir']!s:3} " f"enc={str(p['enc_counts']):>7} ({str(p['enc_deg']):>8}°) " f"t={fmt(p['time_s'])}") print('=' * 58) print(f' Total time : {fmt(total_elapsed)}') print(' Coils released. Done.') if __name__ == '__main__': main()