Logo
  • start
LinkedIn

American Dream Code

1st step - remove all the shit.

2nd step -

low will have a minus

high will have a plus

  • That sensor has a 1 to 25 PSI but

Dec 13, Cody

To be more specific, it needs to detect when the average value of the loop has a variation of under 1 and update a new “average pressure” variable. Then the other variables need to be changed to avgpressure + x for activation and avgpressure - z for deactivation

Prev Cody

Super messy and miss labeled, sorry about that. I think going through commenting each line, then assigning the important variables to the top is a good first step

Cody said we’re sending power in through the USB of the Arduino which you’re not meant to do bc it becomes deregulated.

image
image
image

Log of atmospheric pressure changes across time

Dec 13 - 4:30pm: 1011 and 1012

image

if Raw hPa jumps more than 5 values suddenly - then we’re pumping.

But can’t activate the pump immediately is pressure jumps one time - it needs to be a number of times.

Actually what needs to happen is from the moment there’s a spike, then either start the timer or start adding to a counter.

How many pumps does it take for the balloon to be full? vs how many do we see in the code.

Number of pumps, lightly done for it to be full + what values do we see.

Variables that now have changed between long and short

sampleInterval = 100;

windowSizeShort (10secs) vs windowSizeLong (60seconds);

float pressureBufferShort[windowSizeShort]; int bufferIndexShort = 0; float runningSumShort = 0;

float pressureBufferLong[windowSizeLong]; int bufferIndexLong = 0; float runningSumLong = 0;

American dream in china, russia, britain,

On point 4 - "If someone pumps for a long time, your “ambient” will drift toward the pumped pressure because avgLong includes those samples. That might be fine; just know that your thresholds are tied to it." How can I then keep this from happening? 5) Two “turn pump on” paths can fight each other (logic overlap) - this is the goal, as a fail safe.

To stop Ambient (= avgLong) from “getting dragged” by sustained pumping, you need one idea:

Don’t feed “pumping samples” into the long buffer (or feed them with much lower weight).

You can do that with very small changes — no new state machines, no fancy failsafes. Here are three clean options, from simplest to most robust.

Option A (simplest): Freeze the LONG buffer while pumping

If pumpingNow is true, you still update the short buffer (so detection remains responsive), but you skip updating the long buffer. That keeps ambient stable.

Key detail: pumpingNow currently depends on avgLong, so compute pumpingNow using the previous avgLong, then decide whether to update the long buffer this cycle.

What it looks like in your loop

  1. Update short buffer → compute avgShort
  2. Compute avgLongPrev = runningSumLong / windowSizeLong
  3. Compute pumpingNow = (avgShort - avgLongPrev) >= pumpStartDelta
  4. If NOT pumping, update long buffer and recompute avgLong; else keep it unchanged
  5. Set ambientPressure = avgLong

Minimal code pattern:

Pros: dead simple, prevents drift completely.

Cons: if someone pumps for 10 minutes, ambient is “frozen” for 10 minutes (atmospheric pressure won’t change meaningfully in 10 minutes, so usually fine).

Option B (still simple): Clamp long updates to “near ambient”

Instead of freezing, you allow long buffer updates only when the raw reading is close-ish to the existing ambient (or avgLongPrev). That lets atmospheric drift through, but rejects pumping spikes.

Example gate:

float gateBand = 3.0; // hPa
bool allowLongUpdate = fabs(pressure_hPa - avgLongPrev) <= gateBand;

if (allowLongUpdate) { update long buffer }

Pros: ambient can keep slowly drifting even during mild activity.

Cons: if pumping shifts pressure by only a few hPa, you might still contaminate; if the gate is too tight you’ll effectively freeze anyway.

Option C (most robust, still not too complex): Keep long buffer updating, but “winsorize” input

This means: when pressure is wild, you cap what you feed the long buffer:

float capped = pressure_hPa;
float maxStep = 2.0; // hPa
if (capped > avgLongPrev + maxStep) capped = avgLongPrev + maxStep;
if (capped < avgLongPrev - maxStep) capped = avgLongPrev - maxStep;

// feed capped into long buffer

Pros: ambient never freezes; pumping can’t drag it fast.

Cons: slightly more math; you must pick maxStep.

What I’d pick for your goals

Given your stated preference (remove complication) + running for hours:

  • Start with Option A (freeze long buffer while pumping).
  • If you later see “ambient feels stale during long pumping sessions,” switch to Option C.

Atmospheric pressure changes over hours, not seconds; freezing for the duration of “active pumping” is usually perceptually invisible but functionally stabilizing.

About point 5 (“two turn-on paths as fail safe”)

That’s fine. If you want both:

  • Keep hysteresis as the “physics-based control”
  • Keep time-based as the “guarantee after sustained pumping”
  • Just be aware that when you freeze long/ambient during pumping, your thresholds won’t slide under you — which actually makes the two-path safety strategy more predictable.

Three prompts to continue

  1. “Patch my exact code to implement Option A (freeze long buffer while pumping) with the smallest possible diff.”
  2. “Show me the exact diff-style changes for Option B (gate long updates by a band around avgLongPrev).”
  3. “Which values should I use for gateBand or maxStep given my sensor noise, and what should I log to tune it in 2 minutes?”

Previous pumpingNow

// test 2 pumps in a 35 sec window doesnt kick in

Key changes:

  • Time based logic is now if someone hits the pump for X amount of times
    • Which you can change to be 1
  • 30 seconds is way too little, where it’s almost impossible for anyone to activate the pump the other way.
  • 35 seconds
  • It can detect really well if someone is pumping or not, even just after pump has kicked in. Code currently doesn’t register that someone is pumping while it’s suction pump is on. increasing the low threshold can have the same effect.

AmbientPressure is the result of a circular buffer running for 3 minutes that already excludes any hPa samples from when the pump is running.

For the timed logic, I made it a little more robust to avoid someone just pumping once and walking away and the suction still kicking in. If in a space of windowDurationMs (aka 35 seconds), if the pump has been hit 4x counting from the first one, then suction pump will be turned on. Both the 35 secs and the 4x can be changed - to 1x for example.

about code

  • We’re not updating atmospheric pressure when pump is on.

Do we agree on this - suction can only be ON if measured pressure is above ambient pressure?

image

I want “ambient that drifts slowly over ~30 minutes” with minimal code and memory: Option B (EMA).

Take 32 readings;

take the 8th smallest moment

Take away the bottom 25% hypothetically &

don’t update it when the pump is on.

Taking the one reading above

A median of the 25%.

he’s saying that I take a lower value from every minute and then add it to a larger buffer that counts towards the hour.

Connecting Arduino

