Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Makestreme
Published © GPL3+

Chameleon art that changes color with the weather

This is a beautiful artwork of a chameleon that uses an esp8266 and an addressable LED strip to change color based on the weather.

IntermediateFull instructions provided5 hours475
Chameleon art that changes color with the weather

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Waterproof WS2813 RGB LED Strip Waterproof
Seeed Studio Waterproof WS2813 RGB LED Strip Waterproof
×1
5V 2.5A Switching Power Supply
Digilent 5V 2.5A Switching Power Supply
×1

Software apps and online services

Arduino IDE
Arduino IDE
Openweathermap api

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Chameleon stl

Sketchfab still processing.

Schematics

LED connections to D1 mini

Code

Test_LED

Arduino
Test your LED strip connection
#include <FastLED.h>

// Define the number of LEDs
#define NUM_LEDS 5
#define LED_PIN D2

// Create an array to hold the LEDs
CRGB leds[NUM_LEDS];

void setup() {
  // Initialize the LED strip
  FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
}

void loop() {
  static uint8_t hue = 0; // Start with hue at 0

  // Set all LEDs to the same color
  fill_solid(leds, NUM_LEDS, CHSV(hue, 255, 255));

  // Show the updated LEDs
  FastLED.show();

  // Increment the hue slightly to cycle through colors
  hue++;

  // Add a short delay for a smooth transition
  delay(20);
}

Weather_color_main

Arduino
Main Code to change color based on weather
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include <FastLED.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

// WiFi and HTTP client
WiFiClient client;
HTTPClient http;

// Time Client for NTP
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800, 60000); // UTC +5:30 for India (adjust for your timezone)

#define MAX_BRIGHTNESS  200
#define LOW_BRIGHTNESS  120 

// Define the number of LEDs and the data pin
#define NUM_LEDS 5
#define DATA_PIN D2  // Change to the pin connected to your LED strip

// Create an array to hold the LED data
CRGB leds[NUM_LEDS];

uint8_t condition = 0; // Start with the first palette

// WiFi and OpenWeatherMap credentials
const char* ssid = "WIFI_SSID";  // Replace with your WiFi SSID
const char* password = "WIFI_PASSWORD";     // Replace with your WiFi password
const char* apiKey = "API Key"; // OpenWeatherMap API key
const char* city = "City,country code";     // City name and country code separated by comma for weather data (No space after comma)
const char* server = "http://api.openweathermap.org/data/2.5/weather?q=";

// Weather check interval (10 minutes)
const unsigned long checkInterval = 600000;
unsigned long lastCheck = 0;

// Define the gradients for each phase
 
CRGB blueToPink[] = {CRGB::Blue, CRGB::Purple, CRGB::DeepPink, CRGB::Purple, CRGB::Blue};

CRGB deepGreenToYellow[] = {CRGB::DarkGreen, CRGB::ForestGreen, CRGB(255, 255, 0), CRGB(255, 255, 0), CRGB::DarkGreen};

CRGB yellowToRed[] = {CRGB(255, 255, 0), CRGB::Orange, CRGB::Red, CRGB::DarkRed, CRGB(255, 255, 0)};

CRGB icyBlueGradient5[] = {CRGB(0, 191, 255),CRGB(135, 206, 250),CRGB(240, 255, 255), CRGB(135, 206, 250), CRGB(0, 191, 255)};

// Function to fetch weather data and update the condition
void fetchWeather() {
  String url = String(server) + city + "&appid=" + apiKey + "&units=metric";
  http.begin(client, url);

  int httpCode = http.GET();
  if (httpCode > 0) {
    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.println("Weather Data:");
      Serial.println(payload);

      StaticJsonDocument<1024> doc;
      DeserializationError error = deserializeJson(doc, payload);

      if (!error) {
        float temp = doc["main"]["temp"].as<float>();
        String weather = doc["weather"][0]["main"].as<String>();

        Serial.print("Temperature: ");
        Serial.print(temp);
        Serial.println(" C");
        Serial.print("Weather description: ");
        Serial.println(weather);

        if (weather.indexOf("Rain") >= 0 || weather.indexOf("Drizzle") >= 0 || weather.indexOf("Thunderstorm") >= 0) {
          condition = 1; //rain
        } else if (temp < 20) {
          condition = 2; //cold
        } else if (temp > 30) {
          condition = 3;  //hot
        } else {
          condition = 4;  //perfect
        }

      } else {
        Serial.print("JSON Parsing failed: ");
        Serial.println(error.c_str());
        condition = 0;
      }
    }
  } else {
    Serial.print("HTTP GET failed, error: ");
    Serial.println(http.errorToString(httpCode).c_str());
    condition = 0;
  }

  http.end();
}

