WeatherCube

Hi Guys,

I have been working on implementing a previous project I had worked on into Home Assistant and thought I’d share what I have done if any one is interested or could provide any suggestions.

A few years ago I purchased a 4X4X4 RGB LED Arduino kit and a raspberry pi and took to learning how to program the cube and pi to show me the current weather reports. I thought it would be a fun birthday present for my girlfriend at the time.

It was a long process, and for the most part it worked as intended, but was my first ever attempt at coding anything let alone soldering!

Fast forward a couple of years, girlfriend turned into Fiance and we are in the process of building our first home. I started looking into home automation and found my way to Home Assistant.

Me being me and always striving to improve decided that it would be a great idea to incorporate the weather cube into my new found home automation obsession.

My goal was to get home assistant to tell the Arduino board what program to run based off the weather, and if the TV went on, to fade the cube out to black (its quite bright when on).

Again, having not ever done any sort of programming other than the original cube scripts that I cobbled together with the help of Google, I needed a bit of a hand from these forums.
I found bits and pieces from various posts and adjusted to suit. I am proud to say as of last night in the early hours of the morning I finally got it working how I like it. I thought I would share my scripts etc. below, maybe someone could critique what I have done and suggest ways to improve.

Automations:

  • Turns Cube off when TV is on

  • Turns Cube on when TV is off

  • Selects the weather on a input selector

    • alias: ‘TV Stop Weather Cube’
      trigger:
      platform: state
      entity_id: media_player.lg_webos_smart_tv
      state: ‘playing’
      action:
      - service: script.turn_off
      data:
      entity_id: script.weatherloop

    • alias: ‘TV Start Weather Cube’
      trigger:
      platform: state
      entity_id: media_player.lg_webos_smart_tv
      state: ‘off’
      action:
      - service: script.turn_on
      data:
      entity_id: script.weatherloop

    • alias: Weather change
      trigger:
      platform: state
      entity_id: sensor.pws_weather
      action:
      service: input_select.select_option
      entity_id: input_select.weather
      data_template:
      option: >
      {% if is_state( ‘sensor.pws_weather’, ‘Rain’ ) %}
      cube_rain
      {%-elif is_state( ‘sensor.pws_weather’, ‘Clear’ ) %}
      {% if is_state ( ‘sun.sun’, ‘below_horizon’ ) %}
      cube_clearnight
      {% else %}
      cube_clear
      {% endif %}
      {%-elif is_state( ‘sensor.pws_weather’, ‘Mostly Cloudy’ ) %}
      {% if is_state ( ‘sun.sun’, ‘below_horizon’ ) %}
      cube_clearnight
      {% else %}
      cube_cloud
      {% endif %}
      {%-elif is_state( ‘sensor.pws_weather’, ‘Partly Cloudy’ ) %}
      {% if is_state ( ‘sun.sun’, ‘below_horizon’ ) %}
      cube_clearnight
      {% else %}
      cube_cloud
      {% endif %}
      {%-elif is_state( ‘sensor.pws_weather’, ‘Overcast’ ) %}
      {% if is_state ( ‘sun.sun’, ‘below_horizon’ ) %}
      cube_clearnight
      {% else %}
      cube_cloud
      {% endif %}
      {% endif %}

