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.weathersetweatherloop:
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.