import time from collections import deque import Adafruit_ADS1x15 # ---- Config ---- CHANNEL = 0 # A0 GAIN = 1 # +/-4.096V SAMPLE_HZ = 50 # 50 samples/sec is enough for breathing WINDOW_SEC = 6 # normalise over last N seconds REFRACTORY_SEC = 1.0 # minimum time between detected breaths adc = Adafruit_ADS1x15.ADS1115() LSB = 4.096 / 32768.0 N = int(SAMPLE_HZ * WINDOW_SEC) buf = deque(maxlen=N) baseline = 0.0 alpha = 0.02 # baseline tracking speed (lower = slower drift removal) last_cross = 0.0 breath_times = deque(maxlen=8) armed = True def read_v(): raw = adc.read_adc(CHANNEL, gain=GAIN) return raw * LSB def clamp(x, lo, hi): return lo if x < lo else hi if x > hi else x print("Breath scope: Ctrl+C to stop") print("Tip: put belt on, breathe slowly for 10s to let it settle.\n") while True: v = read_v() # Remove slow drift (piezo/belt baseline wander) baseline = (1 - alpha) * baseline + alpha * v x = v - baseline # "AC" part (what we care about) buf.append(x) # Normalise based on recent window if len(buf) > 20: mn = min(buf) mx = max(buf) span = (mx - mn) if (mx - mn) > 1e-6 else 1e-6 norm = (x - mn) / span # 0..1 else: norm = 0.5 # Draw a bar (0..50 chars) width = 50 pos = int(clamp(norm, 0.0, 1.0) * width) bar = (" " * pos) + "|" + (" " * (width - pos)) # Simple breath detection: threshold crossing on normalised signal now = time.time() thr = 0.75 # adjust if needed if len(buf) > 20: if armed and norm > thr and (now - last_cross) > REFRACTORY_SEC: breath_times.append(now) last_cross = now armed = False if norm < 0.55: armed = True bpm = None if len(breath_times) >= 3: periods = [breath_times[i] - breath_times[i-1] for i in range(1, len(breath_times))] avg_p = sum(periods) / len(periods) if avg_p > 0: bpm = 60.0 / avg_p # Print one line, overwrite in place if bpm is None: line = "V={:0.3f} AC={:+0.3f} [{}]".format(v, x, bar) else: line = "V={:0.3f} AC={:+0.3f} BPM={:0.1f} [{}]".format(v, x, bpm, bar) print("\r" + line + " ", end="") time.sleep(1.0 / SAMPLE_HZ)