Scripts:

  • Two scripts, one to set what serial command to send and one to loop back to the first

  • What serial command to be sent to cube

    weatherset:
    alias: “Set Weather Cube”
    sequence:
    - alias: “Set Weather”
    service: script.turn_on
    data_template:
    entity_id: >
    {% if is_state (‘input_select.weather’, ‘cube_clear’) %}
    script.cube_clear
    {% elif is_state (‘input_select.weather’, ‘cube_cloud’) %}
    script.cube_cloud
    {% elif is_state (‘input_select.weather’, ‘cube_mostcloud’) %}
    script.cube_mostcloud
    {% elif is_state (‘input_select.weather’, ‘cube_rain’) %}
    script.cube_rain
    {% elif is_state (‘input_select.weather’, ‘cube_wind’) %}
    script.cube_wind
    {% elif is_state (‘input_select.weather’, ‘cube_nightcloud’) %}
    script.cube_nightcloud
    {% elif is_state (‘input_select.weather’, ‘cube_clearnight’) %}
    script.cube_clearnight
    {% endif %}
    - delay:
    seconds: 30
    - alias: “Loop weather”
    service: script.turn_on
    data:
    entity_id: script.weatherloop
    - alias: “Stop weather”
    service: script.turn_off
    data:
    entity_id: script.weatherset

    weatherloop:
    alias: “Loop Weather Cube”
    sequence:
    - delay:
    seconds: 1
    - service: script.turn_on
    data:
    entity_id: script.weatherset

    ######## WEATHER CHANGES ########

    cube_clear:
    alias: “Set cube_clear”
    sequence:
    - service: shell_command.cube_clear
    cube_cloud:
    alias: “Set cube_cloud”
    sequence:
    - service: shell_command.cube_cloud
    cube_mostcloud:
    alias: “Set cube_mostcloud”
    sequence:
    - service: shell_command.cube_mostcloud
    cube_rain:
    alias: “Set cube_rain”
    sequence:
    - service: shell_command.cube_rain
    cube_wind:
    alias: “Set cube_wind”
    sequence:
    - service: shell_command.cube_wind
    cube_nightcloud:
    alias: “Set cube_nightcloud”
    sequence:
    - service: shell_command.cube_nightcloud
    cube_clearnight:
    alias: “Set cube_clearnight”
    sequence:
    - service: shell_command.cube_clearnight

Shell commands:

  • Send a number which represents a program to the cube connected via USB

    shell_command:
    cube_clear: /home/pi/cube_clear.sh
    cube_cloud: /home/pi/cube_cloud.sh
    cube_mostcloud: /home/pi/cube_mostcloud.sh
    cube_rain: /home/pi/cube_rain.sh
    cube_wind: /home/pi/cube_wind.sh
    cube_nightcloud: /home/pi/cube_nightcloud.sh
    cube_clearnight: /home/pi/cube_clearnight.sh
    cube_blank: /home/pi/cube_blank.sh

Serial command:

  • Example of what the shell command calls up

    #!/bin/bash

    Cube cloud

    echo -e -n “4” > /dev/ttyACM0
    sleep 30
    echo -e -n “3” > /dev/ttyACM0

And lastly the Arduino code that I cobbled together

#include "SPI.h"
#include "Cube.h"
#define LIGHTNINGINTERVAL 40 
#define RAINDROPDELAY 100    
#define SHOWCLOUDS 1         
#define DELAY 150
byte drop1XPos;
byte drop1YPos;
byte drop1ZPos = 2;
byte drop2XPos;
byte drop2YPos;
byte drop2ZPos = 0;
word timer=800;
byte rr;
byte gg;
byte bb;
const long PROGMEM runtime = 30000;

Cube cube;

void setup(void) {
   cube.begin(0, 115200); 
}

byte global_angle1;
byte global_angle2;
byte global_angle3;

void colour(byte x,byte y, byte a)
{
  byte r = 0, g = 0, b = 0;
  byte angle;

  angle = global_angle1/2 + a;
  while(angle > 95) angle -= 95;
  if(angle < 32)      r = angle*4;
  else if(angle < 64) r = (63-angle) * 4;
  
  angle = global_angle2/2 + a;
  while(angle > 95) angle -= 95;
  if(angle < 32)      g = angle*4;
  else if(angle < 64) g = (63-angle) * 4;
  
  angle = global_angle3/2 + a;
  while(angle > 95) angle -= 95;
  if(angle < 32)      b = angle*4;
  else if(angle < 64) b = (63-angle) * 4;
  
  cube.set(x, y, 0, RGB(r,   g,   b));
  cube.set(x, y, 1, RGB(r/2, g/2, b/2));
  cube.set(x, y, 2, RGB(r/4, g/4, b/4));
  cube.set(x, y, 3, RGB(r/8, g/8, b/8));
}

