0001-01-01
Code & Control System
The system is controlled using a Raspberry Pi, which manages both the LED panel and the water pump. The code evolved alongside the physical system, adapting to hardware limitations and design changes during development.
LED & Pump Control
Below is the code I used for the Storm and to turn the pump on or off.
#include <FastLED.h>
// ===================== CONFIG =====================
#define PUMP_PIN 9
#define LED_PIN 6
#define NUM_LEDS 102
#define BRIGHTNESS 255
#define COLS 6
#define ROWS 17
#define GRASS_ROWS 5
#define LED_TYPE WS2813
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
// ===================== TIMING =====================
const unsigned long TRANSITION_TIME = 30000; // 30 sec darkening
const unsigned long STORM_TIME = 30000; // 1 min storm
const unsigned long RETURN_TIME = 30000; // 30 sec brighten
const unsigned long PUMP_START_DELAY = 3000; // wait 3 sec after storm starts
const unsigned long PUMP_ON_TIME = 4000; // pump on duration
const unsigned long PUMP_OFF_TIME = 2000; // pump off duration
// ===================== COLORS =====================
CRGB SKY_DAY = CRGB(0, 0, 255);
CRGB GRASS_DAY = CRGB(0, 255, 0);
CRGB SKY_STORM = CRGB(0, 0, 20);
CRGB GRASS_STORM = CRGB(5, 30, 5);
CRGB FLASH_WHITE = CRGB(140, 140, 180);
CRGB FLASH_PURP = CRGB(100, 0, 180);
CRGB FLASH_BLUE = CRGB(40, 40, 255);
// ===================== STATE =====================
enum Phase {
TRANSITION_TO_STORM,
STORM_HOLD,
RETURN_TO_DAY,
DONE
};
Phase phase = TRANSITION_TO_STORM;
unsigned long phaseStart = 0;
bool pumpIsOn = false;
unsigned long pumpLastChange = 0;
// ===================== ZIG-ZAG =====================
int idx(int col, int row) {
if (col % 2 == 0) return col * ROWS + row;
return col * ROWS + (ROWS - 1 - row);
}
// ===================== PUMP =====================
void pumpOn() {
digitalWrite(PUMP_PIN, HIGH); // active HIGH
pumpIsOn = true;
}
void pumpOff() {
digitalWrite(PUMP_PIN, LOW); // active HIGH
pumpIsOn = false;
}
void updatePumpPulse() {
unsigned long now = millis();
if (pumpIsOn) {
if (now - pumpLastChange >= PUMP_ON_TIME) {
pumpOff();
pumpLastChange = now;
}
} else {
if (now - pumpLastChange >= PUMP_OFF_TIME) {
pumpOn();
pumpLastChange = now;
}
}
}
// ===================== SCENE =====================
void drawScene(uint8_t amount) {
for (int col = 0; col < COLS; col++) {
for (int row = 0; row < ROWS; row++) {
if (row >= ROWS - GRASS_ROWS) {
uint8_t grassAmount = min(255, (int)(amount * 1.5));
leds[idx(col, row)] = blend(GRASS_DAY, GRASS_STORM, grassAmount);
} else {
leds[idx(col, row)] = blend(SKY_DAY, SKY_STORM, amount);
}
}
}
}
// ===================== LIGHTNING =====================
void addPixelSafe(int col, int row, CRGB color) {
if (col >= 0 && col < COLS && row >= 0 && row < ROWS) {
leds[idx(col, row)] += color;
}
}
void lightningStrike() {
int col = random(0, COLS);
int startRow = random(0, 3);
int len = random(8, 15);
for (int k = 0; k < len; k++) {
int row = startRow + k;
if (row >= ROWS - 1) break;
CRGB c;
uint8_t pick = random8();
if (pick < 130) c = FLASH_PURP;
else if (pick < 220) c = FLASH_BLUE;
else c = FLASH_WHITE;
if (random8() < 220) addPixelSafe(col, row, c);
if (random8() < 90) addPixelSafe(col - 1, row, c);
if (random8() < 90) addPixelSafe(col + 1, row, c);
if (random8() < 70) {
int branchCol = col + (random8() < 128 ? -1 : 1);
int branchRow = row + random(0, 3);
addPixelSafe(branchCol, branchRow, FLASH_PURP);
}
}
if (random8() < 180) {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i].b = qadd8(leds[i].b, 10);
if (random8() < 70) leds[i].r = qadd8(leds[i].r, 4);
}
}
}
void stormShimmer() {
for (int i = 0; i < 8; i++) {
int col = random(0, COLS);
int row = random(0, ROWS - GRASS_ROWS);
leds[idx(col, row)].b = qadd8(leds[idx(col, row)].b, random8(5, 18));
}
}
// ===================== SETUP =====================
void setup() {
digitalWrite(PUMP_PIN, LOW); // preload OFF
pinMode(PUMP_PIN, OUTPUT);
pumpIsOn = false;
delay(500);
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
randomSeed(analogRead(A0));
phaseStart = millis();
pumpLastChange = millis();
drawScene(0);
FastLED.show();
}
// ===================== LOOP =====================
void loop() {
unsigned long now = millis();
switch (phase) {
case TRANSITION_TO_STORM: {
pumpOff();
unsigned long elapsed = now - phaseStart;
if (elapsed >= TRANSITION_TIME) {
phase = STORM_HOLD;
phaseStart = now;
pumpLastChange = now;
pumpOff();
break;
}
uint8_t amount = map(elapsed, 0, TRANSITION_TIME, 0, 255);
drawScene(amount);
FastLED.show();
delay(30);
break;
}
case STORM_HOLD: {
unsigned long elapsed = now - phaseStart;
if (elapsed >= STORM_TIME) {
pumpOff();
phase = RETURN_TO_DAY;
phaseStart = now;
break;
}
drawScene(255);
stormShimmer();
if (elapsed >= PUMP_START_DELAY) {
updatePumpPulse();
} else {
pumpOff();
}
if (random8() < 55) {
lightningStrike();
}
FastLED.show();
delay(40);
break;
}
case RETURN_TO_DAY: {
pumpOff();
unsigned long elapsed = now - phaseStart;
if (elapsed >= RETURN_TIME) {
phase = DONE;
drawScene(0);
pumpOff();
FastLED.show();
break;
}
uint8_t amount = map(elapsed, 0, RETURN_TIME, 255, 0);
drawScene(amount);
FastLED.show();
delay(30);
break;
}
case DONE: {
pumpOff();
drawScene(0);
FastLED.show();
break;
}
}
}```