ESP 8266 - Laboratory Test Tubes Tix Clock

by Juan DiegoP in Circuits > Clocks

1384 Views, 9 Favorites, 0 Comments

ESP 8266 - Laboratory Test Tubes Tix Clock

WhatsApp Image 2021-05-23 at 23.17.40.jpeg

It´s been a long time since the hype for the tix clocks has faded. I started building it at the beginning of 2014! but, 2 kids and moving abroad made this project to fall in the list of priorities.

The idea of a tix clock is to count the dots that are illuminated. Each group illuminates the amount of LEDs corresponding to its digit of the time read in the HH:MM format. I know, life is hard enough to complicate reading the time, but it has color lights!

For this clock I used mostly scraps I had laying around. My wife had some unused lab tubes from some old experiment. She had like, hundreds of them, and I was wondering what could I do before discarding them.

I used to visit the reuse center in Ann Arbor, where you could buy pieces of reclaimed wood, mostly from the trimming or feeling of city trees. It was really fun to visit them because they always had a wide variety of woods in small pieces. So, the dimensions were based in those limiting factors.

Supplies

This is the list of supplies, I added some links in case you want more info about the product.

  • Wood - I used Cherry because it is beautiful and was the one I had. The piece I used was ~10.5" (266mm) x 4 3/8" (111mm) x1.6" (40mm).
  • 32 Lab tubes the tubes I used where very similar to these. But any tube will work, the only thing is that you´ll need to use a different drill bit. 32 is the strictly necessary, but you´ll need more, since some of them will end broken.
  • 32 WS218b LEDs I used these type of LEDs, but any other addressable RGB LED will work. I bought 100 for around 10 US Dollars
  • Wemos D1 mini or any other ESP8266 (or ESP32) board will work, (NodeMCU, etc)
  • Logic level converter
  • jumper wires, conectors and
  • Drill Press
  • Glass cutter
  • Capacitor (100uF or more at 16v or more)
  • 1/2" drill bit
  • Sandpaper 150, 220 and 400 grit.
  • Finishing for wood, I used something I bought at Home Depot or Lowes, It´s called "Formby's Clear Tung Oil" It worked really well.
  • 5v power source

Preparing the Wood

IMG_1195.JPG
0001.jpg
descarga.png

The first thing is to prepare the wood, with my materials I draw in CAD the layout for the holes to be drilled. Attached is the version I used. I added a line that measure 1". If you use this, print and check the scale until that line measures exactly 1".

If you are making one from scratch, I recommend to draw the crossing lines, so when you drill the holes is easier to pinpoint the center.

After I printed the model, I checked the alignment and then I glued with school glue, like Elmer's glue, to my piece of wood. Let it set for a while, because if you start drilling with the glue still wet, you´ll end with a torn and misaligned guide.

Once the glue is set, proceed to drill the holes. Since my tubes where a little less than 1/2", I used a 1/2" drill bit. If you have a different diameter of tube, I would use a 1/16th" bigger drill, or maybe 1/8th". It´s important to mention that probably the diameter will shrink a little when applying the finish, and since we´re working with glass tubes, a smaller diameter will probably end in a broken tube.

Once you are done drilling the holes, you can dampen the paper guide and remove it. Check if the tubes can go through, and start sanding. In my case I started with 150grit then moved to 220grit and went up to 400grit. In my opinion, 220 grit was good enough.

I applied the finish with a rag, let it dry and sand with the 400 grit to remove those little hairs that rise in the wood. after that, cleaned it up and applied a second, third and fourth hand. Follow the recommendations from the manufacturer for curing time, but every layer you add, give it a little more time to cure.

Regarding the sanding, you probably won´t need to sand each layer, just check if you need to get rid of some imperfection, if not, apply the next coat.

Once you have your piece of wood finished you can start adding the test tubes.

Adjusting and Installing the Tubes

IMG_1200.JPG
IMG_1201.JPG
IMG_1202.JPG
IMG_1204.JPG
IMG_1206.JPG
IMG_1218.JPG
IMG_1214.JPG
IMG_1216.JPG