void dblcube(void) {

  rr = random(0, 2) * 255;
  gg = random(0, 2) * 255;
  bb = random(0, 2) * 255;


  cube.setplane(X,0,RGB(rr, gg, bb));
  cube.setplane(X,3,RGB(rr, gg, bb));
  cube.setplane(Y,0,RGB(rr, gg, bb));
  cube.setplane(Y,3,RGB(rr, gg, bb));
  cube.setplane(Z,0,RGB(rr, gg, bb));
  cube.setplane(Z,3,RGB(rr, gg, bb));

  rr = random(0, 2) * 255;
  gg = random(0, 2) * 255;
  bb = random(0, 2) * 255;

  cube.set( 1,1,1,RGB(rr, gg, bb));
  cube.set( 2,1,1,RGB(rr, gg, bb));
  cube.set( 1,2,1,RGB(rr, gg, bb));
  cube.set( 2,2,1,RGB(rr, gg, bb));
  cube.set( 1,1,2,RGB(rr, gg, bb));
  cube.set( 2,1,2,RGB(rr, gg, bb));
  cube.set( 1,2,2,RGB(rr, gg, bb));
  cube.set( 2,2,2,RGB(rr, gg, bb));
  delay(timer);

  rr = random(0, 2) * 255;
  gg = random(0, 2) * 255;
  bb = random(0, 2) * 255;


  cube.setplane(X,0,RGB(rr, gg, bb));
  cube.setplane(X,3,RGB(rr, gg, bb));
  cube.setplane(Y,0,RGB(rr, gg, bb));
  cube.setplane(Y,3,RGB(rr, gg, bb));
  cube.setplane(Z,0,RGB(rr, gg, bb));
  cube.setplane(Z,3,RGB(rr, gg, bb));

  rr = random(0, 2) * 255;
  gg = random(0, 2) * 255;
  bb = random(0, 2) * 255;


  cube.set( 1,1,1,RGB(rr, gg, bb));
  cube.set( 2,1,1,RGB(rr, gg, bb));
  cube.set( 1,2,1,RGB(rr, gg, bb));
  cube.set( 2,2,1,RGB(rr, gg, bb));
  cube.set( 1,1,2,RGB(rr, gg, bb));
  cube.set( 2,1,2,RGB(rr, gg, bb));
  cube.set( 1,2,2,RGB(rr, gg, bb));
  cube.set( 2,2,2,RGB(rr, gg, bb));
  delay(timer);

}

void lasers(void) {
  byte x = 0;
  byte y = 0;
  byte step_x = 1;
  byte step_y = 0;
  for (byte i = 0; i < 24; i++) {
    cube.all(BLACK);
    if (i <= 12) {
      cube.line(0, 3, 0, y, 3-x, 3, WHITE);
      cube.line(3, 3, 0, 3-x, 3-y, 3, BLUE);
      cube.line(3, 0, 0, 3-y, x, 3, GREEN);
      cube.line(0, 0, 0, x, y, 3, RED);
    } else {
      cube.line(0, 3, 3, y, 3-x, 0, WHITE);
      cube.line(3, 3, 3, 3-x, 3-y, 0, BLUE);
      cube.line(3, 0, 3, 3-y, x, 0, GREEN);
      cube.line(0, 0, 3, x, y, 0, RED);
    }
    if (x == 3 && y == 0) {
      step_x = 0;
      step_y = 1;
    } else if (x == 3 && y == 3) {
      step_x = -1;
      step_y = 0;
    } else if (x == 0 && y == 3) {
      step_x = 0;
      step_y = -1;
    } else if (x == 0 && y == 0) {
      step_x = 1;
      step_y = 0;
    }
    x += step_x;
    y += step_y;
    delay(DELAY);
  }
  cube.all(BLACK);
  x = 0;
  y = 0;
  step_x = 1;
  step_y = 0;
  for (byte i = 0; i < 16; i++) {
    cube.line(x, y, 0, x, y, 3, RED);
    if (x == 3 && y == 0) {
      step_x = 0;
      step_y = 1;
    } else if (x == 3 && y == 3) {
      step_x = -1;
      step_y = 0;
    } else if (x == 0 && y == 3) {
      step_x = 0;
      step_y = -1;
    } else if (x == 0 && y == 1) {
      step_x = 1;
      step_y = 0;
    } else if (x == 2 && y == 1) {
      step_x = 0;
      step_y = 1;
    } else if (x == 2 && y == 2) {
      step_x = -1;
      step_y = 0;
    }
    x += step_x;
    y += step_y;
    delay(DELAY);
  }
}