Power from brown cable was on!

Post 4am Michael call

6) Your pumping detector can generate fake “hits” because it’s tied to pump state

You define:

pumpingNow = (!suctionOn) && ((pressure_hPa - ambientPressure) >= pumpStartDelta);

So whenever suction turns ON, pumpingNow is forced false. When suction turns OFF, it can become true again immediately, creating rising edges that are caused by your pump toggling, not by a person pumping.

That can inflate pumpHits and cause self-triggering.

If you want “user pumping” events, base it on input signal behavior, not on the actuator state, or add a guard like:

  • Only count rising edges if pump has been OFF for at least N seconds
  • Or count events from avgShort - ambient rather than raw pressure_hPa - ambient
  • Or freeze the hit window entirely while suction is ON

Pump kicked in after

Raw hPa: 989.65 | AvgShort(10s) hPa: 990.07 | AvgLong(60s) or ambientpressure: 1007.25 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 969.47 | AvgShort(10s) hPa: 989.89 | AvgLong(60s) or ambientpressure: 1007.22 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 985.70 | AvgShort(10s) hPa: 989.64 | AvgLong(60s) or ambientpressure: 1007.21 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 977.72 | AvgShort(10s) hPa: 989.57 | AvgLong(60s) or ambientpressure: 1007.19 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 975.05 | AvgShort(10s) hPa: 988.99 | AvgLong(60s) or ambientpressure: 1007.17 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 983.13 | AvgShort(10s) hPa: 988.81 | AvgLong(60s) or ambientpressure: 1007.16 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 970.67 | AvgShort(10s) hPa: 988.16 | AvgLong(60s) or ambientpressure: 1007.14 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 984.52 | AvgShort(10s) hPa: 987.92 | AvgLong(60s) or ambientpressure: 1007.12 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 965.15 | AvgShort(10s) hPa: 987.27 | AvgLong(60s) or ambientpressure: 1007.09 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 987.05 | AvgShort(10s) hPa: 987.02 | AvgLong(60s) or ambientpressure: 1007.06 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 959.52 | AvgShort(10s) hPa: 986.39 | AvgLong(60s) or ambientpressure: 1007.02 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 990.34 | AvgShort(10s) hPa: 986.17 | AvgLong(60s) or ambientpressure: 1007.01 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 948.64 | AvgShort(10s) hPa: 985.46 | AvgLong(60s) or ambientpressure: 1006.97 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 993.19 | AvgShort(10s) hPa: 985.28 | AvgLong(60s) or ambientpressure: 1006.96 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 946.89 | AvgShort(10s) hPa: 984.59 | AvgLong(60s) or ambientpressure: 1006.93 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 991.53 | AvgShort(10s) hPa: 984.40 | AvgLong(60s) or ambientpressure: 1006.92 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 967.91 | AvgShort(10s) hPa: 983.92 | AvgLong(60s) or ambientpressure: 1006.89 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 971.52 | AvgShort(10s) hPa: 983.52 | AvgLong(60s) or ambientpressure: 1006.87 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 985.70 | AvgShort(10s) hPa: 983.22 | AvgLong(60s) or ambientpressure: 1006.84 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 960.28 | AvgShort(10s) hPa: 982.71 | AvgLong(60s) or ambientpressure: 1006.80 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 988.80 | AvgShort(10s) hPa: 982.43 | AvgLong(60s) or ambientpressure: 1006.78 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 950.74 | AvgShort(10s) hPa: 981.82 | AvgLong(60s) or ambientpressure: 1006.75 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 993.99 | AvgShort(10s) hPa: 981.58 | AvgLong(60s) or ambientpressure: 1006.74 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 944.90 | AvgShort(10s) hPa: 980.91 | AvgLong(60s) or ambientpressure: 1006.70 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 992.26 | AvgShort(10s) hPa: 980.65 | AvgLong(60s) or ambientpressure: 1006.69 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 948.47 | AvgShort(10s) hPa: 980.03 | AvgLong(60s) or ambientpressure: 1006.66 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 991.79 | AvgShort(10s) hPa: 979.77 | AvgLong(60s) or ambientpressure: 1006.64 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH Raw hPa: 962.28 | AvgShort(10s) hPa: 979.29 | AvgLong(60s) or ambientpressure: 1006.60 | PumpingNow: N | windowActive: N | pumpHits: 0 | windowAgeMs: 0 | PumpPin: HIGH

5:30am sun

AllowSuction logic has to go.

image

Decent working code.

Even more decent and more working

#include <Wire.h> //RXTX
#include <math.h>
#include "Adafruit_MPRLS.h"

// DIF? Avg(60s) hPa: 1012.64 | Ambient hPa: 1012.64

// 🌸 2 modes of activating

// Otherwise, if we're also triggering every 30 seconds from when someone starts pumping, 
// that becomes an issue and assumes they're doing it continously?

// The key idea: don’t update ambient while people are pumping.

// Could add a user + threshold that is the only thing we add - that is otherwise 0?

// --------------------------

// You dont *need* a reset and EOC pin for most uses, so we set to -1 and don't connect
#define RESET_PIN  -1  // set to any GPIO pin # to hard-reset on begin()
#define EOC_PIN    -1  // set to any GPIO pin to read end-of-conversion by pin
Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);
const int pumpPin = 3;

// ---- PRESSURE ABOVE ------
bool pressureAbove = false; // "someone is pumping" state
unsigned long pressureStartTime = 0; // when pumping began (for timing logic)

// ---- TIME VARIABLES -------
unsigned long currentMillis;
unsigned long lastSampleTime = 0;

// ---- Sampling INTERVAL ----
const unsigned long sampleInterval = 100; // 100ms => 10Hz sampling for both Ambient and AvgPressure.

// -------- SHORT MOVING AVERAGE = for pumping detection + pump decisions (responsive)
const int windowSizeShort = 100; // 10 seconds
float pressureBufferShort[windowSizeShort];  // circular buffer of recent pressures
int bufferIndexShort = 0;   // next write position
float runningSumShort = 0; // O(1) moving average 🌸 understand

// -------- LONG MOVING AVERAGE = for ambient/baseline logic (stable)
const int windowSizeLong = 600; // Windowx length (number of samples): 60,000ms / 100ms = 600 samples (1 minute)
float pressureBufferLong[windowSizeLong];
int bufferIndexLong = 0;
float runningSumLong = 0;

// --- ambient baseline tracking ---
float ambientPressure = 0.0;  // Starts at 0, updated in setup during first 10 seconds of readings.
const unsigned long ambientInitDurationMs = 10000; // How long to sample at boot to establish baseline
  // Has no effect on measurements during loop - 10 seconds
