Comfort Bear – Galia Quezada Final Project

For my final project I wanted to expand upon my original bear project with a pulse sensor inside of it. With this in mind I kept the pulse sensor, but added some new interactive elements to my project. For my final, I incorporated two nightlights in the stuffed bears ears, a breathing exercise mode on the CPX, and using an Edison light to visualize what the pulse sensor would read.

When creating this project I envisioned creating an item to support children who may struggle with anxiety, specifically at night when they are trying to sleep. A fear of the dark is something common in children, so I wanted to add some type of nightlight, and additionally something that could assist children in staying calm. By adding a breathing exercise, I intended to help educate children on healthy ways to cope with anxiety!

Materials:

  • Adafruit CPX
  • Adafruit Neopixel rings
  • Adafruit Edison light
  • Adafruit buttons
  • Flexible wires
  • Fabric
  • Needles and thread

I first began by cutting out the shape of my bear on fabric. I cut two copies of the same shape, one for the front of the bear and one for the back. I additionally cut a slightly smaller version of the bear outline in a thicker pink fabric that would not be visible from the outside as that would be there I stitch all the wires to keep them from moving around freely inside the bear (a revision from the original bear)

After getting the shapes of the fabric cut out, I was ready to begin attaching wires to the CPX, as well as test different parts of the wiring. While some sections were easier to figure out such as attaching the buttons and the neopixel rings, some aspects were more difficult and took more testing and playing around to figure out (such as the pulse sensor and Edison light).

Once all the wiring and connections were figured out, I was able to stitch things down (a step I was holding off on in case something had been wired incorrectly). I sewed the neopixel rings to the ears and their corresponding button to one of the arms, the pulse sensor to the other arm and the edison light in a heart shape, as well as the cpx next to the heart with the corresponding button on the leg.

With all the components sewn down and secured, it was time to sew both sides of the bear together with the pink fabric in the center. I decided to use a overcast stitch this time instead of a simple running stitch since my original bear had stuffing falling out the sides as well as more loose ends with a running stitch whereas the overcast stitch would be a bit more secure in holding the inside contents. I also made sure to include a zipper in my design this time around to allow for easy access to the interior wiring in case I needed to reattach or adjust anything (in my original design there was no way to easily access the interior wiring).

The final step to get the Comfort bear working is connecting it to a power source. I decided on using a battery pack and stuffing it inside the empty leg so it wouldn’t be hanging loose outside the bear and potentially ripping a wire. Once turned on, the bear is able to work!

Video:

Code:

#include <Adafruit_CircuitPlayground.h>

#include <math.h>

// —————- Pin setup —————-

const int pulsePin = A4; // Pulse sensor

const int HeartLightEdison = A5;  // string light edison – heart shape

// —————- Buttons —————- (both buttons toggle on and off the related routine)

const int breathingButtonPin = A6;

const int nightlightButtonPin = A1;

const unsigned long debounceDelay = 30;

bool lastBreathingButtonReading = HIGH;  // breathing exercise

bool breathingButtonState = HIGH;

unsigned long lastBreathingDebounceTime = 0;

bool lastNightlightButtonReading = HIGH; // night light

bool nightlightButtonState = HIGH;

unsigned long lastNightlightDebounceTime = 0;

// —————- Ear Night Light / Lights & Variables —————-

// NeoPixel ear rings

const int leftEarPin = A2; // neopixel ring

const int rightEarPin = A3;  // HeartLightEdison

const int earPixelCount = 12;

Adafruit_CPlay_NeoPixel leftEar(earPixelCount, leftEarPin, NEO_GRB + NEO_KHZ800);

Adafruit_CPlay_NeoPixel rightEar(earPixelCount, rightEarPin, NEO_GRB + NEO_KHZ800);

bool nightlightOn = false;

int nightlightBrightness = 10;

int nightlightDirection = 1;

const unsigned long nightlightStepInterval = 35; // how fast the lights dim and brighten

unsigned long lastNightlightStepTime = 0;

// —————- Calming Breath  4-7-8 breathing exercise —————-

// Breathing plays only on the CPX onboard NeoPixels.

// It does not control the ear ring lights.