The tubes I had were way much longer than I needed, so I had to cut them. If you find tubes shorter, just skip this step.

****First of all****

Since we´re cutting glass, wear safety glasses and a mask!

I tried several ways to cut the tubes and this one was the only one that worked (kind of).

I put a tape mark were the cut goes, then I put the tubes in the Drill press and made them spin, pushing with a glass cutter ( I tried a rotary tool and worked fine too, but resulted in a higher ratio of broken tubes).

Try to cut the tubes a little shorter than your wood, this way, you´ll fit the LEDs better later.

Once you have the tubes cut to size, you can start putting them.

I used tiny layer of clear silicone sealant (like the one used for caulking). I "painted" with a thin layer the tube (the part that is not exposed, and pushed it from the back. Here you realize the importance of the tolerance you chose. If you opted for a very tight fit, you´ll find yourself pushing to hard, and probably breaking some tubes.

Find a rod or something to push uniformly, so the tube won´t get stuck.

If you measured correctly the tubes will be flush with the wood in the back and protruding a little on the front.

Let it dry for a couple hours and you can start soldering.

Electronics - Wiring the LEDs

IMG_1220.JPG
IMG_1223.JPG
IMG_1219.JPG
IMG_1221.JPG

The wiring is very simple, just solder the 3 cables (VCC - GND and Data Line) joining every WS2812b. I Used solid coper wire to make it easier the soldering and fixing to the wood.

Since the separation is always the same, you can precut the wires and solder to the LEDs. Just respect the direction of the data line. I used red for VCC, black for ground and green for Data Line.

in one of the ends, I soldered a small PCB with 3 pins just to avoid the wiggling in the last LED, and to make easier to attach a back for the clock.

In order to fix the LEDs to the wood I used pieces of aluminum with some small screws.

Wiring the Micro Controller

fritzing_bb.jpg
IMG_1225.JPG
IMG_1227.JPG
FastLED test on tix Clock
IMG_1232.JPG

This is very simple, the connections to the Wemos are as following:

5V ---> Power source +5V

GND ---> Power Source and the black wire from the "LED Snake"

D5 ---> Level Shifter--> Data Line of LED (check the direction of the arrow in your LEDs, you have to connect it to the first one.

That´s all. But there is one thing, the WS2512b are 5V and the WeMos´ data pins are 3.3volts, and as far as I know, they are not 5v tolerant.

Since the WS2812b acts weird when you attach to a 3.3v data line, I used a Level shifter, the only thing you have to do is provide 5v in th "High" Side, 3.3v in the "Low" side and a ground. You can use the Wemos 3.3v pint to provide the lower voltage.

Then you can connect the D5 Pin from the Wemos at one of the pins in the "lower" side, and the data pin from the LED strip to its correspondent pin in the Higher side.

At this point, you can burn the fastled Examples just to show off the colors and check if everything is ok.

The only parameters you need to replace are the data pin (since it´s a wemos you have to write D5) and the number of LEDs (32 in this case) hit upload and it should start displaying colors.

Code

20151221_205604.jpg
WhatsApp Image 2021-05-23 at 23.17.40.jpeg

This is the code, it has a lot of bits from examples from its libraries, is still a work in progress. If you want to point me to some improvements I am ager to know!

At this point the code only grabs the time from a NTP server and displays it as the numbers in the Led strip. It randomize the position of the dots every 30 seconds

In the future I want to change the colors randomly every 2 or 3 minutes, but I couldn´t find a way to make the colors random, and at the same time different among them. If I (or someone else) come with a solution I´ll update here.

You´ll need the following Libraries:

FastLED (from libray manager at Arduino IDE)

NTPClient (from libray manager at Arduino IDE)

WifiManager (from libray manager at Arduino IDE)

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <FastLED.h>
#include <Ticker.h>
Ticker ticker;

void tick()
{
  //toggle state
  int state = digitalRead(BUILTIN_LED);  // get the current state of GPIO1 pin
  digitalWrite(BUILTIN_LED, !state);     // set pin to the opposite state
}

//gets called when WiFiManager enters configuration mode
void configModeCallback (WiFiManager *myWiFiManager) {
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  //if you used auto generated SSID, print it
  Serial.println(myWiFiManager->getConfigPortalSSID());
  //entered config mode, make led toggle faster
  ticker.attach(0.2, tick);
}



#define LED_PIN     D5
#define COLOR_ORDER GRB
#define CHIPSET     WS2812
#define NUM_LEDS    32
#define BRIGHTNESS  100
//#define FRAMES_PER_SECOND 60

//const char *ssid     = "Your-WIFI-NAME";
//const char *password = "Your-WIFI-Password";
const long utcOffsetInSeconds = -10800; //(this is your  local time difference vs UTC)
CRGB leds[NUM_LEDS];

// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds); //(by default it updates every 60 seconds so it doesn´t make so many requests)
int H, h, M, m, hora, minutos, pos, sum, numer;
long colorH, colorh, color2puntos, colorM, colorm;
int matrizpos[9];
int array1[9] = {1, 0, 0, 0, 0, 0, 0, 0, 0};
int array2[9] = {1, 1, 0, 0, 0, 0, 0, 0, 0};
int array3[9] = {1, 1, 1, 0, 0, 0, 0, 0, 0};
int array4[9] = {1, 1, 1, 1, 0, 0, 0, 0, 0};
int array5[9] = {1, 1, 1, 1, 1, 0, 0, 0, 0};
int array6[9] = {1, 1, 1, 1, 1, 1, 0, 0, 0};
int array7[9] = {1, 1, 1, 1, 1, 1, 1, 0, 0};
int array8[9] = {1, 1, 1, 1, 1, 1, 1, 1, 0};
int primero1[3] = {1, 0, 0};
int primero2[3] = {1, 1, 0};
unsigned long previousMillis = 0;
unsigned long previousMillis2 = 0;
unsigned long vloc = 20000;
unsigned long lastMillis;
boolean B2puntos;
boolean marca = false;



//Functions:
void Prendeh(int valor) {
}

void ArrayShaker (int num, int ubi) {
  int posicion = 0;
  long color = 0;

  switch (ubi) {
    case 2:
      posicion = 3;
      color = colorh;
      break;
    case 3:
      posicion = 14;
      color = colorM;
      break;
    case 4:
      posicion = 23;
      color = colorm;
      break;
  }
  if ( ubi != 1) {
    switch (num) {
      case 0: for (int z = 0; z < 9; z++) {
          leds[z + posicion] = CRGB::Black;
        }

        break;
      case 1:
        for (int i = 0; i < 9; i++)
        { int pos = random(9);
          int t = array1[i];
          array1[i] = array1[pos];
          array1[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array1[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }

        break;
      case 2:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array2[i];
          array2[i] = array2[pos];
          array2[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array2[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 3:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array3[i];
          array3[i] = array3[pos];
          array3[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array3[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 4:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array4[i];
          array4[i] = array4[pos];
          array4[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array4[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 5:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array5[i];
          array5[i] = array5[pos];
          array5[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array5[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 6:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array6[i];
          array6[i] = array6[pos];
          array6[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array6[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 7:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array7[i];
          array7[i] = array7[pos];
          array7[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array7[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 8:
        for (int i = 0; i < 9; i++)
        {
          int pos = random(9);
          int t = array8[i];
          array8[i] = array8[pos];
          array8[pos] = t;
        }
        for (int z = 0; z < 9; z++) {
          if (array8[z] == 1) {
            leds[z + posicion] = color;
          }
          else {
            leds[z + posicion] = CRGB::Black;
          }
        }
        break;
      case 9: for (int z = 0; z < 9; z++) {
          leds[z + posicion] = color;
        }
        break;
    }
  }

  else {
    switch (num) {
      case 0:
        for (int s = 0; s < 4; s++) {
          leds[s] = CRGB::Black;
        }
        break;
      case 1:
        for (int r = 0; r < 3; r++)
        { int pos = random(3);
          int t = primero1[r];
          primero1[r] = primero1[pos];
          primero1[pos] = t;
        }
        for (int s = 0; s < 3; s++) {
          if (primero1[s] == 1) {
            leds[s] = colorH;
          }
          else {
            leds[s] = CRGB::Black;
          }
        }

        break;
      case 2:
        for (int r = 0; r < 3; r++)
        { int pos = random(3);
          int t = primero2[r];
          primero2[r] = primero2[pos];
          primero2[pos] = t;
        }
        for (int s = 0; s < 3; s++) {
          if (primero2[s] == 1) {
            leds[s] = colorH;
          }
          else {
            leds[s] = CRGB::Black;
          }
        }
        break;
      case 3:
        for (int z = 0; z < 4; z++) {
          leds[z + posicion] = colorH;
        }
        break;
    }
  }
}

void blinkingcolon() {

  unsigned long currentMillis = millis();
  const long interval = 1000;
  long Ttransc = 0;
  Ttransc = currentMillis - previousMillis;
  if (Ttransc > interval) {
    previousMillis = currentMillis;
    if (B2puntos == false ) {
      leds[12] = color2puntos;
      leds[13] = color2puntos;
      B2puntos = true;
    }
    else {
      leds[12] = 0x000000;
      leds[13] = 0x000000;
      B2puntos = false;
    }
  }
}

void updatenumbers() {
  unsigned long currentMillis2 = millis();
  const long interval2 = vloc;
  long Ttransc2 = 0;
  Ttransc2 = currentMillis2 - previousMillis2;
  if (Ttransc2 > interval2) {
    previousMillis2 = currentMillis2;
    ArrayShaker(H, 1);
    ArrayShaker(h, 2);
    ArrayShaker(M, 3);
    ArrayShaker(m, 4);
  }

}
void ReadTime() {
  timeClient.update();
  //Get the values of each digit Hh:Mm
  hora = timeClient.getHours();
  H = hora / 10;
  h = hora - (H * 10);
  minutos = timeClient.getMinutes();
  M = minutos / 10;
  m = minutos - (M * 10);
}

void colorSelect() {
  colorH = 0xFF0000;
  colorh = 0xFFFF00;
  color2puntos = 0x00FF00;
  colorM = 0xFF007F;
  colorm = 0x20B2AA;
}


void setup() {
  Serial.begin(115200);
  //randomSeed(analogRead(0));
  //set led pin as output
  pinMode(BUILTIN_LED, OUTPUT);
  ticker.attach(0.6, tick);

  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( BRIGHTNESS );
  timeClient.begin();
  FastLED.clear();


  WiFiManager wifiManager; //  wifiManager.resetSettings();
  wifiManager.setAPCallback(configModeCallback);
  if (!wifiManager.autoConnect("Mega-Reloj")) {
    Serial.println("failed to connect and hit timeout");
    //reset and try again, or maybe put it to deep sleep
    ESP.reset(
    );
    delay(1000);
  }
  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  ticker.detach();
  //keep LED on
  digitalWrite(BUILTIN_LED, LOW);

}
/*Wiring/location of numbers:
  H from LED 0 to 2
  h from LED 3 to 11 (ubi 2)
  : LEDS 12 and 13
  M from LED 14 to 22 (ubi 3)
  m from LED 23 to 31 (ubi 4)
*/

void loop() {
  ReadTime();

  //Select color.
  colorSelect();
  //Assign and Randomize the position vector corresponding to each digit (ArrayShaker)
  updatenumbers();
  blinkingcolon();
  Serial.print(", ");
  Serial.print(timeClient.getHours());
  Serial.print(":");
  Serial.print(timeClient.getMinutes());
  Serial.print(":");
  Serial.println(timeClient.getSeconds());
  delay(1000);
  FastLED.show()<br>