const float ambientAlpha = 0.02;             // EMA speed (2% per quiet update)
const float ambientHoldBand = 3.0;           // hPa band around ambient considered "quiet"

// --- 🌸 thresholds (relative to ambient; more robust than hardcoding absolute hPa) ---
const float pressureThresholdDeltaHigh = 20.0;   // hPa above ambient -> "high"
const float pressureThresholdDeltaLow  = -30.0;  // hPa below ambient -> "low"
const float pumpStartDelta             = 4.0;    // hPa above ambient -> consider "someone is pumping"

// --- pump logic timing ---
const unsigned long pumpHoldTimeMs = 30000;   // must stay above pumpStartDelta this long before turning on

void setup() {
  Serial.begin(9600);
  Serial.println("MPRLS + 1-minute Moving Average + Ambient Baseline");
  
  if (! mpr.begin()) {
    Serial.println("Failed to communicate with MPRLS sensor, check wiring?");
    while (1) delay(10);
  }
  Serial.println("Found MPRLS sensor");

  pinMode(pumpPin, OUTPUT);
  digitalWrite(pumpPin, LOW);

  // ------------------------------------------------------------
  // 1) Initialize ambientPressure from the FIRST 10 SECONDS (setup only)
  // ------------------------------------------------------------
  Serial.println("Bootstrapping ambientPressure from first 10 seconds...");
  
  float initSum = 0.0;
  int initCount = 0;

  unsigned long startMs = millis();
  unsigned long lastInitSampleMs = 0;

  while (millis() - startMs < ambientInitDurationMs) {
    unsigned long now = millis();
    if (now - lastInitSampleMs >= sampleInterval) {
      lastInitSampleMs = now;
      float p = mpr.readPressure();
      initSum += p;
      initCount++;
    }
  }

  if (initCount > 0) {
    ambientPressure = initSum / initCount;
  } else {
    ambientPressure = 1012.0; // Fallback
  }

  Serial.print("Initial ambientPressure (avg first 10s) hPa: ");
  Serial.println(ambientPressure, 2);

  // ------------------------------------------------------------
  // 2.1) Prefill the 10-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeShort; i++) {
    pressureBufferShort[i] = ambientPressure;
  }
  runningSumShort = ambientPressure * windowSizeShort;
  bufferIndexShort = 0;

  // ------------------------------------------------------------
  // 2.2) Prefill the 60-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeLong; i++) {
    pressureBufferLong[i] = ambientPressure;
  }
  runningSumLong = ambientPressure * windowSizeLong;
  bufferIndexLong = 0;

  // Set sampling clock so loop starts cleanly
  lastSampleTime = millis();
}

// next up: What is timing dependent on? Got too complicated. 
// Why is Raw HpA not visible?

void loop() {
  // SERIAL COMMAND / MANUAL OVERRIDE TO SHUT DEMON PUMP OFF and ON
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();

    if (cmd.equalsIgnoreCase("pumpOn")) {
      digitalWrite(3, HIGH);
      Serial.println("Pin 3 / Demon Pump forced HIGH by serial command");
      delay(10000); // 10 seconds
    }

    if (cmd.equalsIgnoreCase("pumpOff")) {
      digitalWrite(3, LOW);
      Serial.println("Pin 3 / Demon Pump forced LOW by serial command");
      delay(10000); // 10 seconds
    }
  }

  // ----------------------------------------------
  currentMillis = millis();

  if (currentMillis - lastSampleTime >= sampleInterval) {
    lastSampleTime = currentMillis;

    // raw read
    float pressure_hPa = mpr.readPressure();

    // 🔥 pressureBufferLong changed. check
    // --- 1-minute moving average (SMA) using circular buffer + running sum ---

    // -----------------------------
    // Update 10s Average Pressure (10s)
    // -----------------------------
    float oldShort = pressureBufferShort[bufferIndexShort];          // overwritten value (oldest in ring)
    pressureBufferShort[bufferIndexShort] = pressure_hPa;
    runningSumShort += (pressure_hPa - oldShort);
    bufferIndexShort = (bufferIndexShort + 1) % windowSizeShort;
    
    float avgPressure = runningSumShort / windowSizeShort;

    // -----------------------------
    // Update LONG window (60s) => ONLY for ambient update
    // (no separate avgPressureLong variable exposed)
    // -----------------------------
    float oldLong = pressureBufferLong[bufferIndexLong];
    pressureBufferLong[bufferIndexLong] = pressure_hPa;
    runningSumLong += (pressure_hPa - oldLong); // buffer is filled from setup(), so this is safe: - check
    bufferIndexLong = (bufferIndexLong + 1) % windowSizeLong;

    // --- derive thresholds from ambient ---
    float pressureThreshold    = ambientPressure + pressureThresholdDeltaHigh;
    float pressureThresholdLow = ambientPressure + pressureThresholdDeltaLow;

    // ------------------------------------------------------------
    // --- "someone is pumping" detector (avg-based, 10s) ---
    // ------------------------------------------------------------
    if (avgPressure >= (ambientPressure + pumpStartDelta)) { // it being AvgPressure is what makes it work essentially. 
      if (!pressureAbove) {
        pressureAbove = true;
        pressureStartTime = currentMillis;
      }
    } else {
      pressureAbove = false;
    }
    
    // ------------------------------------------------------------
    // --- ambient update: ONLY when "quiet" (not pumping, and close to baseline) ---
    // ------------------------------------------------------------
    float avgForAmbient = runningSumLong / windowSizeLong; // 60s window
    bool quiet = (!pressureAbove) && (fabs(avgForAmbient - ambientPressure) <= ambientHoldBand);

    if (quiet) {
      ambientPressure = (1.0f - ambientAlpha) * ambientPressure + ambientAlpha * avgForAmbient;
    }

    // ------------------------------------------------------------
    // Pump control
    // ------------------------------------------------------------
    // 1) High/low hysteresis control (ambient-relative)
    if (avgPressure >= pressureThreshold) { //  pressureThreshold(ambientPressure + pressureThresholdDeltaHigh;)
      digitalWrite(pumpPin, HIGH);
    } else if (avgPressure <= pressureThresholdLow) {
      digitalWrite(pumpPin, LOW);
    }

    // check avgPressure >= pressureThreshold  && pressureAbove vs pressureAbove

    // 2) Time-based control: only turn on if above pumpStartDelta for > pumpHoldTimeMs
    if (pressureAbove) { // bool, true - determined by avgPressure >= (ambientPressure + pumpStartDelta)
      if (currentMillis - pressureStartTime > pumpHoldTimeMs) {
        digitalWrite(pumpPin, HIGH);
      }
    } else {
      if (avgPressure <= pressureThresholdLow) {
        digitalWrite(pumpPin, LOW);
      }
    }

    // --- debug prints ---
    Serial.print("Raw hPa: "); Serial.print(pressure_hPa, 2);
    Serial.print(" | Avg(10s) hPa: "); Serial.print(avgPressure, 2);
    Serial.print(" | Ambient hPa: "); Serial.print(ambientPressure, 2);
    Serial.print(" | Pumping: "); Serial.print(pressureAbove ? "Y" : "N");
    Serial.print(" | PumpPin: "); Serial.println(digitalRead(pumpPin) ? "HIGH" : "LOW");
  }
}
// after updating SHORT:
float avgShort = runningSumShort / windowSizeShort;