// It does not control HeartLightEdison string light.

bool breathingExerciseOn = false;

int breathingStage = 0;

// 0 = off

// 1 = ready blink

// 2 = inhale, green, 4 seconds

// 3 = hold, purple, 7 seconds

// 4 = exhale, blue, 8 seconds

int breathingCycleCount = 0;

const int breathingMaxCycles = 3;

unsigned long breathingStageStart = 0;

const unsigned long breathingReadyBlinkTime = 1000;  // two quick blinks before starting light routine

const unsigned long breathingInhaleTime = 4000;

const unsigned long breathingHoldTime = 7000;

const unsigned long breathingExhaleTime = 8000;

// Choose 3 different colors for the 3 stages of the calming breath

const int inhaleRed = 40;

const int inhaleGreen = 255;

const int inhaleBlue = 135;

const int holdRed = 135;

const int holdGreen = 65;

const int holdBlue = 255;

const int exhaleRed = 70;

const int exhaleGreen = 220;

const int exhaleBlue = 255;

// —————- Pulse Sensor Sampling —————-

const unsigned long sampleInterval = 10;  // 10 ms = 100 samples/sec

unsigned long lastSampleTime = 0;

int raw;

unsigned long sleepStartTime = 0; // when not interacted with, put the sensor to sleep

const unsigned long minSleepMs = 4000; // four seconds

// —————- Finger presence confirm  —————-

int fingerConfirmCount = 0; // confirm 3 times that finger is on pulse sensor

const int fingerConfirmRequired = 2;

// —————- Pulse Sensor Variables —————-

float baseline = 512;

float pulseSignal = 0;

float beatSignal = 0;

float envelope = 0;

float threshold = 0;

// —————- Pulse smoothing  —————-

const float baselineSmoothing = 0.003;

const float envelopeSmoothing = 0.08;

// Two-threshold values for finger detection:

// envelope must rise above fingerOnThreshold to GAIN finger presence

// envelope must drop below minFingerActivity to LOSE finger presence

// The dead zone between them prevents noise from activating the sensor

const float minFingerActivity = 10.0;  // loose finger below this

const float fingerOnThreshold = 18.0;  // gain finger above this

const float maxUsefulActivity = 260;

// This pulse sensor sometimes creates a downward pulse (but not in its current setup).

const bool invertPulse = false;

// Pulse threshold tuning

const float minBeatDelta = 27.0;    // raise if false beats, lower if missing beats

const float thresholdScale = 0.45;  // .45 to .85 – the lower the number, the more it is sensative to each heart heat

// —————- Clipping out random noise from the sensor on high and low ends —————-

const int railLow = 340;

const int railHigh = 790;

const int maxRailSamples = 6;

int railSamples = 0;

bool badContact = false;

unsigned long badContactUntil = 0;

// —————- Heart Beat detection —————-

bool wasAboveThreshold = false;

bool fingerPresent = false;

unsigned long lastBeatTime = 0;

unsigned long lastFlashTime = 0;

int bpm = 0;

float smoothBPM = 0;

const unsigned long minBeatInterval = 340;   // fastest allowed beat = about 176 BPM

const unsigned long maxBeatInterval = 1500;  // slowest allowed beat = 40 BPM

// —————- Put pulse sensor to sleep (or wake it) via accelerometer —————-

// —————- Sleep after ?? seconds of no movement

const unsigned long sleepAfterMs = 10000;   // 8-12 sec to sleep — tap or shake to wake

const float motionThreshold = 0.8;  *—       // m/s² change to count as movement

const unsigned long wakeDisplayMs = 1200;  // show amber light wake animation for this long

float lastMagX = 0, lastMagY = 0, lastMagZ = 0;

bool isSleeping = false;

bool wakingUp = false;

unsigned long lastMotionTime = 0;

unsigned long wakeStartTime = 0;

// How often to sample the accelerometer

const unsigned long accelInterval = 200; // sense if there’s been movement

unsigned long lastAccelTime = 0;

// —————- HeartLightEdison String Light / Pulse Light  —————-

int heartLightBrightness = 0;

int heartLightTarget = 0;

const int heartLightOff = 0;