void CloudyNight()
{
  // Color the sky
  cube.all(BLACK);
  // Color the clouds
  cube.setplane(Z, 3, RGB(0x74, 0x74, 0x74));
  cube.setplane(Z, 2, RGB(0x74, 0x74, 0x74));
  // Color the ground
  cube.setplane(Z, 0, RGB(0x00, 0x43, 0x00));
  // Add back a few black sky bits
  cube.set(0, 0, 3, BLACK);
  cube.set(1, 1, 3, BLACK);
  cube.set(0, 1, 2, BLACK);
  cube.set(2, 0, 3, BLACK);
  cube.set(2, 1, 2, BLACK);
  cube.set(2, 2, 3, BLACK);
  cube.set(2, 3, 3, BLACK);
  cube.set(3, 2, 2, BLACK);
  cube.set(0, 3, 2, BLACK);
  cube.set(0, 2, 2, BLACK);
  cube.set(1, 2, 3, BLACK);
}
void rainbow(void) {
  
  colour(0,0, 0);
  colour(0,1, 8);
  colour(0,2,16);
  colour(0,3,24);
  colour(1,3,32);
  colour(2,3,40);
  colour(3,3,48);
  colour(3,2,56);
  colour(3,1,64);
  colour(3,0,72);
  colour(2,0,80);
  colour(1,0,88);

  colour(1,1, 0);
  colour(1,2, 24);
  colour(2,2, 48);
  colour(2,1, 72);
  
  if(global_angle1 > 191-3)
    global_angle1 -= 192-3;
  else
    global_angle1 += 3;

  if(global_angle2 > 191-4)
    global_angle2 -= 192-4;
  else
    global_angle2 += 3;
    
  if(global_angle3 > 191-5)
    global_angle3 -= 192-5;
  else
    global_angle3 += 5;
    
  delay(50);
}

void rain() 
{
  if(drop1ZPos == 4)
  {
    drop1XPos = random(4);
    drop1YPos = random(4);
  }

  if(drop2ZPos == 4)
  {
    drop2XPos = random(4);
    drop2YPos = random(4);
  }

  cube.all(BLACK);
  cube.setplane(Z, 3, WHITE);

  if(drop1ZPos > 0)
  {
    drop1ZPos--;
    cube.set(drop1XPos, drop1YPos, drop1ZPos, BLUE);
  } else {
    drop1ZPos = 4;
  }

  if(drop2ZPos > 0)
  {
    drop2ZPos--;
    cube.set(drop2XPos, drop2YPos, drop2ZPos, BLUE);
  } else {
    drop2ZPos = 4;
  }

  delay(RAINDROPDELAY);
}

void clouds()
{
  cube.all(RGB(0x00, 0x00, 0x22));
  cube.setplane(Z, 3, WHITE);
  cube.setplane(Z, 0, GREEN);
  cube.set(0, 3, 3, RGB( 0xff, 0xff, 0x00));
  cube.set(0, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 2, RGB( 0x22, 0x22, 0x00));
    
  }


void sun()
{
  cube.all(RGB(0x00, 0x00, 0x22));
  cube.setplane(Z, 0, GREEN);
  cube.set(0, 3, 3, RGB( 0xff, 0xff, 0x00));
  cube.set(0, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 2, RGB( 0x22, 0x22, 0x00));
}

