Magic Mushroom Lamp! - Bambu3Design – 3D Printing & Design
magic mushroom lamp

Magic Mushroom Lamp!

#include <Adafruit_NeoPixel.h>
#include <math.h>

// =====================================================
// PIN SETTINGS
// =====================================================

#define HALL_PIN   2
#define TOUCH_PIN  3
#define LED_PIN    4

#define NUM_LEDS   10

Adafruit_NeoPixel strip(
  NUM_LEDS,
  LED_PIN,
  NEO_GRB + NEO_KHZ800
);

// =====================================================
// TIMING
// =====================================================

const unsigned long WATER_SPEED       = 120;
const unsigned long DOUBLE_CLICK_TIME = 350;
const unsigned long LONG_PRESS_TIME   = 1000;
const unsigned long DEBOUNCE_TIME     = 30;

// =====================================================
// MODES
// =====================================================

enum LampMode {
  MODE_OFF,
  MODE_NIGHT,
  MODE_COLOR,
  MODE_RAINBOW,
  MODE_BREATHING
};

LampMode mode = MODE_OFF;
LampMode modeBeforeBreathing = MODE_OFF;

// =====================================================
// COLORS
// =====================================================

// 12 fixed colors

const uint8_t colors[][3] = {

  {0,   255, 0},      // 1 Green
  {120, 255, 0},      // 2 Lime
  {0,   255, 180},    // 3 Aqua Green
  {0,   220, 255},    // 4 Cyan
  {0,   100, 255},    // 5 Light Blue
  {0,   0,   255},    // 6 Blue
  {120, 0,   255},    // 7 Purple
  {210, 60,  255},    // 8 Lilac
  {255, 0,   120},    // 9 Pink
  {255, 0,   0},      // 10 Red
  {255, 80,  0},      // 11 Orange
  {255, 220, 0}       // 12 Yellow
};

const int COLOR_COUNT =
  sizeof(colors) / sizeof(colors[0]);

int colorIndex = 0;


// This stores the current selected color
uint8_t currentR = 0;
uint8_t currentG = 255;
uint8_t currentB = 0;

// =====================================================
// WATERING VARIABLES
// =====================================================

int waterPosition = -1;

bool watering = false;
bool returning = false;

unsigned long lastWaterStep = 0;

// =====================================================
// HALL SENSOR
// =====================================================

bool hallRaw = false;
bool hallStable = false;

unsigned long hallChangeTime = 0;

// =====================================================
// TOUCH SENSOR
// =====================================================

bool touchRaw = false;
bool touchStable = false;

unsigned long touchChangeTime = 0;
unsigned long pressStartTime = 0;
unsigned long lastReleaseTime = 0;

int clickCount = 0;

bool longPressHandled = false;

// =====================================================
// BASIC LED FUNCTIONS
// =====================================================

void clearStrip() {

  strip.clear();
  strip.show();
}


void showColor(
  uint8_t r,
  uint8_t g,
  uint8_t b
) {

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

    strip.setPixelColor(
      i,
      strip.Color(r, g, b)
    );
  }

  strip.show();
}


// =====================================================
// SET CURRENT COLOR
// =====================================================

void selectColor(
  uint8_t r,
  uint8_t g,
  uint8_t b
) {

  currentR = r;
  currentG = g;
  currentB = b;

  showColor(r, g, b);
}

// =====================================================
// WATERING EFFECT
// =====================================================

void showWater() {

  strip.clear();

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

    // Always GREEN

    strip.setPixelColor(
      i,
      strip.Color(0, 255, 0)
    );
  }

  strip.show();
}

// =====================================================
// HALL SENSOR
// =====================================================

void updateHall() {

  unsigned long now = millis();

  // A3144 typically:
  // LOW = magnet detected

  bool current =
    (digitalRead(HALL_PIN) == LOW);


  if (current != hallRaw) {

    hallRaw = current;

    hallChangeTime = now;
  }


  if (
    now - hallChangeTime >= DEBOUNCE_TIME &&
    hallStable != hallRaw
  ) {

    hallStable = hallRaw;


    // MAGNET DETECTED
    if (hallStable) {

      watering = true;
      returning = false;

      waterPosition = -1;

      lastWaterStep = now;

      clearStrip();
    }


    // MAGNET REMOVED
    else {

      watering = false;

      if (waterPosition >= 0) {

        returning = true;

      } else {

        returning = false;
      }

      lastWaterStep = now;
    }
  }
}

