#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();
}