const int heartLightDim = 20;       // dim glow while CPX is purple

const int heartLightBeat = 230;     // bright pulse flash

const int heartLightFadeUpStep = 45;    // fast rise

const int heartLightFadeDownStep = 10;  // slower fall

const unsigned long heartLightFadeInterval = 5;

unsigned long lastHeartLightFadeTime = 0;

void setup() {

  Serial.begin(115200);

  CircuitPlayground.begin();

  CircuitPlayground.setBrightness(40);

  pinMode(HeartLightEdison, OUTPUT);

  digitalWrite(HeartLightEdison, LOW);

  pinMode(breathingButtonPin, INPUT_PULLUP);

  pinMode(nightlightButtonPin, INPUT_PULLUP);

  leftEar.begin();

  rightEar.begin();

  leftEar.setBrightness(60);

  rightEar.setBrightness(60);

  clearEarRings();

  raw = analogRead(pulsePin);

  baseline = raw;

  // Read initial accel baseline

  lastMagX = CircuitPlayground.motionX();

  lastMagY = CircuitPlayground.motionY();

  lastMagZ = CircuitPlayground.motionZ();

  lastMotionTime = millis();

  CircuitPlayground.clearPixels();

  Serial.println(“raw,baseline,pulseSignal,beatSignal,envelope,threshold,bpm,finger,beat,badContact,breathing,nightlight”);

  wakingUp = true;

  wakeStartTime = millis();

}

void loop() {

  unsigned long now = millis();

  // Keep the HeartLightEdison fake-PWM fade running

  updateHeartLight(now);

  // Buttons should always be checked, even while other animations are running

  checkButtons(now);

  // Ear nightlight runs independently from CPX breathing and pulse display

  updateEarNightlights(now);

  // Accelerometer sleep / wake logic

  checkMotionAndSleep(now);

  // Wake animation has priority over everything on the CPX onboard pixels

  if (wakingUp) {

    updateWakeAnimation(now);

    return;

  }

  // If sleeping, keep everything quiet

  if (isSleeping) {

    heartLightTarget = heartLightOff;

    updateHeartLight(now);

    CircuitPlayground.clearPixels();

    return;

  }

  // Pulse sensing still runs while the breathing exercise is active.

  // But the CPX onboard pixels will show breathing if breathing is active.

  updatePulseSensor(now);

  // HeartLightEdison follows the heartbeat state, not the breathing exercise

  updateHeartLightTarget(now);

  // CPX onboard pixel display:

  // breathing exercise has priority.

  // if breathing is off, CPX shows pulse state.

  updateCPXDisplay(now);

}

// —————- Button handling —————-

void checkButtons(unsigned long now) {

  checkBreathingButton(now);

  checkNightlightButton(now);

}

void checkBreathingButton(unsigned long now) {

  bool breathingReading = digitalRead(breathingButtonPin);

  if (breathingReading != lastBreathingButtonReading) {

    lastBreathingDebounceTime = now;

  }

  if ((now – lastBreathingDebounceTime) > debounceDelay) {

    if (breathingReading != breathingButtonState) {

      breathingButtonState = breathingReading;

      if (breathingButtonState == LOW) {

        lastMotionTime = now;

        if (isSleeping && (now – sleepStartTime > minSleepMs)) {

          wakeFromSleep(now);

        }

        if (breathingExerciseOn) {

          stopBreathingExercise();

        } else {

          startBreathingExercise(now);

        }

      }

    }

  }

  lastBreathingButtonReading = breathingReading;

}

void checkNightlightButton(unsigned long now) {

  bool nightlightReading = digitalRead(nightlightButtonPin);

  if (nightlightReading != lastNightlightButtonReading) {

    lastNightlightDebounceTime = now;

  }

  if ((now – lastNightlightDebounceTime) > debounceDelay) {

    if (nightlightReading != nightlightButtonState) {

      nightlightButtonState = nightlightReading;

      if (nightlightButtonState == LOW) {

        lastMotionTime = now;

        if (isSleeping && (now – sleepStartTime > minSleepMs)) {

          wakeFromSleep(now);

        }

        nightlightOn = !nightlightOn;

        if (!nightlightOn) {

          clearEarRings();

        }

      }

    }

  }

  lastNightlightButtonReading = nightlightReading;

}

