Word clock on steroids
Nowaday it seems that everyone is building a word clock. The idea is nothing new anymore so I shouldn’t build one too, right? WRONG! Instead of going the easy route and rebuild a proven design I decided to create my own and beef it up a little. It’s definitely more expensive, more work, uses more power, produces more heat and has a lot of other drawbacks but I just need to have it. So far so good.
Planning
In order to do it right the first time some planning had to be done. (Thanks…captain obvious) The layout of other word clocks didn’t really please me so I did my own – in Swiss German! I didn’t want to show only the time but also the day, month and year. To display all that information at once I had to do a 15×15 raster. In the picture below a first test of the layout is shown. It was made using the laser I built in a previous project.
When the date and year is displayed there will be some gaps but I guess it won’t be too much of a problem to read it.
The clock has to be readable from at least 10m so the height of each letter has to be 30mm or more. I made each letter around 40mm just to be sure. Lets calculate the whole size: 15x 40mm + Space + Frame… that’s around 900x900mm. Because the LED spacings of standard WS2812b LED strips is either too big or to small I decided to build PCBs for my word clock. I didn’t want to make the clock too deep so I had to take more LEDs than just one to get an even light distribution over the letter.
Because the beam angle of the LED is around 120° a minimum distance of around 12mm to the front had to be kept. I used 2 8mm MDF sheets so the distance to the front will be around 15mm. I used 4 LEDs per letter that makes a total of 900 LEDs for the whole word clock! I used the LED panels that are described in an earlier blog entry. Each panel has 36 LEDs that makes 25 panels in total.
I used a stencil font because I didn’t want to deal with letters that would fall apart when lasering the front stencil.
Build time
The project took me quite long because it was a filler project with low priority. Here are some pictures of the building process…
PCBs!!!!
I used low melting point solder paste because I don’t have a proper reflow oven, just a normal industrial oven that can’t do temperature profiles. It melts at around 138° so the soldering puts way less stress on the LEDs than with normal solder paste. Only one LED on a panel didn’t work and that was because I placed it the wrong way. I also couldn’t make out a difference in the intensities of the LEDs.
I didn’t have access to an SMT assembly machine and was too stingy to let them produce professionally so I placed every LED and capacitor by hand! Gosh that was a lot of monotonous work.
I put everything in the oven and after 5 minutes @ 160° the PCBs were soldered.
Now to the frame.. In the end I made it 860x860mm because the Y-Axis of the laser supports sheet just a little below 900mm.
I began to paint the frame but I noticed it doesn’t really make a difference in light intensity when the stencil is on. The frame won’t be seen afterwards and because I am lazy I didn’t paint the whole frame.
Light it up!
I used the FastLed3.1 Library from FastLed in combination with an Arduino Due to control the LEDs. Setting the library up and let the demo code run wasn’t a problem at all.
The code is basically a huge look up table.
There are 225 field of 4 LEDs. There are only 3 types of fields (cases). Field 1 consisting of LED 0,1,10,11 field 2 consisting of LED 2,3,8,9 and field 3 consisting of LED 4,5,6,7. All other fields are the base field plus a multiplication of 3 (-> 9 fields per panel).
For example the letter A in field 189 is case 0. 189* 4 LEDs per letter gives us LEDs 757, 758, 759, 760.
This can be easily programmed using modulo and multiplication operations. Then we have 68 different field sequences (= arrays with different lengths). For example “halbi” is sequence 8 using fields 24,25,26,33,34. I wrote a function with an array as input that displays each field of the sequence. Now when we want to display 13:45 = “Es isch viertel vor zwei” the sequences 1,4,6 and 10 have to light up. So in another truth table I hard-coded all the possibilities.
There are 60 / 5 = 12 time steps, 12 hour steps, 31 day steps, 12 month steps and some year steps. Now i programmed a function void displayTime(int year, int month, int day, int hour, int minute, int R, int G, int B){} that gets the value from the DS3231 time module and rounds it to 5 minute time steps and displays it. Maybe it sounds complicated but it’s really not a big deal. So here is the code. At the moment the colors are fading slowly. Note I used the DS1307 Library for the DS3231 because the native library for the DS3231 doesn’t work on the arduino due.
#include "FastLED.h" #include <Wire.h> #include "RTClib.h" #define NUM_LEDS 900 #define DATA_PIN 6 const unsigned char seq1[]={6,0,1,9,10,11,18}; //es isch const unsigned char seq2[]={4,20, 27, 28, 29}; //foif const unsigned char seq3[]={3,36,37,38}; //zäh const unsigned char seq4[]={7,3,4,5,12,13,14,21}; //viertel const unsigned char seq5[]={6,22,23,30,31,32,39}; //zwänzg const unsigned char seq6[]={3,6,7,8}; //vor const unsigned char seq7[]={2,15,16}; //ab const unsigned char seq8[]={5,24,25,26,33,34}; //halbi const unsigned char seq9[]={3,42,43,44}; //eis const unsigned char seq10[]={4,89,88,87,80}; //zwei const unsigned char seq11[]={3,79,78,71}; //drü const unsigned char seq12[]={5,70,69,62,61,60}; //vieri const unsigned char seq13[]={5,86, 85, 84, 77, 76}; //foifi const unsigned char seq14[]={6,75, 68, 67, 66, 59, 58}; //sächsi const unsigned char seq15[]={4,57,50, 49, 48}; //SCMD const unsigned char seq16[]={5,83,82,81,74,73}; //sibni const unsigned char seq17[]={5,72, 65, 64, 63, 56}; //achti const unsigned char seq18[]={4,55,54,47,46}; //nüni const unsigned char seq19[]={4,90, 91, 92, 99}; //zäni const unsigned char seq20[]={4,100,101,108,109}; //elfi const unsigned char seq21[]={6,110,117,118,119,126,127}; //zwölfi const unsigned char seq22[]={6,93,94,95,102,103,104}; //mentig const unsigned char seq23[]={8,111,112,113,120,121,122,129,130}; //zischtig const unsigned char seq24[]={8,96,97,98,105,106,107,114,115}; //mittwuch const unsigned char seq25[]={9,179,178,177,170,169,168,161,160,159}; //dunschtig const unsigned char seq26[]={6,152,151,150,143,142,141}; //fritig const unsigned char seq27[]={9,176,175,174,167,166,165,158,157,156}; //samschtig const unsigned char seq28[]={7,116,123,124,125,132,133,134}; //Sunntig const unsigned char seq29[]={1,149}; //1 const unsigned char seq30[]={1,148}; //2 const unsigned char seq31[]={1,147}; //3 const unsigned char seq32[]={1,140}; //1 const unsigned char seq33[]={1,139}; //2 const unsigned char seq34[]={1,138}; //3 const unsigned char seq35[]={1,173}; //4 const unsigned char seq36[]={1,172}; //5 const unsigned char seq37[]={1,171}; //6 const unsigned char seq38[]={1,164}; //7 const unsigned char seq39[]={1,163}; //8 const unsigned char seq40[]={1,162}; //9 const unsigned char seq41[]={1,155}; //0 const unsigned char seq42[]={3,154,153,146}; //JAN const unsigned char seq43[]={3,145,144,137}; //FEB const unsigned char seq44[]={3,180,181,182}; //MAR const unsigned char seq45[]={3,189,190,191}; //APR const unsigned char seq46[]={3,198,199,200}; //MAI const unsigned char seq47[]={3,207,208,209}; //JUN const unsigned char seq48[]={3,216,217,218}; //JUL const unsigned char seq49[]={3,183,184,185}; //AUG const unsigned char seq50[]={3,192,193,194}; //SEP const unsigned char seq51[]={3,201,202,203}; //OKT const unsigned char seq52[]={3,210,211,212}; //NOV const unsigned char seq53[]={3,219,220,221}; //DEZ const unsigned char seq54[]={1,186}; //2 const unsigned char seq55[]={1,187}; //0 const unsigned char seq56[]={1,188}; //1 const unsigned char seq57[]={1,195}; //2 const unsigned char seq58[]={1,196}; //1 const unsigned char seq59[]={1,197}; //2 const unsigned char seq61[]={1,204}; //3 const unsigned char seq62[]={1,205}; //4 const unsigned char seq63[]={1,206}; //5 const unsigned char seq64[]={1,213}; //6 const unsigned char seq65[]={1,214}; //7 const unsigned char seq66[]={1,215}; //8 const unsigned char seq67[]={1,222}; //9 const unsigned char seq68[]={1,223}; //0 CRGB leds[NUM_LEDS]; RTC_DS1307 rtc; int minu,hr,dy,mon,yr,wkd; void field(const unsigned char NR[], int R, int G, int B){ int arraySize=NR[0]; for(int i=1;i<=arraySize; i++){ int case_nr; int multiplier; case_nr=NR[i]%3; multiplier=(NR[i]-case_nr)/3; multiplier*=12; switch (case_nr){ case 0: leds[1+multiplier-1].setRGB( R, G, B); leds[2+multiplier-1].setRGB( R, G, B); leds[11+multiplier-1].setRGB( R, G, B); leds[12+multiplier-1].setRGB( R, G, B); break; case 1: leds[3+multiplier-1].setRGB( R, G, B); leds[4+multiplier-1].setRGB( R, G, B); leds[9+multiplier-1].setRGB( R, G, B); leds[10+multiplier-1].setRGB( R, G, B); break; case 2: leds[5+multiplier-1].setRGB( R, G, B); leds[6+multiplier-1].setRGB( R, G, B); leds[7+multiplier-1].setRGB( R, G, B); leds[8+multiplier-1].setRGB( R, G, B); break; } } } void displayTime(int year, int month, int day, int twhor, int minute, int wkd, int R, int G, int B){ int rndHr; int hor=twhor; if(hor>12){hor-=12;} for(int i=0;i<=899;i++){ //set all leds to Black, so the old values dont light up; leds[i].setRGB(0,0,0); } field(seq1,R,G,B); //Es isch switch (minute){ case 0: rndHr=hor; break; case 1:case 2:case 3:case 4: case 5: //foif ab field(seq2,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 6:case 7:case 8:case 9: case 10: //zäh ab field(seq3,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 11:case 12:case 13:case 14: case 15: //viertel ab field(seq4,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 16:case 17:case 18:case 19: case 20: //zwänzg ab field(seq5,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 21:case 22:case 23:case 24: case 25: //foif vor halbi field(seq2,R,G,B); field(seq6,R,G,B); field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 26:case 27:case 28:case 29: case 30: //halbi field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 31:case 32:case 33:case 34: case 35: //foif ab halbi field(seq2,R,G,B); field(seq7,R,G,B); field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 36:case 37:case 38:case 39: case 40: //zwänzg vor field(seq5,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 41:case 42:case 43:case 44: case 45: //viertel vor field(seq4,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 46:case 47:case 48:case 49: case 50: //zäh vor field(seq3,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 51:case 52:case 53:case 54: case 55: //foif vor field(seq2,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 56:case 57:case 58:case 59: //stunde +1 rndHr=hor; rndHr=(hor+1)%12; break; } switch (rndHr){ case 1: field(seq9,R,G,B); //eis break; case 2: field(seq10,R,G,B); //zwei break; case 3: field(seq11,R,G,B); //drü break; case 4: field(seq12,R,G,B); //vieri break; case 5: field(seq13,R,G,B); //foifi break; case 6: field(seq14,R,G,B); //sächsi break; case 7: field(seq16,R,G,B); //sibni break; case 8: field(seq17,R,G,B); //achti break; case 9: field(seq18,R,G,B); //nüni break; case 10: field(seq19,R,G,B); //zäni break; case 11: field(seq20,R,G,B); //elfi break; case 0: case 12: field(seq21,R,G,B); //zwölfi break; } switch (day){ case 1: field(seq32,R,G,B); break; case 2: field(seq33,R,G,B); break; case 3: field(seq34,R,G,B); break; case 4: field(seq35,R,G,B); break; case 5: field(seq36,R,G,B); break; case 6: field(seq37,R,G,B); break; case 7: field(seq38,R,G,B); break; case 8: field(seq39,R,G,B); break; case 9: field(seq40,R,G,B); break; case 10: field(seq29,R,G,B); field(seq41,R,G,B); break; case 11: field(seq29,R,G,B); field(seq32,R,G,B); break; case 12: field(seq29,R,G,B); field(seq33,R,G,B); break; case 13: field(seq29,R,G,B); field(seq34,R,G,B); break; case 14: field(seq29,R,G,B); field(seq35,R,G,B); break; case 15: field(seq29,R,G,B); field(seq36,R,G,B); break; case 16: field(seq29,R,G,B); field(seq37,R,G,B); break; case 17: field(seq29,R,G,B); field(seq38,R,G,B); break; case 18: field(seq29,R,G,B); field(seq39,R,G,B); break; case 19: field(seq29,R,G,B); field(seq40,R,G,B); break; case 20: field(seq30,R,G,B); field(seq41,R,G,B); break; case 21: field(seq30,R,G,B); field(seq32,R,G,B); break; case 22: field(seq30,R,G,B); field(seq33,R,G,B); break; case 23: field(seq30,R,G,B); field(seq34,R,G,B); break; case 24: field(seq30,R,G,B); field(seq35,R,G,B); break; case 25: field(seq30,R,G,B); field(seq36,R,G,B); break; case 26: field(seq30,R,G,B); field(seq37,R,G,B); break; case 27: field(seq30,R,G,B); field(seq38,R,G,B); break; case 28: field(seq30,R,G,B); field(seq39,R,G,B); break; case 29: field(seq30,R,G,B); field(seq40,R,G,B); break; case 30: field(seq31,R,G,B); field(seq41,R,G,B); break; case 31: field(seq31,R,G,B); field(seq32,R,G,B); break; } switch (month){ case 1: field(seq42,R,G,B); break; case 2: field(seq43,R,G,B); break; case 3: field(seq44,R,G,B); break; case 4: field(seq45,R,G,B); break; case 5: field(seq46,R,G,B); break; case 6: field(seq47,R,G,B); break; case 7: field(seq48,R,G,B); break; case 8: field(seq49,R,G,B); break; case 9: field(seq50,R,G,B); break; case 10: field(seq51,R,G,B); break; case 11: field(seq52,R,G,B); break; case 12: field(seq53,R,G,B); break; } switch (year){ case 2015: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq63,R,G,B); break; case 2016: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq64,R,G,B); break; case 2017: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq65,R,G,B); break; case 2018: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq66,R,G,B); break; case 2019: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq67,R,G,B); break; case 2020: field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq68,R,G,B); break; } switch (wkd){ case 1: field(seq22,R,G,B); //mentig break; case 2: field(seq23,R,G,B); //zischtig break; case 3: field(seq24,R,G,B); //mittwuch break; case 4: field(seq25,R,G,B); //dunnschtig break; case 5: field(seq26,R,G,B); //fritig break; case 6: field(seq27,R,G,B); //samstig break; case 0: field(seq28,R,G,B); //Sunntig break; } FastLED.show(); } void getTime() { DateTime now = rtc.now(); minu=now.minute(); hr=now.hour(); dy=now.day(); mon=now.month(); yr=now.year(); wkd=now.dayOfTheWeek(); } void setup() { // Start the I2C interface rtc.begin(); //rtc.adjust(DateTime(2015, 12, 25, 20, 51, 30)); //year, month, day, hour, minute, second // Serial.begin(57600); Wire.begin(); FastLED.addLeds<WS2812,DATA_PIN, GRB>(leds, NUM_LEDS); } void loop() { getTime(); float redVal = (exp(sin(millis()/7000.0*PI)) - 0.36787944)*108.0; float grnVal = (exp(cos(millis()/11000.0*PI)) - 0.36787944)*108.0; float bluVal = (exp(sin(millis()/13000.0*PI)) - 0.36787944)*108.0; displayTime(yr, mon, dy, hr, minu, wkd, int(redVal), int(grnVal), int(bluVal)); delay(10); }
In the meantime I moved to a new flat, the word clock has been installed and obviously something had to break. So while moving the solder joint of two of the 900 LEDs broke, which resulted in some weird display patterns. I then took the opportunity to update the code and move from the Arduino DUE platform to an ESP8266 so the microcontroller can be connected to my guest WIFI and get the time from a NTP server. I rewrote parts of the code and included another feature: From 9pm to 8am the LEDs will be in a dim red color. The reason for that feature is the fact that blue light can have a bad influence on the sleep quality. Moreover looking into a bright blue light during a nightly bathroom break is not really pleasant. So here’s the new code:
define FASTLED_ESP8266_RAW_PIN_ORDER include "FastLED.h" define NUM_LEDS 900 define DATA_PIN 4 //= PinD2 ufem Board include include include includefloat redVal = 0; float grnVal = 0; float bluVal = 0;
const char ssid[] = "*********"; // your network SSID (name) const char pass[] = "*********"; // your network password // NTP Servers: static const char ntpServerName[] = "0.ch.pool.ntp.org"; //static const char ntpServerName[] = "time.nist.gov"; //static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov"; //static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov"; //static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov"; const int timeZone = 0; // Central European Time 2, leave 0 if auto changing time zone is activated //const int timeZone = -5; // Eastern Standard Time (USA) //const int timeZone = -4; // Eastern Daylight Time (USA) //const int timeZone = -8; // Pacific Standard Time (USA) //const int timeZone = -7; // Pacific Daylight Time (USA) long previousMillis = 0; // will store last time LED was updated long interval = 1000; WiFiUDP Udp; unsigned int localPort = 8888; // local port to listen for UDP packets time_t getNtpTime(); void digitalClockDisplay(); void printDigits(int digits); void sendNTPpacket(IPAddress &address); //Timezone //Central European Time (Frankfurt, Paris) TimeChangeRule CEST = { "CEST", Last, Sun, Mar, 2, 120 }; //Central European Summer Time TimeChangeRule CET = { "CET ", Last, Sun, Oct, 3, 60 }; //Central European Standard Time Timezone CE(CEST, CET); TimeChangeRule *tcr; //pointer to the time change rule, use to get the TZ abbrev time_t utc, local; CRGB leds[NUM_LEDS]; const unsigned char seq1[]={6,0,1,9,10,11,18}; //es isch const unsigned char seq2[]={4,20, 27, 28, 29}; //foif const unsigned char seq3[]={3,36,37,38}; //zäh const unsigned char seq4[]={7,3,4,5,12,13,14,21}; //viertel const unsigned char seq5[]={6,22,23,30,31,32,39}; //zwänzg const unsigned char seq6[]={3,6,7,8}; //vor const unsigned char seq7[]={2,15,16}; //ab const unsigned char seq8[]={5,24,25,26,33,34}; //halbi const unsigned char seq9[]={3,42,43,44}; //eis const unsigned char seq10[]={4,89,88,87,80}; //zwei const unsigned char seq11[]={3,79,78,71}; //drü const unsigned char seq12[]={5,70,69,62,61,60}; //vieri const unsigned char seq13[]={5,86, 85, 84, 77, 76}; //foifi const unsigned char seq14[]={6,75, 68, 67, 66, 59, 58}; //sächsi const unsigned char seq15[]={4,57,50, 49, 48}; //SCMD const unsigned char seq16[]={5,83,82,81,74,73}; //sibni const unsigned char seq17[]={5,72, 65, 64, 63, 56}; //achti const unsigned char seq18[]={4,55,54,47,46}; //nüni const unsigned char seq19[]={4,90, 91, 92, 99}; //zäni const unsigned char seq20[]={4,100,101,108,109}; //elfi const unsigned char seq21[]={6,110,117,118,119,126,127}; //zwölfi const unsigned char seq22[]={6,93,94,95,102,103,104}; //mentig const unsigned char seq23[]={8,111,112,113,120,121,122,129,130}; //zischtig const unsigned char seq24[]={8,96,97,98,105,106,107,114,115}; //mittwuch const unsigned char seq25[]={9,179,178,177,170,169,168,161,160,159}; //dunschtig const unsigned char seq26[]={6,152,151,150,143,142,141}; //fritig const unsigned char seq27[]={9,176,175,174,167,166,165,158,157,156}; //samschtig const unsigned char seq28[]={7,116,123,124,125,132,133,134}; //Sunntig const unsigned char seq29[]={1,149}; //1 const unsigned char seq30[]={1,148}; //2 const unsigned char seq31[]={1,147}; //3 const unsigned char seq32[]={1,140}; //1 const unsigned char seq33[]={1,139}; //2 const unsigned char seq34[]={1,138}; //3 const unsigned char seq35[]={1,173}; //4 const unsigned char seq36[]={1,172}; //5 const unsigned char seq37[]={1,171}; //6 const unsigned char seq38[]={1,164}; //7 const unsigned char seq39[]={1,163}; //8 const unsigned char seq40[]={1,162}; //9 const unsigned char seq41[]={1,155}; //0 const unsigned char seq42[]={3,154,153,146}; //JAN const unsigned char seq43[]={3,145,144,137}; //FEB const unsigned char seq44[]={3,180,181,182}; //MAR const unsigned char seq45[]={3,189,190,191}; //APR const unsigned char seq46[]={3,198,199,200}; //MAI const unsigned char seq47[]={3,207,208,209}; //JUN const unsigned char seq48[]={3,216,217,218}; //JUL const unsigned char seq49[]={3,183,184,185}; //AUG const unsigned char seq50[]={3,192,193,194}; //SEP const unsigned char seq51[]={3,201,202,203}; //OKT const unsigned char seq52[]={3,210,211,212}; //NOV const unsigned char seq53[]={3,219,220,221}; //DEZ const unsigned char seq54[]={1,186}; //2 const unsigned char seq55[]={1,187}; //0 const unsigned char seq56[]={1,188}; //1 const unsigned char seq57[]={1,195}; //2 const unsigned char seq58[]={1,196}; //1 const unsigned char seq59[]={1,197}; //2 const unsigned char seq61[]={1,204}; //3 const unsigned char seq62[]={1,205}; //4 const unsigned char seq63[]={1,206}; //5 const unsigned char seq64[]={1,213}; //6 const unsigned char seq65[]={1,214}; //7 const unsigned char seq66[]={1,215}; //8 const unsigned char seq67[]={1,222}; //9 const unsigned char seq68[]={1,223}; //0 falsch auf schablone, sollte 0 sein ist aber N //RTC_DS1307 rtc; int minu,hr,dy,mon,yr,wkd; void field(const unsigned char NR[], int R, int G, int B){ int arraySize=NR[0]; for(int i=1;i<=arraySize; i++){ int case_nr; int multiplier; case_nr=NR[i]%3; multiplier=(NR[i]-case_nr)/3; multiplier*=12; switch (case_nr){case 0: leds[1+multiplier-1].setRGB( R, G, B); leds[2+multiplier-1].setRGB( R, G, B); leds[11+multiplier-1].setRGB( R, G, B); leds[12+multiplier-1].setRGB( R, G, B); break; case 1: leds[3+multiplier-1].setRGB( R, G, B); leds[4+multiplier-1].setRGB( R, G, B); leds[9+multiplier-1].setRGB( R, G, B); leds[10+multiplier-1].setRGB( R, G, B); break; case 2: leds[5+multiplier-1].setRGB( R, G, B); leds[6+multiplier-1].setRGB( R, G, B); leds[7+multiplier-1].setRGB( R, G, B); leds[8+multiplier-1].setRGB( R, G, B); break;
} } } void displayTime(int year, int month, int day, int twhor, int minute, int wkd, int R, int G, int B){ int rndHr; int hor=twhor; if(hor>12){hor-=12;} for(int i=0;i<=899;i++){ //set all leds to Black, so the old values dont light up; leds[i].setRGB(0,0,0); } field(seq1,R,G,B); //Es isch switch (minute){ case 0: rndHr=hor; break; case 1:case 2:case 3:case 4: case 5: //foif ab field(seq2,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 6:case 7:case 8:case 9: case 10: //zäh ab field(seq3,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 11:case 12:case 13:case 14: case 15: //viertel ab field(seq4,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 16:case 17:case 18:case 19: case 20: //zwänzg ab field(seq5,R,G,B); field(seq7,R,G,B); rndHr=hor; break; case 21:case 22:case 23:case 24: case 25: //foif vor halbi field(seq2,R,G,B); field(seq6,R,G,B); field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 26:case 27:case 28:case 29: case 30: //halbi field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 31:case 32:case 33:case 34: case 35: //foif ab halbi field(seq2,R,G,B); field(seq7,R,G,B); field(seq8,R,G,B); rndHr=(hor+1)%12; break; case 36:case 37:case 38:case 39: case 40: //zwänzg vor field(seq5,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 41:case 42:case 43:case 44: case 45: //viertel vor field(seq4,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 46:case 47:case 48:case 49: case 50: //zäh vor field(seq3,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 51:case 52:case 53:case 54: case 55: //foif vor field(seq2,R,G,B); field(seq6,R,G,B); rndHr=(hor+1)%12; break; case 56:case 57:case 58:case 59: //stunde +1 rndHr=hor; rndHr=(hor+1)%12; break; } switch (rndHr){ case 1: field(seq9,R,G,B); //eis break; case 2: field(seq10,R,G,B); //zwei break; case 3: field(seq11,R,G,B); //drü break; case 4: field(seq12,R,G,B); //vieri break; case 5: field(seq13,R,G,B); //foifi break; case 6: field(seq14,R,G,B); //sächsi break; case 7: field(seq16,R,G,B); //sibni break; case 8: field(seq17,R,G,B); //achti break; case 9: field(seq18,R,G,B); //nüni break; case 10: field(seq19,R,G,B); //zäni break; case 11: field(seq20,R,G,B); //elfi break; case 0: case 12: field(seq21,R,G,B); //zwölfi break; } switch (day){ case 1: field(seq32,R,G,B); break; case 2: field(seq33,R,G,B); break; case 3: field(seq34,R,G,B); break; case 4: field(seq35,R,G,B); break; case 5: field(seq36,R,G,B); break; case 6: field(seq37,R,G,B); break; case 7: field(seq38,R,G,B); break; case 8: field(seq39,R,G,B); break; case 9: field(seq40,R,G,B); break; case 10: field(seq29,R,G,B); field(seq41,R,G,B); break; case 11: field(seq29,R,G,B); field(seq32,R,G,B); break; case 12: field(seq29,R,G,B); field(seq33,R,G,B); break; case 13: field(seq29,R,G,B); field(seq34,R,G,B); break; case 14: field(seq29,R,G,B); field(seq35,R,G,B); break; case 15: field(seq29,R,G,B); field(seq36,R,G,B); break; case 16: field(seq29,R,G,B); field(seq37,R,G,B); break; case 17: field(seq29,R,G,B); field(seq38,R,G,B); break; case 18: field(seq29,R,G,B); field(seq39,R,G,B); break; case 19: field(seq29,R,G,B); field(seq40,R,G,B); break; case 20: field(seq30,R,G,B); field(seq41,R,G,B); break; case 21: field(seq30,R,G,B); field(seq32,R,G,B); break; case 22: field(seq30,R,G,B); field(seq33,R,G,B); break; case 23: field(seq30,R,G,B); field(seq34,R,G,B); break; case 24: field(seq30,R,G,B); field(seq35,R,G,B); break; case 25: field(seq30,R,G,B); field(seq36,R,G,B); break; case 26: field(seq30,R,G,B); field(seq37,R,G,B); break; case 27: field(seq30,R,G,B); field(seq38,R,G,B); break; case 28: field(seq30,R,G,B); field(seq39,R,G,B); break; case 29: field(seq30,R,G,B); field(seq40,R,G,B); break; case 30: field(seq31,R,G,B); field(seq41,R,G,B); break; case 31: field(seq31,R,G,B); field(seq32,R,G,B); break; } switch (month){ case 1: field(seq42,R,G,B); break; case 2: field(seq43,R,G,B); break; case 3: field(seq44,R,G,B); break; case 4: field(seq45,R,G,B); break; case 5: field(seq46,R,G,B); break; case 6: field(seq47,R,G,B); break; case 7: field(seq48,R,G,B); break; case 8: field(seq49,R,G,B); break; case 9: field(seq50,R,G,B); break; case 10: field(seq51,R,G,B); break; case 11: field(seq52,R,G,B); break; case 12: field(seq53,R,G,B); break; } switch (year){ case 2015: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq63,R,G,B); break; case 2016: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq64,R,G,B); break; case 2017: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq65,R,G,B); break; case 2018: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq66,R,G,B); break; case 2019: field(seq54,R,G,B); field(seq55,R,G,B); field(seq56,R,G,B); field(seq67,R,G,B); break; case 2020: field(seq54,R,G,B); field(seq55,R,G,B); //field(seq57,R,G,B); //field(seq68,R,G,B); break;case 2021:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq58,R,G,B); break;case 2022:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq59,R,G,B); break;case 2023:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq61,R,G,B); break;case 2024:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq62,R,G,B); break;case 2025:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq63,R,G,B); break;case 2026:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq64,R,G,B); break;case 2027:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq65,R,G,B); break;case 2028:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq66,R,G,B); break;case 2029:
field(seq54,R,G,B); field(seq55,R,G,B); field(seq57,R,G,B); field(seq67,R,G,B); break; } switch (wkd){ case 2: field(seq22,R,G,B); //mentig break; case 3: field(seq23,R,G,B); //zischtig break; case 4: field(seq24,R,G,B); //mittwuch break; case 5: field(seq25,R,G,B); //dunnschtig break; case 6: field(seq26,R,G,B); //fritig break; case 7: field(seq27,R,G,B); //samstig break; case 1: field(seq28,R,G,B); //Sunntig break; } FastLED.show(); } void getTime(time_t t) { minu=minute(t); hr=hour(t); dy=day(t); mon=month(t); yr=year(t); wkd=weekday(t); Serial.println(minu, DEC); Serial.println(hr, DEC); Serial.println(dy, DEC); Serial.println(mon, DEC); Serial.println(yr, DEC); Serial.println(wkd, DEC); } void getTime() { minu=minute(); delay(5); hr=hour(); delay(5); dy=day(); delay(5); mon=month(); delay(5); yr=year(); delay(5); wkd=weekday(); delay(5); //Serial.println(minu, DEC); //Serial.println(hr, DEC); //Serial.println(dy, DEC); //Serial.println(mon, DEC); //Serial.println(yr, DEC); //Serial.println(wkd, DEC); } void setup() { Serial.begin(115200); FastLED.addLeds(leds, NUM_LEDS); leds[0].setRGB(0,255,0); FastLED.show(); //while (!Serial) ; // Needed for Leonardo only delay(250); Serial.println("TimeNTP Example"); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("IP number assigned by DHCP is "); Serial.println(WiFi.localIP()); Serial.println("Starting UDP"); Udp.begin(localPort); Serial.print("Local port: "); Serial.println(Udp.localPort()); Serial.println("waiting for sync"); setSyncProvider(getNtpTime); setSyncInterval(300); } time_t prevDisplay = 0; // when the digital clock was displayed void loop() { if (timeStatus() != timeNotSet) { if (now() != prevDisplay) { //update the display only if time has changed prevDisplay = now(); // digitalClockDisplay(); } } unsigned long currentMillis = millis(); if(currentMillis - previousMillis > interval) { previousMillis = currentMillis; // getTime(); //not auto changing time zone Serial.println("Still alive"); Serial.println(hr);getTime(local); //auto changing time zone local = CE.toLocal(now(), &tcr); //auto changing time zone
// printTime(local); } if(hr>=8 && hr<=21){ //color fade only during the day redVal = (exp(sin(millis()/7000.0PI)) - 0.36787944)108.0; grnVal = (exp(cos(millis()/11000.0PI)) - 0.36787944)108.0; bluVal = (exp(sin(millis()/13000.0PI)) - 0.36787944)108.0; } else{ redVal=64; grnVal=0; bluVal=0; } displayTime(yr, mon, dy, hr, minu, wkd, int(redVal), int(grnVal), int(bluVal)); delay(10); if (WiFi.status() != WL_CONNECTED) { //achtung nur zum testen, falls wifi connection verlorenWiFi.begin(ssid, pass); Udp.begin(localPort); setSyncProvider(getNtpTime); setSyncInterval(300);
} } void digitalClockDisplay() { // digital clock display of the time Serial.print(hour()); printDigits(minute()); printDigits(second()); Serial.print(" "); Serial.print(day()); Serial.print("."); Serial.print(month()); Serial.print("."); Serial.print(year()); Serial.println(); } void printDigits(int digits) { // utility for digital clock display: prints preceding colon and leading 0 Serial.print(":"); if (digits < 10) Serial.print('0'); Serial.print(digits); } /-------- NTP code ----------/ const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { IPAddress ntpServerIP; // NTP server's ip address while (Udp.parsePacket() > 0) ; // discard any previously received packets Serial.println("Transmit NTP Request"); // get a random server from the pool WiFi.hostByName(ntpServerName, ntpServerIP); Serial.print(ntpServerName); Serial.print(": "); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } Serial.println("No NTP Response :-("); return 0; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }
hi there, do you sell also? that would be funny to have it in schwizzerdütsch. if it is not too expensive show me your price 🙂 thanks
No I don’t as it would be way too expensive. I would need to populate the PCB’s professionally which is quite expensive (like 300-400CHF including shipping) for only 36 boards and it also takes a lot of time to assemble. Then the LEDs are around 100USD, PCBs another 150USD, stencil around 100CHF and then the cost of assembly. All in all it would be around 2000CHF. If you think that price is acceptable there is another problem. I won’t be back in Switzerland for some months or even a year so you would have to wait until I return.
Wow, that was a serious undertaking on a DIY project, thank you for also sharing the soruce code 🙂