Arduino Projection Clock
So I have been a fan of projection clocks at least 20 years (yeah... I'm quite old, no need to remind me ... :) When I took fancy in my later years for Arduino, I stumbled to Arduino project where this (https://hackaday.com/2018/05/05/simple-home-built-projection-clock-projects-time/) Richard guy had built Him self a nice projection clock...and from there the idea just kept on smoldering until...
It was clear from the start that I did not want to follow fully Richards footsteps , but more to build on his project and make it look mine. And base was to get it working with Node-Red. Node-Red is my chosen platform to integrate quite a lot of IoT sensors around my house.
So Idea and base was clear but the first problem was really to get the parts for the build. The Arduino platform uses NodeMCU as brains, The CCTV lens, 1-3W High-power lens were fairly easy to get from China, but the display proved to be a complex one to obtain. The originals project display was nowhere to be bought and I was getting quite desperate at one point and I actually bought quite several different models...(which I could not get to work) until finally I landed with the correct one (details later; what I used and where I bought it). You have to remember that ordering these things from china usually take 4-6 weeks these Covid-19 times so there was quite a lot of waiting on this project.
So about a month ago came the day that all parts were finally here and the hands on time finally began.
Here's the part list. On top of the NodeMCU everything else is from Aliexpress except the display that I finally got from BuyDisplay.com.
And remember HEATSINK.
So what do you get when you put all these parts together, throw some arduino code and finalize it with parts out of your 3D Printer....
A Projection clock that connects to Wifi / Node Red with MQTT and currently shows; The Time, Out Temperature, IN Temperature and shows the current weather Icon via OpenWeather.com API. Brightness can be adjusted with the MOSFET chip.
So was everything smooth sailing... NO. And here's what I have learned so take a note.
- As said earlier the display was the hardest. You need a negative back light LCD display you can shine light through (project) and these were hard to come buy. I was originally looking for COG screen ( i.e. displays with out boards) but the biggest problem really came that I could not get a proper ZIP/FPC connector (where my not so great soldering skills would not be a problem ) to be used with NodeMCU. My biggest / best idea came when prying one old LCD display from its back light as they are typically connected only with some sort of tape ( like in the final display I used). So easy to use COB display and easy wire connection to NodeMCU.
- HEATSINK. The original project by Richard did not mention anything about the HighPower LED getting HOT. Well you learn by doing... AND EVER NEVER LEAVE YOUR PROTOTYPE WITH OUT SUPERVISON FOR THE FIRST 6HRS...
It did not burn, but my wife really did not like the smell of smoldering PLA.
- On the Arduino Code the issue was the displays UC1609 controller which was not a "standard" one that a lot of Arduino libraries were built. Luckily after understanding that this display does would not work with standard libraries I found the GitHub site where user KiLLAAA had created a Adafruit GFX compatible library, So Kudos to KiLLAAA ( https://github.com/KiLLAAA/LCD_UC1609)
- Finding also the correct Display PINS to connect to NodeMCU required some digging as original UC1609 library was built for UNO board. Look on the code I wrote them down to help you.
- Also something curious also happened with the pins connected to MOSFET. The board would not boot correctly. Only pin where it worked correctly was D8... No idea though why.
- Drawing text / pictures one pixel at the time is nightmare. Use this site to turn your images into byte arrays will simplify your task 1000% faster. https://javl.github.io/image2cpp/
Here's the Arduino Code (I'm not a professional coder...but hey...it works):
#include <Adafruit_GFX.h>
#include <LCD_UC1609.h>
#include <PubSubClient.h>
#include <ESP8266WiFi.h>
float IN;
float OUT;
String TIME;
int PC_PWR = 100;
String PC_WEATHER;
const int ledPin = D8;
const char* ssid = "XXXX";
const char* password = "XXXXX";
const char* mqttServer = "192.168.11.51";
const int mqttPort = 1883;
WiFiClient espClient;
PubSubClient client(espClient);
LCD_UC1609 display(D1, D0, D2); // DC, RST, CS
// NodeMCU SPI Fixed pins ;SDA(MOSI)/pin D7; SCK pin D5
void setup() {
Serial.begin(115200);
display.begin(); // initialize the LCD
display.clearDisplay(0x55); // this version with single param writes directly to the display, 0x55 (B01010101) makes horizontal strips
//delay(1000);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
client.setServer(mqttServer, mqttPort);
client.setCallback(callback);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("ESP8266Client-")) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
client.subscribe("OUT_TEMP8");
delay (500);
//client.loop();
client.subscribe("TEMP5",1);
delay (500);
//client.loop();
client.subscribe("TIME");
delay (500);
//client.loop();
client.subscribe("PC_PWR");
delay (500);
client.subscribe("PC_WEATHER");
delay (500);
}
void callback(char* topic, byte* payload, unsigned int length) {
if (strcmp(topic,"TIME")==0) {
delay (500);
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
char buff_a[length];
for (int a = 0; a < length; a++)
{
// Serial.println((char)payload[i]);
buff_a[a] = (char)payload[a];
}
buff_a[length] = '\0';
String msg_a = String(buff_a);
// val = msg_p.toFloat(); //to float
// val = msg_p.toInt(); // to Int
TIME = msg_a;
Serial.println (TIME);
delay (500);
Serial.println("-----------------------");
Serial.println(WiFi.status());
// delay (3000);
}
if (strcmp(topic,"OUT_TEMP8")==0) {
delay (500);
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
char buff_b[length];
for (int i = 0; i < length; i++)
{
// Serial.println((char)payload[i]);
buff_b[i] = (char)payload[i];
}
buff_b[length] = '\0';
String msg_b = String(buff_b);
// val = msg_p.toFloat(); //to float
// val = msg_p.toInt(); // to Int
OUT = msg_b.toFloat() ;
Serial.println (OUT,1);
delay (500);
Serial.println("-----------------------");
Serial.println(WiFi.status());
// delay (3000);
}
if (strcmp(topic,"TEMP5")==0) {
delay (500);
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
char buff_c[length];
for (int i = 0; i < length; i++)
{
// Serial.println((char)payload[i]);
buff_c[i] = (char)payload[i];
}
buff_c[length] = '\0';
String msg_c = String(buff_c);
// val = msg_p.toFloat(); //to float
// val = msg_p.toInt(); // to Int
IN = msg_c.toFloat() ;
Serial.println (IN,1);
delay (500);
Serial.println("-----------------------");
Serial.println(WiFi.status());
//delay (3000);
}
if (strcmp(topic,"PC_PWR")==0) {
delay (500);
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
char buff_c[length];
for (int i = 0; i < length; i++)
{
// Serial.println((char)payload[i]);
buff_c[i] = (char)payload[i];
}
buff_c[length] = '\0';
String msg_c = String(buff_c);
// val = msg_p.toFloat(); //to float
// val = msg_p.toInt(); // to Int
PC_PWR = msg_c.toInt() ;
Serial.println (PC_PWR);
delay (500);
Serial.println("-----------------------");
Serial.println(WiFi.status());
//delay (3000);
}
if (strcmp(topic,"PC_WEATHER")==0) {
delay (500);
Serial.print("Message arrived in topic: ");
Serial.println(topic);
Serial.print("Message:");
char buff_d[length];
for (int i = 0; i < length; i++)
{
// Serial.println((char)payload[i]);
buff_d[i] = (char)payload[i];
}
buff_d[length] = '\0';
String msg_d = String(buff_d);
// val = msg_p.toFloat(); //to float
// val = msg_p.toInt(); // to Int
PC_WEATHER = msg_d ;
Serial.println (PC_WEATHER);
delay (500);
Serial.println("-----------------------");
Serial.println(WiFi.status());
//delay (3000);
}
}
void text1() {
//display.selectedBuffer = &main_window; // set target buffer object
delay (1000);
display.clearDisplay();
display.setTextSize(1); // not required until other func changes it
display.setTextColor(WHITE);
// DRAW OUT TOP
display.drawPixel(94, 22, WHITE);
display.drawPixel(95, 22, WHITE);
display.drawPixel(96, 22, WHITE);
display.drawPixel(98, 22, WHITE);
display.drawPixel(100, 22, WHITE);
display.drawPixel(102, 22, WHITE);
display.drawPixel(103, 22, WHITE);
display.drawPixel(104, 22, WHITE);
display.drawPixel(94, 23, WHITE);
display.drawPixel(96, 23, WHITE);
display.drawPixel(98, 23, WHITE);
display.drawPixel(100, 23, WHITE);
display.drawPixel(103, 23, WHITE);
display.drawPixel(94, 24, WHITE);
display.drawPixel(96, 24, WHITE);
display.drawPixel(98, 24, WHITE);
display.drawPixel(100, 24, WHITE);
display.drawPixel(103, 24, WHITE);
display.drawPixel(94, 25, WHITE);
display.drawPixel(95, 25, WHITE);
display.drawPixel(96, 25, WHITE);
display.drawPixel(98, 25, WHITE);
display.drawPixel(99, 25, WHITE);
display.drawPixel(100, 25, WHITE);
display.drawPixel(103, 25, WHITE);
// END DRAW
display.setCursor(88, 30);
//display.print(":");
display.print(OUT,1);
//display.print(" C");
display.display();
delay (1000);
}
void text2() {
//display.selectedBuffer = &main_window; // set target buffer object
delay (1000);
display.clearDisplay();
display.setTextSize(1); // not required until other func changes it
display.setTextColor(WHITE);
display.setCursor(84, 28);
display.print(TIME);
display.display();
delay (1000);
}
void text3() {
//display.selectedBuffer = &main_window; // set target buffer object
delay (1000);
display.clearDisplay();
display.setTextSize(1); // not required until other func changes it
display.setTextColor(WHITE);
// DRAW IN
display.drawPixel(98, 22, WHITE);
display.drawPixel(98, 23, WHITE);
display.drawPixel(98, 24, WHITE);
display.drawPixel(98, 25, WHITE);
display.drawPixel(100, 22, WHITE);
display.drawPixel(103, 22, WHITE);
display.drawPixel(100, 23, WHITE);
display.drawPixel(101, 23, WHITE);
display.drawPixel(103, 23, WHITE);
display.drawPixel(100, 24, WHITE);
display.drawPixel(102, 24, WHITE);
display.drawPixel(103, 24, WHITE);
display.drawPixel(100, 25, WHITE);
display.drawPixel(103, 25, WHITE);
// END DRAW
display.setCursor(90, 30);
// display.print("I:");
display.print(IN,1);
//display.print(" C");
display.display();
delay (1000);
}
void text4() {
delay(100);
display.clearDisplay(0); // clear display memory
// create buffer with vertically addressed 16x16 bitmap data
uint8_t clouds [] = {
0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,
0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t sun [] = {
0x00, 0x00, 0x00, 0x08, 0xb0, 0xf0, 0x60, 0x30, 0x38, 0x1e, 0x38, 0x30, 0x60, 0xf0, 0xb0, 0x08,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x87, 0x6f, 0x7d, 0x30, 0x60, 0xe0, 0xc0, 0xe0, 0x60,
0x30, 0x7d, 0x6f, 0x87, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t night_1 [] = {
0x00, 0x00, 0x08, 0x00, 0x00, 0xc0, 0x20, 0x20, 0x90, 0x50, 0x30, 0x10, 0x00, 0x00, 0x00, 0x84,
0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x18, 0x20, 0x20, 0x47, 0x48, 0x50, 0x50,
0x28, 0x18, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t night_2 [] = {
0x00, 0x00, 0x20, 0x00, 0x00, 0xc0, 0x20, 0x20, 0x90, 0x50, 0x30, 0x10, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x07, 0x18, 0x20, 0x20, 0x47, 0x48, 0x50, 0x50,
0x28, 0x18, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t rain_1 [] = {
0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,
0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00,
0x08, 0x02, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t rain_2 [] = {
0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,
0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00,
0x04, 0x01, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t atmosphere [] = {
0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x80, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x16, 0x56, 0x56, 0x56, 0x56, 0x56,
0x56, 0x56, 0x12, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t thunder [] = {
0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf0, 0xf0, 0xf8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,
0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x7f, 0xdf, 0x0f, 0x87, 0x5f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t snow [] = {
0x00, 0xa0, 0x40, 0xe8, 0xb0, 0xe8, 0x40, 0xa0, 0x00, 0x00, 0x22, 0x6b, 0x1c, 0x36, 0x1c, 0x6b,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x59, 0xe2, 0xb0, 0xe0, 0x58, 0x10, 0x00, 0x00,
0x28, 0x10, 0xba, 0x6c, 0xba, 0x10, 0x28, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x01, 0x00,
0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
uint8_t drizzle [] = {
0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xb0, 0xf0, 0xf0, 0xb8, 0xe4, 0xc4, 0xc4, 0xc4, 0x88, 0x10,
0x10, 0x60, 0x40, 0x80, 0x00, 0x0e, 0x3f, 0x3f, 0x3b, 0x6f, 0x7d, 0x7f, 0x7f, 0x77, 0x7f, 0x5d,
0x7f, 0x77, 0x3f, 0x3f, 0x1e, 0x04, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)atmosphere);
if (PC_WEATHER =="Clouds") {
display.displayBuffer(90, 23, 20, 20, (uint8_t*)clouds);
}
if (PC_WEATHER =="Sun") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 24, 20, 20, (uint8_t*)sun);
}
if (PC_WEATHER =="Night") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_1);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_2);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_1);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)night_2);
}
if (PC_WEATHER =="Rain") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_1);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_2);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_1);
delay(500);
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)rain_2);
}
if (PC_WEATHER =="Snow") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)snow);
}
if (PC_WEATHER =="Thunderstorm") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)thunder);
}
if (PC_WEATHER =="Drizzle") {
display.clearDisplay(0); // clear display memory
display.displayBuffer(90, 23, 20, 20, (uint8_t*)drizzle);
}
delay(500);
}
void loop() {
uint8_t bitmapBuffer[1536]; // 64x64 pixels take 512 bytes
////////////////////////////////////////////////////////////////
AdvancedBuffer main_window;
main_window.bitmap = (uint8_t*) &bitmapBuffer;
main_window.width = 192; // 192
main_window.height = 64; // 64
main_window.x = 0; // offset on display
display.selectedBuffer = &main_window;
//////////////////////////////// yes, it can be that simple!
// client.subscribe("OUT_TEMP8");
// client.subscribe("TEMP5",1);
// client.subscribe("TIME");
text1(); //OUT temp
client.loop();
//delay (1000);
text2(); //TIME
client.loop();
//delay (1000);
text3(); //IN temp
//delay (1000);
client.loop();
text4(); //WEATHER
client.loop();
//delay (1000);
analogWrite(ledPin, PC_PWR);
Serial.println(WiFi.status());
client.loop();
Serial.println ("LOOP");
}
Hope this will help you on your project. Have Fun. Soiski71@gmail.com