// —————- Ear Ring nightlight —————-

void updateEarNightlights(unsigned long now) {

  if (!nightlightOn) {

    clearEarRings();

    return;

  }

  if (now – lastNightlightStepTime >= nightlightStepInterval) {

    lastNightlightStepTime = now;

    nightlightBrightness += nightlightDirection;

    if (nightlightBrightness >= 60) {

      nightlightBrightness = 60;

      nightlightDirection = -1;

    }

    if (nightlightBrightness <= 8) {

      nightlightBrightness = 8;

      nightlightDirection = 1;

    }

    leftEar.setBrightness(nightlightBrightness);

    rightEar.setBrightness(nightlightBrightness);

    for (int i = 0; i < earPixelCount; i++) {

      leftEar.setPixelColor(i, leftEar.Color(101, 71, 31));  // chage these colors?

      rightEar.setPixelColor(i, rightEar.Color(101, 71, 31));

    }

    leftEar.show();

    rightEar.show();

  }

}

void clearEarRings() {

  for (int i = 0; i < earPixelCount; i++) {

    leftEar.setPixelColor(i, 0);

    rightEar.setPixelColor(i, 0);

  }

  leftEar.show();

  rightEar.show();

}

// —————- Calming Breathing exercise —————-

void startBreathingExercise(unsigned long now) {

  breathingExerciseOn = true;

  breathingStage = 1;  // ready blink

  breathingCycleCount = 0;

  breathingStageStart = now;

}

void stopBreathingExercise() {

  breathingExerciseOn = false;

  breathingStage = 0;

  breathingCycleCount = 0;

  CircuitPlayground.clearPixels();

}

void updateBreathingExercise(unsigned long now) {

  if (!breathingExerciseOn) {

    return;

  }

  unsigned long elapsed = now – breathingStageStart;

  if (breathingStage == 1) {

    // Ready blink: two quick blinks before starting the breathing exercise

    if (elapsed >= breathingReadyBlinkTime) {

      breathingStage = 2;

      breathingStageStart = now;

      return;

    }

    int blinkPhase = elapsed / 250;

    bool blinkOn = (blinkPhase == 0 || blinkPhase == 2);

    if (blinkOn) {

      for (int i = 0; i < 10; i++) {

        CircuitPlayground.setPixelColor(i, 180, 180, 180);

      }

    } else {

      CircuitPlayground.clearPixels();

    }

    return;

  }

  if (breathingStage == 2) {

    // Inhale: 4 seconds, green, fade up

    if (elapsed >= breathingInhaleTime) {

      breathingStage = 3;

      breathingStageStart = now;

      return;

    }

    int brightness = map(elapsed, 0, breathingInhaleTime, 5, 255);

    drawBreathingSoftChase(now, inhaleRed, inhaleGreen, inhaleBlue, brightness);

    return;

  }

  if (breathingStage == 3) {

    // Hold: 7 seconds, purple, stay bright

    if (elapsed >= breathingHoldTime) {

      breathingStage = 4;

      breathingStageStart = now;

      return;

    }

    drawBreathingSoftChase(now, holdRed, holdGreen, holdBlue, 255);

    return;

  }

  if (breathingStage == 4) {

    // Exhale: 8 seconds, blue, fade down

    if (elapsed >= breathingExhaleTime) {

      breathingCycleCount++;

      if (breathingCycleCount >= breathingMaxCycles) {

        stopBreathingExercise();

        return;

      } else {

        breathingStage = 2;

        breathingStageStart = now;

        return;

      }

    }

    int brightness = map(elapsed, 0, breathingExhaleTime, 255, 5);

    drawBreathingSoftChase(now, exhaleRed, exhaleGreen, exhaleBlue, brightness);

    return;

  }

}