// compute long avg BEFORE updating long buffer
float avgLongPrev = runningSumLong / windowSizeLong;

// pumping detector uses prev long avg
pumpingNow = (avgShort - avgLongPrev) >= pumpStartDelta;

// ONLY update long buffer when not pumping
if (!pumpingNow) {
  float oldLong = pressureBufferLong[bufferIndexLong];
  pressureBufferLong[bufferIndexLong] = pressure_hPa;
  runningSumLong += (pressure_hPa - oldLong);
  bufferIndexLong = (bufferIndexLong + 1) % windowSizeLong;
}

// ambient is long avg (which did not move during pumping)
float avgLong = runningSumLong / windowSizeLong;
ambientPressure = avgLong;

    // 2) Time-based control: only turn on if above pumpStartDelta for > pumpHoldTimeMs
    // Count events (aka if pumpingNow is true more than 4 times) instead of just once setting it off. 
    if (pumpingNow) { // bool, true
      if (pumpStartTime != 0 && (currentMillis - pumpStartTime > pumpHoldTimeMs)) {
        digitalWrite(pumpPin, HIGH);
      }
    } else {
      // When not pumping, allow the low threshold to shut it down - probably need to tweak
      if (avgShort <= pressureThresholdLow) { 
        // Why not just doing !pumping now for more than 5 seconds? Wouldn't that give us something more accurate?
        digitalWrite(pumpPin, LOW);
      }
    }
 // SERIAL COMMAND / MANUAL OVERRIDE TO SHUT DEMON PUMP OFF and ON
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    cmd.trim();

    if (cmd.equalsIgnoreCase("pumpOn")) {
      digitalWrite(pumpPin, HIGH);
      Serial.println("Pin 3 / Demon Pump forced HIGH by serial command");
      delay(10000); // 10 seconds
    }

    if (cmd.equalsIgnoreCase("pumpOff")) {
      digitalWrite(pumpPin, LOW);
      Serial.println("Pin 3 / Demon Pump forced LOW by serial command");
      delay(10000); // 10 seconds
    }
  }
#include <Wire.h> //RXTX
#include <math.h>
#include "Adafruit_MPRLS.h"

// Otherwise, if we're also triggering every 30 seconds from when someone starts pumping, 
// that becomes an issue and assumes they're doing it continously?

// key idea: don’t update ambient while people are pumping.

// You dont *need* a reset and EOC pin for most uses, so we set to -1 and don't connect
#define RESET_PIN  -1  // set to any GPIO pin # to hard-reset on begin()
#define EOC_PIN    -1  // set to any GPIO pin to read end-of-conversion by pin
Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);
const int pumpPin = 3;

// ---- TIME -------
unsigned long currentMillis;
unsigned long lastSampleTime = 0;
const unsigned long sampleInterval = 100; // 100ms => 10Hz sampling for AvgPressure.
// const unsigned long sampleIntervalAmbient = 60UL * 1000UL;  // 60,000 ms = 1 min // 🌸

// -------- SHORT MOVING AVERAGE = for pumping detection + pump decisions (responsive)
const int windowSizeShort = 100; // 10 seconds
float pressureBufferShort[windowSizeShort];  // circular buffer of recent pressures
int bufferIndexShort = 0;   // next write position
float runningSumShort = 0; // O(1) moving average 🌸 understand

// -------- LONG MOVING AVERAGE = for ambient/baseline logic (stable)
const int windowSizeLong = 1800; // Window length (number of samples): 180,000ms / 100ms = 1800 samples (3 minutes)
float pressureBufferLong[windowSizeLong];
int bufferIndexLong = 0;
float runningSumLong = 0;

// --- ambient baseline tracking ---
float ambientPressure = 0.0;  // Starts at 0, updated in setup during first 10 seconds of readings.
const unsigned long ambientInitDurationMs = 10000; // How long to sample at boot to establish baseline

// --- 🌸 thresholds (relative to ambient; more robust than hardcoding absolute hPa) ---
const float pressureThresholdDeltaHigh = 20.0;   // hPa above ambient -> "high"
const float pressureThresholdDeltaLow  = -30.0;  // hPa below ambient -> "low"
const float pumpStartDelta             = 6.0;    // hPa above ambient -> consider "someone is pumping"

// --- pump logic timing --- // DELETE IF NOT BEING USED
const unsigned long pumpHoldTimeMs = 30000;   // must stay above pumpStartDelta this long before turning on

// ------ for time-based pump activation based on # of times that pumpingNow is true
const unsigned long windowDurationMs = 35000; // 35000 = 35s window
const int hitsNeeded = 4;

bool windowActive = false;
unsigned long windowStartMs = 0;
int pumpHits = 0;

bool lastPumpingNow = false; // for rising-edge detection 🌸🌸🌸🌸🌸🌸🌸🌸🌸
// ---- main pumping variable (previous pressure above) ------
bool pumpingNow = false; // "someone is pumping" state
  // 🌸 probably an ISSUE!!! BC WE WERE SEEING IF PRESSURE WAS GOING DOWN OR UP, 
  // AND NOW WE'RE JUST SEEING ON OR OFF