// =====================================================
// WATER MOVEMENT
// =====================================================

void updateWater() {

  if (!watering && !returning) {
    return;
  }


  unsigned long now = millis();


  if (
    now - lastWaterStep <
    WATER_SPEED
  ) {

    return;
  }


  lastWaterStep = now;


  // ------------------------------
  // GOING UP
  // ------------------------------

  if (watering) {

    if (
      waterPosition <
      NUM_LEDS - 1
    ) {

      waterPosition++;
    }

    showWater();
  }


  // ------------------------------
  // RETURNING DOWN
  // ------------------------------

  else if (returning) {

    waterPosition--;


    if (waterPosition < 0) {

      returning = false;

      return;
    }


    showWater();
  }
}

// =====================================================
// SINGLE TOUCH
// =====================================================

void singleTouch() {

  if (mode == MODE_OFF) {

    mode = MODE_NIGHT;
  }

  else {

    mode = MODE_OFF;
  }
}

// =====================================================
// DOUBLE TOUCH
// =====================================================

void doubleTouch() {

  // If currently in rainbow,
  // return to first fixed color

  if (mode == MODE_RAINBOW) {

    colorIndex = 0;

    mode = MODE_COLOR;

    currentR = colors[colorIndex][0];
    currentG = colors[colorIndex][1];
    currentB = colors[colorIndex][2];

    return;
  }


  // Enter color mode if not there

  if (mode != MODE_COLOR) {

    mode = MODE_COLOR;

    colorIndex = 0;

  }

  else {

    colorIndex++;
  }


  // After 12 colors → Rainbow

  if (colorIndex >= COLOR_COUNT) {

    mode = MODE_RAINBOW;

    colorIndex = 0;

    return;
  }


  currentR = colors[colorIndex][0];
  currentG = colors[colorIndex][1];
  currentB = colors[colorIndex][2];
}

// =====================================================
// LONG TOUCH
// =====================================================

void longTouch() {

  // Enter breathing mode

  if (mode != MODE_BREATHING) {

    modeBeforeBreathing = mode;

    // If OFF → use warm white breathing

    if (mode == MODE_OFF) {

      currentR = 255;
      currentG = 100;
      currentB = 25;
    }


    // If NIGHT → save warm white

    if (mode == MODE_NIGHT) {

      currentR = 255;
      currentG = 100;
      currentB = 25;
    }


    mode = MODE_BREATHING;
  }


  // Exit breathing
  else {

    mode = modeBeforeBreathing;
  }
}

// =====================================================
// TOUCH READING
// =====================================================

void updateTouch() {

  unsigned long now = millis();


  // TTP223 default:
  // HIGH while touched

  bool current =
    (digitalRead(TOUCH_PIN) == HIGH);


  if (current != touchRaw) {

    touchRaw = current;

    touchChangeTime = now;
  }


  if (
    now - touchChangeTime >= DEBOUNCE_TIME &&
    touchStable != touchRaw
  ) {

    touchStable = touchRaw;


    // Finger pressed

    if (touchStable) {

      pressStartTime = now;

      longPressHandled = false;
    }


    // Finger released

    else {

      if (!longPressHandled) {

        clickCount++;

        lastReleaseTime = now;
      }
    }
  }


  // ===================================================
  // LONG PRESS
  // ===================================================

  if (
    touchStable &&
    !longPressHandled
  ) {

    if (
      now - pressStartTime >=
      LONG_PRESS_TIME
    ) {

      longTouch();

      longPressHandled = true;

      clickCount = 0;
    }
  }


  // ===================================================
  // SINGLE / DOUBLE CLICK
  // ===================================================

  if (
    !touchStable &&
    clickCount > 0 &&
    now - lastReleaseTime >
    DOUBLE_CLICK_TIME
  ) {

    if (clickCount == 1) {

      singleTouch();

    }

    else {

      doubleTouch();
    }


    clickCount = 0;
  }
}