void drawBreathingSoftChase(unsigned long now, int red, int green, int blue, int overallBrightness) {

  overallBrightness = constrain(overallBrightness, 0, 255);

  // One full revolution per second around the 10 CPX NeoPixels

  float seconds = now / 1000.0;

  float chasePosition = fmod(seconds * 10.0, 10.0);

  for (int i = 0; i < 10; i++) {

    float distance = fabs(i – chasePosition);

    // Wrap around the circular ring

    if (distance > 5.0) {

      distance = 10.0 – distance;

    }

    // Soft brightness wave:

    // close to chasePosition = brightest

    // farther away = dimmer

    float wave = 0.0;

    if (distance < 2.5) {

      wave = 1.0 – (distance / 2.5);

    }

    float softBase = 0.25;

    float softWave = softBase + (wave * 0.75);

    int pixelBrightness = overallBrightness * softWave;

    int r = (red * pixelBrightness) / 255;

    int g = (green * pixelBrightness) / 255;

    int b = (blue * pixelBrightness) / 255;

    CircuitPlayground.setPixelColor(i, r, g, b);

  }

}

// —————- Pulse Sensing —————-

void updatePulseSensor(unsigned long now) {

  if (now – lastSampleTime < sampleInterval) {

    return;

  }

  lastSampleTime = now;

  raw = analogRead(pulsePin);

  bool beatDetected = false;

  // Detect sensor clipping / finger removed / unusable contact

  bool railed = (raw <= railLow || raw >= railHigh);

  if (railed) {

    railSamples++;

    if (railSamples >= maxRailSamples) {

      badContact = true;

      badContactUntil = now + 600;

      bpm = 0;

      smoothBPM = 0;

      lastBeatTime = 0;

      wasAboveThreshold = false;

      fingerPresent = false;

      fingerConfirmCount = 0;

      baseline = raw;

      pulseSignal = 0;

      beatSignal = 0;

      envelope = 0;

      threshold = minBeatDelta;

    }

    printData(raw, beatDetected);

    return;

  }

  railSamples = 0;

  // Let signal recover after bad contact

  if (badContact) {

    baseline = baseline + 0.10 * (raw – baseline);

    pulseSignal = 0;

    beatSignal = 0;

    envelope = 0;

    threshold = minBeatDelta;

    if (now > badContactUntil) {

      badContact = false;

      baseline = raw;

    }

    printData(raw, beatDetected);

    return;

  }

  // Slow baseline tracking

  baseline = baseline + baselineSmoothing * (raw – baseline);

  // Raw difference from baseline

  pulseSignal = raw – baseline;

  // Invert if your sensor waveform is mostly downward pulses

  if (invertPulse) {

    beatSignal = -pulseSignal;

  } else {

    beatSignal = pulseSignal;

  }

  // Envelope based on total activity

  envelope = envelope + envelopeSmoothing * (fabs(pulseSignal) – envelope);

  threshold = envelope * thresholdScale;

  if (threshold < minBeatDelta) {

    threshold = minBeatDelta;

  }

  // Finger detection with two-threshold hysteresis and confirm counter:

  // – To GAIN finger: envelope must exceed fingerOnThreshold for fingerConfirmRequired samples

  // – To LOSE finger: envelope must drop below minFingerActivity

  if (!fingerPresent) {

    if (envelope > fingerOnThreshold && envelope < maxUsefulActivity) {

      fingerConfirmCount++;

      if (fingerConfirmCount >= fingerConfirmRequired) {

        fingerPresent = true;

        fingerConfirmCount = 0;

        lastMotionTime = now;  // finger counts as activity

      }

    } else {

      fingerConfirmCount = 0;

    }

  } else {

    if (envelope < minFingerActivity || envelope > maxUsefulActivity) {

      fingerPresent = false;

      wasAboveThreshold = false;

      fingerConfirmCount = 0;

    }

  }

  if (fingerPresent) {

    bool aboveThreshold = beatSignal > threshold;

    // Detect only upward crossing of the chosen beat signal

    if (aboveThreshold && !wasAboveThreshold) {

      unsigned long interval = now – lastBeatTime;

      if (lastBeatTime > 0 && interval >= minBeatInterval && interval <= maxBeatInterval) {

        int instantBPM = 60000 / interval;

        if (smoothBPM == 0) {

          smoothBPM = instantBPM;

        } else {

          smoothBPM = smoothBPM * 0.75 + instantBPM * 0.25;

        }

        bpm = smoothBPM;

        beatDetected = true;

        lastFlashTime = now;

      }

      lastBeatTime = now;

    }

    wasAboveThreshold = aboveThreshold;

  } else {

    wasAboveThreshold = false;

    if (now – lastBeatTime > 2000) {

      bpm = 0;

      smoothBPM = 0;

      lastBeatTime = 0;

    }

  }

  bool beatLatch = (lastFlashTime > 0 && (now – lastFlashTime < 300));

  printData(raw, beatLatch);

}