void setup() {
  Serial.begin(115200);
  Serial.println("MPRLS + 1-minute Moving Average + Ambient Baseline");
  if (! mpr.begin()) {
    Serial.println("Failed to communicate with MPRLS sensor, check wiring?");
    while (1) delay(10);
  }
  Serial.println("Found MPRLS sensor");

  pinMode(pumpPin, OUTPUT);
  digitalWrite(pumpPin, LOW);

  // ------------------------------------------------------------
  // 1) Initialize ambientPressure from the FIRST 10 SECONDS (setup only)
  // ------------------------------------------------------------
  Serial.println("Bootstrapping ambientPressure from first 10 seconds...");
  
  float initSum = 0.0f;
  int initCount = 0;

  unsigned long startMs = millis();
  unsigned long lastInitSampleMs = 0;

  while (millis() - startMs < ambientInitDurationMs) {
    unsigned long now = millis();
    if (now - lastInitSampleMs >= sampleInterval) {
      lastInitSampleMs = now;
      float p = mpr.readPressure();
      initSum += p;
      initCount++;
    }
  }

  if (initCount > 0) {
    ambientPressure = initSum / initCount;
  } else {
    ambientPressure = 1012.0; // Fallback
  }

  Serial.print("Initial ambientPressure (avg first 10s) hPa: ");
  Serial.println(ambientPressure, 2);

  // ------------------------------------------------------------
  // 2.1) Prefill the 10-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeShort; i++) {
    pressureBufferShort[i] = ambientPressure;
  }
  runningSumShort = ambientPressure * windowSizeShort;
  bufferIndexShort = 0;

  // ------------------------------------------------------------
  // COULD PROBABLY BE DELETED
  // 2.2) Prefill the 60-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeLong; i++) {
    pressureBufferLong[i] = ambientPressure;
  }
  runningSumLong = ambientPressure * windowSizeLong;
  bufferIndexLong = 0;

  // Set sampling clock so loop starts cleanly
  lastSampleTime = millis();
}

void loop() {
  currentMillis = millis();

  if (currentMillis - lastSampleTime >= sampleInterval) {
    lastSampleTime = currentMillis;

    // raw read
    float pressure_hPa = mpr.readPressure();
    bool suctionOn = (digitalRead(pumpPin) == HIGH);
    
    // Suction can only be ON if measured pressure is above ambient pressure:
    float delta = pressure_hPa - ambientPressure;
    // const float deltaOnMin = 1.0f; // hPa margin (tune) // CHECK IF NEEDED or 
    bool allowSuction = (delta > 1.0f); // only allow suction if the difference is >1

    // 🔥 pressureBufferLong changed. check
    // --- 1-minute moving average (SMA) using circular buffer + running sum ---

    // -----------------------------
    // Update 10s Average Pressure (10s)
    // -----------------------------
    float oldShort = pressureBufferShort[bufferIndexShort];          // overwritten value (oldest in ring)
    pressureBufferShort[bufferIndexShort] = pressure_hPa;
    runningSumShort += (pressure_hPa - oldShort);
    bufferIndexShort = (bufferIndexShort + 1) % windowSizeShort;
    
    float avgShort = runningSumShort / windowSizeShort;

    // -----------------------------
    // Update LONG window (60s)
    // -----------------------------
    float oldLong = pressureBufferLong[bufferIndexLong];
    pressureBufferLong[bufferIndexLong] = pressure_hPa;
    runningSumLong += (pressure_hPa - oldLong); // buffer is filled from setup(), so this is safe: - check
    bufferIndexLong = (bufferIndexLong + 1) % windowSizeLong;

    float avgLong = runningSumLong / windowSizeLong;

    // ------------------------------------------------------------
    // 🚨 Ambient is ALWAYS the 60s moving average
    // ------------------------------------------------------------
    if (!suctionOn && !pumpingNow) {
      ambientPressure = avgLong;
    };

    // --- derive thresholds from ambient ---
    float pressureThresholdHigh    = ambientPressure + pressureThresholdDeltaHigh;
    float pressureThresholdLow = ambientPressure + pressureThresholdDeltaLow;

    // ------------------------------------------------------------
    // --- "someone is pumping" detector (avg-based, 10s) ---
    // ------------------------------------------------------------
    pumpingNow = (!suctionOn) && ((pressure_hPa - ambientPressure) >= pumpStartDelta); // Global variable with ambientPressure = avgLong
      // if suction is On, then pumping can't be done. 

    // ------------------------------------------------------------
    // Pump control
    // ------------------------------------------------------------
    // 1) High/low hysteresis control (using 10s Short Avg)
    if (allowSuction && avgShort >= pressureThresholdHigh) {
      digitalWrite(pumpPin, HIGH);
    } else if (avgShort <= pressureThresholdLow) {
      digitalWrite(pumpPin, LOW);
    }

    // 2) Time-based control
    // --- Count pumping events in a 30s window that starts on the first pumpingNow ---
    bool risingEdge = (pumpingNow && !lastPumpingNow);
    lastPumpingNow = pumpingNow;

    if (!windowActive) {
      if (risingEdge) {
        windowActive = true;
        windowStartMs = currentMillis;
        pumpHits = 1; // first hit starts the window
      }
    } else {
      if (risingEdge) {
        pumpHits++; // Count how many pumps have been done. Beyond 4, the pump will always turn on.
      }

      // If windowDurationMs(30s) passed since first hit, decide once
      if (currentMillis - windowStartMs >= windowDurationMs) {
        if (pumpHits >= hitsNeeded && allowSuction) {
          digitalWrite(pumpPin, HIGH); // kick off suction pump at end of the window
        }
        // Reset for the next window (fresh start from next pumping event)
        windowActive = false;
        windowStartMs = 0;
        pumpHits = 0;
      }
    }

    // UNCOMMENT / Comment
    // Override to make sure that raw pressure below ambient pressure never triggers the pump.
    if (!allowSuction) {
      digitalWrite(pumpPin, LOW);
    }


    // --- debug prints ---
    Serial.print("Raw hPa: "); Serial.print(pressure_hPa, 2);
    Serial.print(" | AvgShort(10s) hPa: "); Serial.print(avgShort, 2);
    Serial.print(" | AvgLong(60s) or ambientpressure: "); Serial.print(avgLong, 2);
    Serial.print(" | PumpingNow: "); Serial.print(pumpingNow ? "Y" : "N");
    Serial.print(" | windowActive: "); Serial.print(windowActive ? "Y" : "N");
    Serial.print(" | pumpHits: "); Serial.print(pumpHits);
    Serial.print(" | windowAgeMs: "); Serial.print(windowActive ? (currentMillis - windowStartMs) : 0);
    Serial.print(" | PumpPin: "); Serial.println(digitalRead(pumpPin) ? "HIGH" : "LOW");
  }
}

// Ambient pressure should probably be a 3rd variable that includes like 5 minutes. 
#include <Wire.h> //RXTX
#include <math.h>
#include "Adafruit_MPRLS.h"

// Otherwise, if we're also triggering every 30 seconds from when someone starts pumping, 
// that becomes an issue and assumes they're doing it continously?

// key idea: don’t update ambient while people are pumping.

// You dont *need* a reset and EOC pin for most uses, so we set to -1 and don't connect
#define RESET_PIN  -1  // set to any GPIO pin # to hard-reset on begin()
#define EOC_PIN    -1  // set to any GPIO pin to read end-of-conversion by pin
Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);
const int pumpPin = 3;