void partlycloudy()
{
  // Color the sky
  cube.all(RGB(0x2f, 0x5e, 0x7f));
  // Color the clouds
  cube.setplane(Z, 3, WHITE);
  cube.setplane(Z, 2, WHITE);
  // Color the ground
  cube.setplane(Z, 0, GREEN);
  // Add back a few blue sky bits
  cube.set(0, 0, 3, RGB(0x2f, 0x5e, 0x7f));
  cube.set(1, 1, 3, RGB(0x2f, 0x5e, 0x7f));
  cube.set(0, 1, 2, RGB(0x2f, 0x5e, 0x7f));
  cube.set(2, 0, 3, RGB(0x2f, 0x5e, 0x7f));
  cube.set(2, 1, 2, RGB(0x2f, 0x5e, 0x7f));
  cube.set(2, 2, 3, RGB(0x2f, 0x5e, 0x7f));
  cube.set(2, 3, 3, RGB(0x2f, 0x5e, 0x7f));
  cube.set(3, 2, 2, RGB(0x2f, 0x5e, 0x7f));
  // Color the sun
  cube.set(0, 3, 3, RGB( 0xff, 0xff, 0x00));
  cube.set(0, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(0, 2, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 3, 2, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 3, RGB( 0x22, 0x22, 0x00));
  cube.set(1, 2, 2, RGB( 0x22, 0x22, 0x00));
}

void wind()
{
   for (int j = 0; j < 20; j++) 
   {
    cube.all(HSBToRGB(random(30,37),random(181,255),random(35,70)));
    cube.setplane(Z, 0, GREEN);
    delay(115);
   }
}

/*
 * Sparkle the cube, white flickering LEDs.
 */
void clearnight()
{
  cube.all(BLACK);
  byte x[20], y[20], z[20];

  for (int j = 0; j < 20; j++)
  {
    x[j] = random(0, 4);
    y[j] = random(0, 4);
    z[j] = random(0, 4);
  }
  for(int k = 0; k < 50; k++)
  {
    for (int j = 0; j < 20; j++) // twinkle the stars
    {
      cube.set(x[j], y[j], z[j], HSBToRGB(255,0,random(115,255)));
    }
    delay(200);
  }   
  for (int j = 0; j < 20; j++)
  {
    cube.set(x[j], y[j], z[j], BLACK);
  }
}

void fadetoblack()
{
  cube.all(BLACK);
}

 
void loop(void) {
  unsigned long starttime = millis();
  unsigned long endtime = starttime;
  if (Serial.available() >0) {
    int sky = Serial.read();

    switch (sky) {
       case '1': 
       while((endtime - starttime) < runtime)
      {
        rain();
        endtime = millis();
        
      }
       break;
       case '2':
        sun();
        break;
       case '3':
        fadetoblack();
        break; 
       case '4':
        clouds();
        break;
        case '5':
        partlycloudy();
        break;
        case '6':
        while((endtime - starttime) < runtime)
      {
         
        wind();
        endtime = millis();
      }
        break;
        case '7':
        while((endtime - starttime) < runtime)
      {
        clearnight();
        endtime = millis();
      }
        break;
        case '8':
        while((endtime - starttime) < runtime)
      {
        rainbow();
        endtime = millis();
      }
        break;
        case 'l':
        while((endtime - starttime) < runtime)
      {
        lasers();
        endtime = millis();
      }
        break;
        case '9':
        while((endtime - starttime) < runtime)
      {
        CloudyNight();
        endtime = millis();
      }
        break;
        case '0':
        while((endtime - starttime) < runtime)
      {
        dblcube();
        endtime = millis();
      }
        break;
       default:
         while((endtime - starttime) < runtime)
      {
        rainbow();
        endtime = millis();
      }
        break; 
    }
  }
}

rgb_t HSBToRGB(unsigned int inHue, unsigned int inSaturation, unsigned int inBrightness)
{
  rgb_t retcol;
  if (inSaturation == 0) 
  {
    // achromatic (grey)
    retcol = RGB(inBrightness, inBrightness, inBrightness);
  } 
  else 
  {
    unsigned int scaledHue = (inHue * 6);
    unsigned int sector = scaledHue >> 8; // sector 0 to 5 around the color wheel
    unsigned int offsetInSector = scaledHue - (sector << 8);   // position within the sector
    unsigned int p = (inBrightness * ( 255 - inSaturation )) >> 8;
    unsigned int q = (inBrightness * ( 255 - ((inSaturation * offsetInSector) >> 8) )) >> 8;
    unsigned int t = (inBrightness * ( 255 - ((inSaturation * ( 255 - offsetInSector )) >> 8) )) >> 8;

    switch(sector) 
    {
    case 0:
      retcol = RGB(inBrightness, t, p);
      break;
    case 1:
      retcol = RGB(q, inBrightness, p);
      break;
    case 2:
      retcol = RGB(p, inBrightness, t);
      break;
    case 3:
      retcol = RGB(p, q, inBrightness);
      break;
    case 4:
      retcol = RGB(t, p, inBrightness);
      break;
    default:    // case 5:
      retcol = RGB(inBrightness, p, q);
      break;
    }
  }
  return retcol;
}

Thats it! I know its very messy, and may be hard to follow, but I am so proud of what I have done in just a few short weekends.

4 Likes

Would be neat to see this in a video!

Ok, tried to do this but it didn’t come out very well. First time doing a YouTube video!

I went through the different states that it does, being in Wellington NZ we don’t exciting weather like snow etc.
Just rain, clouds, Sun, stars.

1 Like

That’s really neat!

The rain looks amazing! This is such a cool idea. I would wrap it in diffusion material though, like a frosted plastic cube. I think it would look awesome. Thanks for providing the link to the kit; it actually looks like something I could build (for once).

I’m giving this some serious thought after I finish my multisensors!

multi sensors still waiting on leds? I received mine and guess I didnt read the packaging because I received a bunch

Just got them but haven’t sat down to put the sensor together yet. Hopefully soon as I need to take some pics for the thread discussing them. (I promised… :flushed:)