// =====================================================
// RAINBOW
// =====================================================

uint32_t wheel(byte pos) {

  pos = 255 - pos;


  if (pos < 85) {

    return strip.Color(
      255 - pos * 3,
      0,
      pos * 3
    );
  }


  if (pos < 170) {

    pos -= 85;

    return strip.Color(
      0,
      pos * 3,
      255 - pos * 3
    );
  }


  pos -= 170;


  return strip.Color(
    pos * 3,
    255 - pos * 3,
    0
  );
}


// =====================================================
// RAINBOW ANIMATION
// =====================================================

void showRainbow(float brightness = 1.0) {

  static uint16_t rainbowOffset = 0;

  rainbowOffset++;


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

    byte position =
      (i * 256 / NUM_LEDS +
       rainbowOffset) & 255;


    uint32_t color =
      wheel(position);


    uint8_t r =
      (uint8_t)((color >> 16) & 0xFF);

    uint8_t g =
      (uint8_t)((color >> 8) & 0xFF);

    uint8_t b =
      (uint8_t)(color & 0xFF);


    r = r * brightness;
    g = g * brightness;
    b = b * brightness;


    strip.setPixelColor(
      i,
      strip.Color(r, g, b)
    );
  }


  strip.show();
}

// =====================================================
// NORMAL LAMP UPDATE
// =====================================================

void updateLamp() {

  // Magnet animation has priority

  if (watering || returning) {

    return;
  }


  switch (mode) {


    // =================================================
    // OFF
    // =================================================

    case MODE_OFF:

      clearStrip();

      break;


    // =================================================
    // NIGHT LIGHT
    // =================================================

    case MODE_NIGHT:

      currentR = 255;
      currentG = 100;
      currentB = 25;

      showColor(
        currentR,
        currentG,
        currentB
      );

      break;


    // =================================================
    // FIXED COLORS
    // =================================================

    case MODE_COLOR:

      currentR =
        colors[colorIndex][0];

      currentG =
        colors[colorIndex][1];

      currentB =
        colors[colorIndex][2];


      showColor(
        currentR,
        currentG,
        currentB
      );

      break;


    // =================================================
    // RAINBOW
    // =================================================

    case MODE_RAINBOW:

      showRainbow(1.0);

      break;


    // =================================================
    // BREATHING
    // =================================================

    case MODE_BREATHING: {

      // Smooth breathing
      // approximately 3 seconds

      float phase =
        (millis() % 3000UL) /
        3000.0f;


      float wave =
        (1.0f -
         cosf(
           phase *
           6.2831853f
         )) / 2.0f;


      // Don't completely turn off

      float brightness =
        0.06f +
        wave * 0.94f;


      // If breathing started
      // while in Rainbow mode

      if (
        modeBeforeBreathing ==
        MODE_RAINBOW
      ) {

        showRainbow(
          brightness
        );

      }


      // Normal fixed-color breathing

      else {

        uint8_t r =
          currentR * brightness;

        uint8_t g =
          currentG * brightness;

        uint8_t b =
          currentB * brightness;


        showColor(
          r,
          g,
          b
        );
      }

      break;
    }
  }
}

// =====================================================
// SETUP
// =====================================================

void setup() {

  Serial.begin(115200);


  pinMode(
    HALL_PIN,
    INPUT_PULLUP
  );


  pinMode(
    TOUCH_PIN,
    INPUT
  );


  strip.begin();


  // Global maximum brightness
  // 0 - 255

  strip.setBrightness(150);


  clearStrip();


  hallRaw =
    (digitalRead(HALL_PIN) == LOW);


  hallStable = hallRaw;


  touchRaw =
    (digitalRead(TOUCH_PIN) == HIGH);


  touchStable = touchRaw;


  // Magnet already near
  // when device starts

  if (hallStable) {

    watering = true;

    waterPosition = -1;

    lastWaterStep =
      millis();
  }
}

// =====================================================
// LOOP
// =====================================================

void loop() {

  updateHall();

  updateTouch();

  updateWater();

  updateLamp();
}
Scroll to Top