// ---- TIME -------
unsigned long currentMillis;
unsigned long lastSampleTime = 0;
const unsigned long sampleInterval = 100; // 100ms => 10Hz sampling for AvgPressure.
// const unsigned long sampleIntervalAmbient = 60UL * 1000UL;  // 60,000 ms = 1 min // 🌸

// -------- SHORT MOVING AVERAGE = for pumping detection + pump decisions (responsive)
const int windowSizeShort = 100; // 10 seconds
float pressureBufferShort[windowSizeShort];  // circular buffer of recent pressures
int bufferIndexShort = 0;   // next write position
float runningSumShort = 0; // O(1) moving average 🌸 understand

// -------- LONG MOVING AVERAGE = for ambient/baseline logic (stable)
const int windowSizeLong = 1800; // Window length (number of samples): 180,000ms / 100ms = 1800 samples (3 minutes)
float pressureBufferLong[windowSizeLong];
int bufferIndexLong = 0;
float runningSumLong = 0;

// --- ambient baseline tracking ---
float ambientPressure = 0.0;  // Starts at 0, updated in setup during first 10 seconds of readings.
const unsigned long ambientInitDurationMs = 10000; // How long to sample at boot to establish baseline

// ---

// --- 🌸 thresholds (relative to ambient; more robust than hardcoding absolute hPa) ---
const float pressureThresholdDeltaHigh = 20.0;   // hPa above ambient -> "high"
const float pressureThresholdDeltaLow  = -15.0;  // hPa below ambient -> "low"
  // Cody, why -30? it quite never made it there.
const float pumpStartDelta             = 6.0;    // hPa above ambient -> consider "someone is pumping"

// --- pump logic timing --- // DELETE IF NOT BEING USED
const unsigned long pumpHoldTimeMs = 30000;   // must stay above pumpStartDelta this long before turning on

// ------ for time-based pump activation based on # of times that pumpingNow is true
const unsigned long windowDurationMs = 35000; // 35000 = 35s window
const int hitsNeeded = 4;

bool windowActive = false;
unsigned long windowStartMs = 0;
int pumpHits = 0;

bool lastPumpingNow = false; // for rising-edge detection 🌸🌸🌸🌸🌸🌸🌸🌸🌸
// ---- main pumping variable (previous pressure above) ------
bool pumpingNow = false; // "someone is pumping" state
  // 🌸 probably an ISSUE!!! BC WE WERE SEEING IF PRESSURE WAS GOING DOWN OR UP, 
  // AND NOW WE'RE JUST SEEING ON OR OFF


void setup() {
  Serial.begin(115200);
  Serial.println("MPRLS + 1-minute Moving Average + Ambient Baseline");
  if (! mpr.begin()) {
    Serial.println("Failed to communicate with MPRLS sensor, check wiring?");
    while (1) delay(10);
  }
  Serial.println("Found MPRLS sensor");

  pinMode(pumpPin, OUTPUT);
  digitalWrite(pumpPin, LOW);

  // ------------------------------------------------------------
  // 1) Initialize ambientPressure from the FIRST 10 SECONDS (setup only)
  // ------------------------------------------------------------
  Serial.println("Bootstrapping ambientPressure from first 10 seconds...");
  
  float initSum = 0.0f;
  int initCount = 0;

  unsigned long startMs = millis();
  unsigned long lastInitSampleMs = 0;

  while (millis() - startMs < ambientInitDurationMs) {
    unsigned long now = millis();
    if (now - lastInitSampleMs >= sampleInterval) {
      lastInitSampleMs = now;
      float p = mpr.readPressure();
      initSum += p;
      initCount++;
    }
  }

  if (initCount > 0) {
    ambientPressure = initSum / initCount;
  } else {
    ambientPressure = 1012.0; // Fallback
  }

  Serial.print("Initial ambientPressure (avg first 10s) hPa: ");
  Serial.println(ambientPressure, 2);

  // ------------------------------------------------------------
  // 2.1) Prefill the 10-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeShort; i++) {
    pressureBufferShort[i] = ambientPressure;
  }
  runningSumShort = ambientPressure * windowSizeShort;
  bufferIndexShort = 0;

  // ------------------------------------------------------------
  // COULD PROBABLY BE DELETED
  // 2.2) Prefill the 60-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeLong; i++) {
    pressureBufferLong[i] = ambientPressure;
  }
  runningSumLong = ambientPressure * windowSizeLong;
  bufferIndexLong = 0;

  // Set sampling clock so loop starts cleanly
  lastSampleTime = millis();
}