// —————- CPX onboard pixels —————-

void updateCPXDisplay(unsigned long now) {

  // Breathing exercise takes over the onboard CPX NeoPixels

  if (breathingExerciseOn) {

    updateBreathingExercise(now);

    return;

  }

  // Otherwise, show pulse status

  updatePulsePixels(now);

}

void updatePulsePixels(unsigned long now) {

  if (badContact || envelope > maxUsefulActivity) {

    // Amber/orange = bad contact, too much pressure, movement, or saturation

    for (int i = 0; i < 10; i++) {

      CircuitPlayground.setPixelColor(i, 80, 35, 0);

    }

    return;

  }

  if (!fingerPresent) {

    CircuitPlayground.clearPixels();

    return;

  }

  if (now – lastFlashTime < 90) {

    // CPX heartbeat flash

    for (int i = 0; i < 10; i++) {

      CircuitPlayground.setPixelColor(i, 255, 20, 40);

    }

  } else {

    // CPX purple resting state

    for (int i = 0; i < 10; i++) {

      CircuitPlayground.setPixelColor(i, 20, 0, 25);

    }

  }

}

// —————- HeartLightEdison Pulse Display —————-

void updateHeartLightTarget(unsigned long now) {

  if (badContact || envelope > maxUsefulActivity) {

    // HeartLightEdison off during yellow/orange CPX state

    heartLightTarget = heartLightOff;

    return;

  }

  if (!fingerPresent) {

    // No finger = HeartLightEdison off

    heartLightTarget = heartLightOff;

    return;

  }

  if (now – lastFlashTime < 90) {

    // HeartLightEdison quickly fades up on pulse

    heartLightTarget = heartLightBeat;

  } else {

    // HeartLightEdison stays dim while CPX would be purple

    heartLightTarget = heartLightDim;

  }

}

void updateHeartLight(unsigned long now) {

  if (now – lastHeartLightFadeTime >= heartLightFadeInterval) {

    lastHeartLightFadeTime = now;

    if (heartLightBrightness < heartLightTarget) {

      heartLightBrightness += heartLightFadeUpStep;

      if (heartLightBrightness > heartLightTarget) {

        heartLightBrightness = heartLightTarget;

      }

    } else if (heartLightBrightness > heartLightTarget) {

      heartLightBrightness -= heartLightFadeDownStep;

      if (heartLightBrightness < heartLightTarget) {

        heartLightBrightness = heartLightTarget;

      }

    }

  }

  fakePWM(HeartLightEdison, heartLightBrightness);

}

void fakePWM(uint8_t pin, uint8_t brightness) {

  // Fake PWM for pins that do not have smooth hardware PWM behavior.

  // Keep cycles low so pulse sensor reading is not blocked too long.

  const int cycles = 4;

  if (brightness <= 0) {

    digitalWrite(pin, LOW);

    return;

  }

  if (brightness >= 255) {

    digitalWrite(pin, HIGH);

    return;

  }

  for (int i = 0; i < cycles; i++) {

    digitalWrite(pin, HIGH);

    delayMicroseconds(brightness * 4);

    digitalWrite(pin, LOW);

    delayMicroseconds((255 – brightness) * 4);

  }

}

// —————- Motion / sleep / wake —————-

