Tiktok Clock

So one day whilst endlessly scrolling TikTok, I came across this clock, which I thought looked pretty cool. so I bought it.

After a week or so, I decided I liked it, but I would like it more if i could dim the digits. There is an auto dimming feature, which only dims the digits and not the second dots.

So in true Big Clive fashion… I took it to bits. Pulled out its little brain and reverse engineered it.

The yellow box shows the modification to the second flashing dots, so they aren’t just powered direct from the 5v rail and grounded at the main IC. I had to grind away any connection to the LEDS leaving enough pad to solder a copper wire to the positive side of the transistor which supplies +5v to the positive side of all the LEDS to modulate them for the auto dimming function. This is an Analogue reference that I don’t currently have connected , so far we are at proof of concept.

At position 1 we had an unknown IC, unmarked or demarked or otherwise unknown.

At positions 2 → 7 we have 6 x TC5020D 16 bit Constant Current IC Drivers. Which are basically shift registers. These are daisy chained from their input to output in the order shown.

So I managed to grind away some of the conformal coating on the rear and get access to the DI (Data input to chip 2) CLK (clock) and (LE) Latch Enable. feed the base of the LED Digit Darlington Array and grab some power and ground from some unused capacitor through holes, and start tapping away to send data to it.

As said this is currently first stage development, I’m not sure if it was pin assignment, failing to use a level shifter, burning though the data line with the 5v rail while soldering, or a definite combination of all 3. But I used my stock of Lolin ESP-32 S2 Development boards which has more available GPIO pins to use all the functions that are supplied with the clock IE,
A piezo buzzer, Analogue Light sensor (that will possibly need changing to 1v resolution), AMT30 Temp and humidity sensor (that will either need its data lines logic shifting, or which I’m planning to do, is cut its 5v line and feed it from 3.3V to save space.

Here is the ESPHOME configuration that updates the clock at second 0 of each minute.

globals:
  - id: light_timeout
    type: int
    restore_value: no
    initial_value: '0'

  - id: segment_map
    type: int[63]  
    restore_value: no
    initial_value: |-
      // Segment mapping to map clock segments to register output pins
      // ** Do not take time to fill this in until you are sure you have LSB or MSB correct **
      //   A   B   C   D   E   F   G  - Segment to register bits
        {
          18, 17, 23, 24, 21, 19, 20,   // Digit 1 (clock hour 10s)
          30, 29, 26, 25, 22, 16, 31,   // Digit 2 (clock hour unit) 
          33, 39, 38, 37, 36, 34, 35,   // Digit 3 (clock minute 10s)
          46, 45, 44, 43, 41, 40, 42,   // Digit 4 (clock minute unit)
          75, 76, 78, 79, 56, 57, 77,   // Digit 5 (humidity 10s)
          92, 93, 95, 72, 73, 74, 94,   // Digit 6 (humidity units)
          -1, 51, 50, -1, -1, -1, -1,   // Digit 7 (temp 100s - only segments B, C for "1")
          55, 66, 68, 52, 53, 54, 67,   // Digit 8 (temp 10s)
          80, 81, 83, 84, 69, 70, 82    // Digit 9 (temp units)
        }
  - id: digit_segments
    type: int[77]  
    restore_value: no
    initial_value: |-
      // Font mapping to create the numbers on the display - Using standard segment order A -> G
      // 1 = Segment Active, 0 = Segment not active
      //  A  B  C  D  E  F  G
        {
          1, 1, 1, 1, 1, 1, 0,    // 0 
          0, 1, 1, 0, 0, 0, 0,    // 1 
          1, 1, 0, 1, 1, 0, 1,    // 2
          1, 1, 1, 1, 0, 0, 1,    // 3
          0, 1, 1, 0, 0, 1, 1,    // 4
          1, 0, 1, 1, 0, 1, 1,    // 5
          1, 0, 1, 1, 1, 1, 1,    // 6
          1, 1, 1, 0, 0, 0, 0,    // 7
          1, 1, 1, 1, 1, 1, 1,    // 8
          1, 1, 1, 1, 0, 1, 1,    // 9
          0, 0, 0, 0, 0, 0, 0     // -1 (Blank)
        }
  - id: day_map
    type: int[14]  
    restore_value: no
    initial_value: |-
      // Map for day LED indicators Blue Left and White Right
      //  B  W
        {
          9, 8,   // Sunday 
          0, 1,   // Monday     
          2, 3,   // Tuesday    
          4, 5,   // Wednesday
          6, 7,   // Thursday
          12, 13, // Friday
          10, 11  // Saturday
        }
    
# Any change to the backlight will trigger a 4 hour clock that resets it back to 50% 
light:
  - platform: monochromatic
    name: "Clock Light"
    id: clock_light
    output: output_component4
    restore_mode: RESTORE_DEFAULT_ON
    on_state: 
      then:
        - script.stop: auto_off_timer    # Stop any existing count down timers
        - script.execute: auto_off_timer # Start new 4 Hour Timer

# Define pins for operation
output:
  - platform: esp8266_pwm
    id: output_component4 
    pin: D2  # Clock Back Light
  - platform: esp8266_pwm
    id: second_timing_led 
    pin: D1  # Clock Seconds indicator
  - platform: gpio
    id: sr_data
    pin: D7  # Data (DI)
  - platform: gpio
    id: sr_clock
    pin: D6  # Clock (CLK)
  - platform: gpio
    id: sr_latch
    pin: D5  # Latch (LE)


# This flashes the seconds indicators
interval:
  - interval: 500ms
    then:
      - lambda: |-
          static bool led_state = false;
          led_state = !led_state;

          // Depending on state flip the output
          if (led_state) {
            id(second_timing_led)->turn_on();
          } else {
            id(second_timing_led)->turn_off();
          }

      
# Sensors for the connection status to home assistant and also the statis of Deekee Alarm
binary_sensor:
  - platform: status
    name: "Deekee Clock Status"
  - platform: homeassistant
    id: deekee_alarm
    entity_id: input_boolean.deekee_alarm
    on_state:
      - script.execute: display_digits

# Wifi Strength sensor
sensor:
  - platform: wifi_signal
    name: "Deekee Clock WiFi"
    update_interval: 60s

# Switches to restart the module from the front end and switch between 12-24 Hour formats
switch:
  - platform: restart
    name: "Deekee Clock Restart"
  - platform: template
    name: "24-Hour Format"
    id: time_format_24h
    restore_mode: RESTORE_DEFAULT_ON  # Default is 24-hour format
    optimistic: true
    on_turn_on:
      - script.execute: display_digits
    on_turn_off:
      - script.execute: display_digits
    
    
# Time platform to grab RTC data and also update the display every minute at second 00
time:
  - platform: homeassistant
    id: esphome_time
    on_time:
      - seconds: 0
        minutes: /1  # Runs every minute at second 0
        then:
          - script.execute: display_digits 

# Scrip to reset the backlight after 4 hours and Logic to run clock display     
script:
  - id: auto_off_timer
    then:
      - delay: 4h
      - light.turn_on:
             id: clock_light
             brightness: 50%
  - id: display_digits
    then:
      - lambda: |-
          // Get current time from ESPHome's RTC
          auto time_now = id(esphome_time).now();

          // Set Variables to make comparison and maths easier 
          int hour = time_now.hour;             // Current Minute
          int minute = time_now.minute;         // Current hour
          int day = time_now.day_of_week - 1;   // 0 = Sunday, 6 = Saturday - C++ Retutns 1 = Sunday, 7 = Saturday
          int date = time_now.day_of_month;     // Day of the month (1-31)
          int month = time_now.month;           // Month (1-12)
          bool dst_active = time_now.is_dst;    // Daylight savint time

          // Check if 24-hour format switch is ON
          bool is_24h = id(time_format_24h).state;
          bool pm = false;

          // 12-hour format
          if (!is_24h) {   
              if (hour >= 12) {
                  pm = true;
                  if (hour > 12) hour -= 12;    // Convert to 12-hour
              }
              if (hour == 0) hour = 12;         // Midnight = 12 AM
          }

          // Get individual digits for time + Remove leading zero in hour
          int digits[4] = { hour / 10, hour % 10, minute / 10, minute % 10 };
          if (digits[0] == 0) digits[0] = -1;


          // Get individual digits for date (day of the month) + Remove leading zero 
          int date_digits[2] = { date / 10, date % 10 };
          if (date_digits[0] == 0) date_digits[0] = -1;


          // Get individual digits for month + Remove leading zero
          int month_digits[3] = { month / 10, month % 10, -1};
          if (month_digits[0] == 0) month_digits[0] = -1;


          // Clear shift register bits
          int bits[96] = {0};  // 96 bits for 6 shift registers

          // Add 4 TIME digits using segment_map + digit_segemnts to the register (Digits 1 -> 4)
          for (int d = 0; d < 4; d++) {
              for (int s = 0; s < 7; s++) {
                  // This fetches the bit position for the current digit + segment
                  int bit_pos = id(segment_map)[(d * 7) + s];

                  // Use blank digit pattern if the value is -1
                  int digit_to_display = (digits[d] == -1) ? 10 : digits[d];

                  // This writes the bit to the register at the correct positon dependant on current digit and segment map
                  bits[bit_pos] = id(digit_segments)[(digit_to_display * 7) + s];
              }
          }

          // Add 2 digits DATE/HUMIDITY digits to the register (Digits 5 and 6)
          for (int d = 0; d < 2; d++) {
              for (int s = 0; s < 7; s++) {
                  int bit_pos = id(segment_map)[(4 + d) * 7 + s]; 

                  int digit_to_display = (date_digits[d] == -1) ? 10 : date_digits[d];

                  bits[bit_pos] = id(digit_segments)[digit_to_display * 7 + s];
              }
          }

          // Add 2/(3) digits MONTH/TEMP digits to the register (Digits (7), 8 and 9)
          for (int d = 0; d < 2; d++) {
              for (int s = 0; s < 7; s++) {
                  int bit_pos = id(segment_map)[(6 + d) * 7 + s];

                  int digit_to_display = (month_digits[d] == -1) ? 10 : month_digits[d];

                  bits[bit_pos] = id(digit_segments)[digit_to_display * 7 + s];
              }
          }

          // AM/PM Indicator
          bits[14] = pm ? 1 : 0;

          // DST Indicator
          bits[58] = dst_active ? 1 : 0;

          // Set Alarm 1 based on room alarm (treated as a global as its out of scope)
          bits[59] = id(deekee_alarm).state ? 1 : 0;

          // Turn On all Weekday WHITE LEDs
          for (int i = 0; i < 7; i++) {
              bits[id(day_map)[i * 2]] = 0;      // Blue LED Off
              bits[id(day_map)[i * 2 + 1]] = 1;  // White LED On
          }

          // Override current day: Turn On Blue, Turn Off White
          bits[id(day_map)[day * 2]] = 1;        // Blue LED On
          bits[id(day_map)[day * 2 + 1]] = 0;    // White LED Off

          // Designed to be sent to 6 x 16bit TC5020D Shift Registers
          // Send data to shift registers in reverse order
          for (int i = 95; i >= 0; i--) {
              id(sr_data)->set_state(bits[i]);   // Set the DATA line HIGH
              id(sr_clock)->turn_on();           // Set the CLK line HIGH to clock the data into the register
              delayMicroseconds(5);              // Delay to ensure we're not sending data too fast to the register
              id(sr_clock)->turn_off();          // Set CLK Low to complete DATA transfer
          }

          // Latch the data to the shift registers
          id(sr_latch)->turn_on();               // Set LE High To latch the data through all shift registers 
          delayMicroseconds(5);                  // Delay to not overload the registers
          id(sr_latch)->turn_off();              // Set LE Low to complete transfer

I will Update/Edit/Add to this post, when I have a delivery of new parts to show final functionality. And at the very least It can hopefully help someone who wants to design their own Network Time Clock, or modify their existing clock.

Many Thanks
Dave :+1:

1 Like

Can you link to the device?