void loop() {
  currentMillis = millis();

  if (currentMillis - lastSampleTime >= sampleInterval) {
    lastSampleTime = currentMillis;

    // raw read
    float pressure_hPa = mpr.readPressure();
    bool suctionOn = (digitalRead(pumpPin) == HIGH);
    

    // 🔥 pressureBufferLong changed. check
    // --- 1-minute moving average (SMA) using circular buffer + running sum ---

    // -----------------------------
    // Update 10s Average Pressure (10s) 
            // Q: should this not include times when we're suctioning?
    // -----------------------------
    float oldShort = pressureBufferShort[bufferIndexShort];          // overwritten value (oldest in ring)
    pressureBufferShort[bufferIndexShort] = pressure_hPa;
    runningSumShort += (pressure_hPa - oldShort);
    bufferIndexShort = (bufferIndexShort + 1) % windowSizeShort;
    
    float avgShort = runningSumShort / windowSizeShort;

    // -----------------------------
    // Update LONG window (60s)
    // -----------------------------
    float avgLong = ambientPressure; // fallback if we skip update
        // Isn't this a condition that eats it self though? 🔥

    if (!suctionOn) { // Suction samples never enter the long buffer.
      float oldLong = pressureBufferLong[bufferIndexLong];
      pressureBufferLong[bufferIndexLong] = pressure_hPa;
      runningSumLong += (pressure_hPa - oldLong); // buffer is filled from setup(), so this is safe: - check
      bufferIndexLong = (bufferIndexLong + 1) % windowSizeLong;

      avgLong = runningSumLong / windowSizeLong;
    }

    // ------------------------------------------------------------
    // 🚨 Ambient is ALWAYS the 60s moving average
    // ------------------------------------------------------------
    if (!pumpingNow) {
      ambientPressure = avgLong;
    };

    // --- derive thresholds from ambient ---
    float pressureThresholdHigh    = ambientPressure + pressureThresholdDeltaHigh;
    float pressureThresholdLow = ambientPressure + pressureThresholdDeltaLow;

    // ------------------------------------------------------------
    // --- "someone is pumping" detector (avg-based, 10s) ---
    // ------------------------------------------------------------
    pumpingNow = (!suctionOn) && ((pressure_hPa - ambientPressure) >= pumpStartDelta); // Global variable with ambientPressure = avgLong
      // if suction is On, then pumping can't be done. 

    // ------------------------------------------------------------
    // Pump control
    // ------------------------------------------------------------
    // 1) High/low hysteresis control (using 10s Short Avg)
    if (avgShort >= pressureThresholdHigh) {
      digitalWrite(pumpPin, HIGH);
    } else if (avgShort <= pressureThresholdLow) {
      digitalWrite(pumpPin, LOW);
    }

    // 2) Time-based control
    // --- Count pumping events in a 30s window that starts on the first pumpingNow ---
    bool risingEdge = (pumpingNow && !lastPumpingNow);
    lastPumpingNow = pumpingNow;

    if (!windowActive) {
      if (risingEdge) {
        windowActive = true;
        windowStartMs = currentMillis;
        pumpHits = 1; // first hit starts the window
      }
    } else {
      if (risingEdge) {
        pumpHits++; // Count how many pumps have been done. Beyond 4, the pump will always turn on.
      }

      // If windowDurationMs(30s) passed since first hit, decide once
      if (currentMillis - windowStartMs >= windowDurationMs) {
        if (pumpHits >= hitsNeeded) {
          digitalWrite(pumpPin, HIGH); // kick off suction pump at end of the window
        }
        // Reset for the next window (fresh start from next pumping event)
        windowActive = false;
        windowStartMs = 0;
        pumpHits = 0;
      }
    }

    // --- debug prints ---
    Serial.print("Raw hPa: "); Serial.print(pressure_hPa, 2);
    Serial.print(" | AvgShort(10s) hPa: "); Serial.print(avgShort, 2);
    Serial.print(" | AvgLong(3mins) or ambientpressure: "); Serial.print(avgLong, 2);
    Serial.print(" | PumpingNow: "); Serial.print(pumpingNow ? "Y" : "N");
    Serial.print(" | windowActive: "); Serial.print(windowActive ? "Y" : "N");
    Serial.print(" | pumpHits: "); Serial.print(pumpHits);
    Serial.print(" | windowAgeMs: "); Serial.print(windowActive ? (currentMillis - windowStartMs) : 0);
    Serial.print(" | PumpPin: "); Serial.println(digitalRead(pumpPin) ? "HIGH" : "LOW");
  }
}

// Ambient pressure should probably be a 3rd variable that includes like 5 minutes. 
#include <Wire.h> //RXTX
#include <math.h>
#include "Adafruit_MPRLS.h"

// Otherwise, if we're also triggering every 30 seconds from when someone starts pumping, 
// that becomes an issue and assumes they're doing it continously?

// key idea: don’t update ambient while people are pumping.

// You dont *need* a reset and EOC pin for most uses, so we set to -1 and don't connect
#define RESET_PIN  -1  // set to any GPIO pin # to hard-reset on begin()
#define EOC_PIN    -1  // set to any GPIO pin to read end-of-conversion by pin
Adafruit_MPRLS mpr = Adafruit_MPRLS(RESET_PIN, EOC_PIN);
const int pumpPin = 3;

// ---- TIME -------
unsigned long currentMillis;
unsigned long lastSampleTime = 0;
const unsigned long sampleInterval = 100; // 100ms => 10Hz sampling for AvgPressure.
// const unsigned long sampleIntervalAmbient = 60UL * 1000UL;  // 60,000 ms = 1 min // 🌸

// -------- SHORT MOVING AVERAGE = for pumping detection + pump decisions (responsive)
const int windowSizeShort = 100; // 10 seconds
float pressureBufferShort[windowSizeShort];  // circular buffer of recent pressures
int bufferIndexShort = 0;   // next write position
float runningSumShort = 0; // O(1) moving average 🌸 understand

// -------- LONG MOVING AVERAGE = for ambient/baseline logic (stable)
const int windowSizeLong = 1800; // Window length (number of samples): 180,000ms / 100ms = 1800 samples (3 minutes)
float pressureBufferLong[windowSizeLong];
int bufferIndexLong = 0;
float runningSumLong = 0;

// --- ambient baseline tracking ---
float ambientPressure = 0.0;  // Starts at 0, updated in setup during first 10 seconds of readings.
const unsigned long ambientInitDurationMs = 10000; // How long to sample at boot to establish baseline

// ---

// --- 🌸 thresholds (relative to ambient; more robust than hardcoding absolute hPa) ---
const float pressureThresholdDeltaHigh = 20.0;   // hPa above ambient -> "high"
const float pressureThresholdDeltaLow  = -15.0;  // hPa below ambient -> "low"
  // Cody, why -30? it quite never made it there.
const float pumpStartDelta             = 6.0;    // hPa above ambient -> consider "someone is pumping"

// --- pump logic timing --- // DELETE IF NOT BEING USED
const unsigned long pumpHoldTimeMs = 30000;   // must stay above pumpStartDelta this long before turning on

// ------ for time-based pump activation based on # of times that pumpingNow is true
const unsigned long windowDurationMs = 35000; // 35000 = 35s window
const int hitsNeeded = 4;

bool windowActive = false;
unsigned long windowStartMs = 0;
int pumpHits = 0;

bool lastPumpingNow = false; // for rising-edge detection 🌸🌸🌸🌸🌸🌸🌸🌸🌸
// ---- main pumping variable (previous pressure above) ------
bool pumpingNow = false; // "someone is pumping" state
  // 🌸 probably an ISSUE!!! BC WE WERE SEEING IF PRESSURE WAS GOING DOWN OR UP, 
  // AND NOW WE'RE JUST SEEING ON OR OFF


