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;
    }
  }
}```