void showSmoothGradient(CRGB gradientColors[], uint16_t durationMillis) {
  static uint32_t lastUpdateTime = 0;
  static float progress = 0.0;
  static uint16_t stepDelay = 100;  // Time between updates in milliseconds
  static uint16_t totalSteps = durationMillis / stepDelay;

  if (millis() - lastUpdateTime >= stepDelay) {
    lastUpdateTime = millis();
    progress += 1.0 / totalSteps;
    if (progress > 1.0) progress = 0.0;

    for (int i = 0; i < NUM_LEDS; i++) {
      float shift = fmod(progress + (float)i / NUM_LEDS, 1.0);  // Wrap-around for cyclic blending
      int startIdx = floor(shift * NUM_LEDS);
      int endIdx = (startIdx + 1) % NUM_LEDS;
      float blendFactor = (shift * NUM_LEDS) - startIdx;

      // Use lerp8by8() for smooth interpolation
      leds[i] = CRGB(
        lerp8by8(gradientColors[startIdx].r, gradientColors[endIdx].r, blendFactor * 255),
        lerp8by8(gradientColors[startIdx].g, gradientColors[endIdx].g, blendFactor * 255),
        lerp8by8(gradientColors[startIdx].b, gradientColors[endIdx].b, blendFactor * 255)
      );
    }
    FastLED.show();
  }
}

void updateBrightness(int currentTime) {
  if (currentTime >= 1800) { // After 6:00 PM
    // Constant low brightness after sunset
    FastLED.setBrightness(LOW_BRIGHTNESS);
  }
  else {
    // Full brightness during the day
    FastLED.setBrightness(MAX_BRIGHTNESS);
  }
}

void setup() {
  Serial.begin(115200);

  // Connect to WiFi
  Serial.println("\nConnecting to WiFi...");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected!");

  // Initialize FastLED
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(MAX_BRIGHTNESS);
  FastLED.clear();

  // Initialize the NTP time client
  timeClient.begin();
  timeClient.update();
  
  int currentHour = timeClient.getHours();
  int currentMinute = timeClient.getMinutes();
  int currentTime = currentHour * 100 + currentMinute;

  // Fetch weather data immediately
  updateBrightness(currentTime);
  fetchWeather();
  lastCheck = millis();
}

void loop() {
  // Update NTP client time
  timeClient.update();
  
  int currentHour = timeClient.getHours();
  int currentMinute = timeClient.getMinutes();
  int currentTime = currentHour * 100 + currentMinute;

  // slight sleep after 10:30pm
  if (currentTime >= 2230 || currentTime < 800) {
    // Calculate sleep duration until 8:00 AM
    uint64_t sleepDuration;
    
    if (currentTime >= 2230) {
      // If it's after 22:30, calculate time until 8:00 next day
      int hoursToSleep = (24 - currentHour) + 8;
      int minutesToSleep = (60 - currentMinute);
      sleepDuration = (hoursToSleep * 3600 + minutesToSleep * 60) * 1000000ULL;
    } else {
      // If it's before 8:00, calculate time until 8:00 same day
      int minutesToSleep = (8 * 60) - (currentHour * 60 + currentMinute);
      sleepDuration = minutesToSleep * 60 * 1000000ULL;
    }

    Serial.println("Entering light sleep");
    Serial.print("Sleep duration (minutes): ");
    Serial.println(sleepDuration / 60000000ULL);
    delay(200); // Allow serial to complete transmission

    // Prepare the system for sleep
    FastLED.clear();
    FastLED.show();

    // Turn off WiFi before sleep
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    WiFi.forceSleepBegin();
    delay(10); // Needed to make sure sleep mode is entered

    // Enter light sleep
    delay(sleepDuration/1000); //delay in milliseconds


    // Wake up and reinitialize WiFi
    Serial.println("Waking up from light sleep");

    WiFi.forceSleepWake();
    delay(100); // Give it time to wake up

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println("\nConnected to WiFi");

    // Force Check weather immediately after wake up
    lastCheck = millis() - checkInterval;
  } 

  // Continue running the gradient animation or weather fetching logic
  else {
      // Normal daytime operation
      if (millis() - lastCheck >= checkInterval) {
      updateBrightness(currentTime);
      fetchWeather();
      lastCheck = millis();
    }

    switch (condition) {
      case 1:
        showSmoothGradient(blueToPink, 15000);
        break;
      case 2:
        showSmoothGradient(icyBlueGradient5, 15000);
        break;
      case 3:
        showSmoothGradient(yellowToRed, 15000);
        break;
      case 4:
        showSmoothGradient(deepGreenToYellow, 15000);
        break;
      default:
        FastLED.clear();
        FastLED.show();
        break;
    }
  }
}

Credits

Makestreme
9 projects • 10 followers
I make DIY projects that are fun and interesting! An engineer by profession :) youtube.com/@makestreme
Contact

Comments

Please log in or sign up to comment.