void setup() {
  Serial.begin(115200);
  Serial.println("MPRLS + 1-minute Moving Average + Ambient Baseline");
  if (! mpr.begin()) {
    Serial.println("Failed to communicate with MPRLS sensor, check wiring?");
    while (1) delay(10);
  }
  Serial.println("Found MPRLS sensor");

  pinMode(pumpPin, OUTPUT);
  digitalWrite(pumpPin, LOW);

  // ------------------------------------------------------------
  // 1) Initialize ambientPressure from the FIRST 10 SECONDS (setup only)
  // ------------------------------------------------------------
  Serial.println("Bootstrapping ambientPressure from first 10 seconds...");
  
  float initSum = 0.0f;
  int initCount = 0;

  unsigned long startMs = millis();
  unsigned long lastInitSampleMs = 0;

  while (millis() - startMs < ambientInitDurationMs) {
    unsigned long now = millis();
    if (now - lastInitSampleMs >= sampleInterval) {
      lastInitSampleMs = now;
      float p = mpr.readPressure();
      initSum += p;
      initCount++;
    }
  }

  if (initCount > 0) {
    ambientPressure = initSum / initCount;
  } else {
    ambientPressure = 1012.0; // Fallback
  }

  Serial.print("Initial ambientPressure (avg first 10s) hPa: ");
  Serial.println(ambientPressure, 2);

  // ------------------------------------------------------------
  // 2.1) Prefill the 10-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeShort; i++) {
    pressureBufferShort[i] = ambientPressure;
  }
  runningSumShort = ambientPressure * windowSizeShort;
  bufferIndexShort = 0;

  // ------------------------------------------------------------
  // COULD PROBABLY BE DELETED
  // 2.2) Prefill the 60-second buffer with ambientPressure so avg starts stable
  // ------------------------------------------------------------
  for (int i = 0; i < windowSizeLong; i++) {
    pressureBufferLong[i] = ambientPressure;
  }
  runningSumLong = ambientPressure * windowSizeLong;
  bufferIndexLong = 0;

  // Set sampling clock so loop starts cleanly
  lastSampleTime = millis();
}

void loop() {
  currentMillis = millis();

  if (currentMillis - lastSampleTime >= sampleInterval) {
    lastSampleTime = currentMillis;

    // raw read
    float pressure_hPa = mpr.readPressure();
    bool suctionOn = (digitalRead(pumpPin) == HIGH);
    

    // 🔥 pressureBufferLong changed. check
    // --- 1-minute moving average (SMA) using circular buffer + running sum ---

    // -----------------------------
    // Update 10s Average Pressure (10s) 
            // Q: should this not include times when we're suctioning?
    // -----------------------------
    float oldShort = pressureBufferShort[bufferIndexShort];          // overwritten value (oldest in ring)
    pressureBufferShort[bufferIndexShort] = pressure_hPa;
    runningSumShort += (pressure_hPa - oldShort);
    bufferIndexShort = (bufferIndexShort + 1) % windowSizeShort;
    
    float avgShort = runningSumShort / windowSizeShort;

    // -----------------------------
    // Update LONG window (60s)
    // -----------------------------
    float avgLong = ambientPressure; // fallback if we skip update
        // Isn't this a condition that eats it self though? 🔥

    if (!suctionOn) { // Suction samples never enter the long buffer.
      float oldLong = pressureBufferLong[bufferIndexLong];
      pressureBufferLong[bufferIndexLong] = pressure_hPa;
      runningSumLong += (pressure_hPa - oldLong); // buffer is filled from setup(), so this is safe: - check
      bufferIndexLong = (bufferIndexLong + 1) % windowSizeLong;

      avgLong = runningSumLong / windowSizeLong;
    }

    // ------------------------------------------------------------
    // 🚨 Ambient is ALWAYS the 60s moving average
    // ------------------------------------------------------------
    // if (!pumpingNow) {
    //   ambientPressure = avgLong;
    // }; // 🚨🚨🚨🚨🚨🚨🚨RMMMMM

   

    // ------------------------------------------------------------
    // --- "someone is pumping" detector (avg-based, 10s) ---
    // ------------------------------------------------------------
    // 🚨🚨🚨🚨🚨🚨🚨RMMMMM
    // pumpingNow = (!suctionOn) && ((pressure_hPa - ambientPressure) >= pumpStartDelta); // Global variable with ambientPressure = avgLong
      // if suction is On, then pumping can't be done. 

    // ------------------------------------------------------------
    // Pumping detector + Ambient update (same-sample consistent)
    // ------------------------------------------------------------

    // -----------------"Is someone pumping?"-----------------
    // Decide pumping based on current pressure + current ambient (before updating ambient)
    bool pumpingCandidate = (!suctionOn) && ((pressure_hPa - ambientPressure) >= pumpStartDelta);
    // Update ambient from long average only when NOT pumping
    if (!pumpingCandidate) {
      ambientPressure = avgLong;
    }
    pumpingNow = pumpingCandidate; // Commit

    // --- Derive thresholds from ambient ---
    float pressureThresholdHigh    = ambientPressure + pressureThresholdDeltaHigh;
    float pressureThresholdLow = ambientPressure + pressureThresholdDeltaLow;
    // ------------------------------------------------------------

    // ------------------------------------------------------------
    // Pump control
    // ------------------------------------------------------------
    // 1) High/low hysteresis control (using 10s Short Avg)
    if (avgShort >= pressureThresholdHigh) {
      digitalWrite(pumpPin, HIGH);
    } else if (avgShort <= pressureThresholdLow) {
      digitalWrite(pumpPin, LOW);
    }

    // 2) Time-based control
    // --- Count pumping events in a 30s window that starts on the first pumpingNow ---
    bool risingEdge = (pumpingNow && !lastPumpingNow);
    lastPumpingNow = pumpingNow; // storing the current value so we can detect a rising edge next time:

    if (!windowActive) {
      if (risingEdge) {
        windowActive = true;
        windowStartMs = currentMillis;
        pumpHits = 1; // first hit starts the window
      }
    } else {
      if (risingEdge) {
        pumpHits++; // Count how many pumps have been done. Beyond 4, the pump will always turn on.
      }

      // If windowDurationMs(30s) passed since first hit, decide once
      if (currentMillis - windowStartMs >= windowDurationMs) {
        if (pumpHits >= hitsNeeded) {
          digitalWrite(pumpPin, HIGH); // kick off suction pump at end of the window
        }
        // Reset for the next window (fresh start from next pumping event)
        windowActive = false;
        windowStartMs = 0;
        pumpHits = 0;
      }
    }

    // --- debug prints ---
    Serial.print("Raw hPa: "); Serial.print(pressure_hPa, 2);
    Serial.print(" | AvgShort(10s) hPa: "); Serial.print(avgShort, 2);
    Serial.print(" | AvgLong(3mins) or ambientpressure: "); Serial.print(avgLong, 2);
    Serial.print(" | PumpingNow: "); Serial.print(pumpingNow ? "Y" : "N");
    Serial.print(" | windowActive: "); Serial.print(windowActive ? "Y" : "N");
    Serial.print(" | pumpHits: "); Serial.print(pumpHits);
    Serial.print(" | windowAgeMs: "); Serial.print(windowActive ? (currentMillis - windowStartMs) : 0);
    Serial.print(" | PumpPin: "); Serial.println(digitalRead(pumpPin) ? "HIGH" : "LOW");
  }
}