void checkMotionAndSleep(unsigned long now) {

  if (now – lastAccelTime < accelInterval) {

    return;

  }

  lastAccelTime = now;

  if (wakingUp) {

    return;

  }

  float ax = CircuitPlayground.motionX();

  float ay = CircuitPlayground.motionY();

  float az = CircuitPlayground.motionZ();

  float dx = fabs(ax – lastMagX);

  float dy = fabs(ay – lastMagY);

  float dz = fabs(az – lastMagZ);

  float totalMotion = dx + dy + dz;

  lastMagX = ax;

  lastMagY = ay;

  lastMagZ = az;

  if (totalMotion > motionThreshold) {

    lastMotionTime = now;

    if (isSleeping && (now – sleepStartTime > minSleepMs)) {

      wakeFromSleep(now);

    }

  }

  // Do not sleep if a mode is intentionally active

  bool interactionActive = fingerPresent || breathingExerciseOn || nightlightOn;

  if (!isSleeping && !interactionActive && (now – lastMotionTime > sleepAfterMs)) {

    goToSleep(now);

  }

}

void goToSleep(unsigned long now) {

  isSleeping = true;

  sleepStartTime = now;

  wakingUp = false;

  fingerPresent = false;

  fingerConfirmCount = 0;

  bpm = 0;

  smoothBPM = 0;

  lastBeatTime = 0;

  stopBreathingExercise();

  heartLightTarget = heartLightOff;

  heartLightBrightness = 0;

  digitalWrite(HeartLightEdison, LOW);

  CircuitPlayground.clearPixels();

  Serial.println(“— SLEEPING —“);

  // Reset accel baseline immediately, without using delay()

  lastMagX = CircuitPlayground.motionX();

  lastMagY = CircuitPlayground.motionY();

  lastMagZ = CircuitPlayground.motionZ();

  lastMotionTime = now;

}

void wakeFromSleep(unsigned long now) {

  isSleeping = false;

  wakingUp = true;

  wakeStartTime = now;

  // Reset signal state so we get a clean start

  baseline = analogRead(pulsePin);

  envelope = 0;

  fingerPresent = false;

  fingerConfirmCount = 0;

  bpm = 0;

  smoothBPM = 0;

  lastBeatTime = 0;

  badContact = false;

  railSamples = 0;

  heartLightTarget = heartLightOff;

}

void updateWakeAnimation(unsigned long now) {

  unsigned long elapsed = now – wakeStartTime;

  if (elapsed < wakeDisplayMs) {

    // Pulse amber to signal waking up

    int brightness = (elapsed < wakeDisplayMs / 2)

                       ? map(elapsed, 0, wakeDisplayMs / 2, 5, 60)

                       : map(elapsed, wakeDisplayMs / 2, wakeDisplayMs, 60, 5);

    CircuitPlayground.setBrightness(brightness);

    for (int i = 0; i < 10; i++) {

      CircuitPlayground.setPixelColor(i, 80, 35, 0);

    }

    // HeartLightEdison stays off during wake animation

    heartLightTarget = heartLightOff;

    updateHeartLight(now);

    return;

  }

  // Done waking, restore brightness and resume

  CircuitPlayground.setBrightness(40);

  wakingUp = false;

  lastMagX = CircuitPlayground.motionX();

  lastMagY = CircuitPlayground.motionY();

  lastMagZ = CircuitPlayground.motionZ();

  lastMotionTime = now;

}

// —————- Serial data for debugging —————-

void printData(int raw, bool beatDetected) {

  Serial.print(raw);

  Serial.print(“,”);

  Serial.print(baseline);

  Serial.print(“,”);

  Serial.print(pulseSignal);

  Serial.print(“,”);

  Serial.print(beatSignal);

  Serial.print(“,”);

  Serial.print(envelope);

  Serial.print(“,”);

  Serial.print(threshold);

  Serial.print(“,”);

  Serial.print(bpm);

  Serial.print(“,”);

  Serial.print(fingerPresent ? “Finger Present” : ” No Finger”);

  Serial.print(“,”);

  Serial.print(beatDetected ? “Beat Detected” : ” No Beat”);

  Serial.print(“,”);

  Serial.print(badContact ? “Poor Contact” : ” Good Contact”);

  Serial.print(“,”);

  Serial.print(breathingExerciseOn ? “Breathing On” : ” Breathing Off”);

  Serial.print(“,”);

  Serial.println(nightlightOn ? “Nightlight On” : ” Nightlight Off”);

}