Introduction: Cyber Clock an IoT OLED Desk Clock With Hygro-Thermometer

About: As I explore the exciting world of electronics, I can't help but feel a sense of wonder and amazement at the incredible things I discover!

As a tech enthusiast, I love seeing new technology and inventions. That's why I'm a big fan of Tesla, especially the Cybertruck. I think its design is cool and futuristic. I always wanted to 3D print a small model of the Cybertruck and put it on my desk. Then I thought I could also put an OLED screen as the windshield and build a cool desk clock out of it. The shape of the truck will make this task easy. I started with that small idea and built on top of it. In the end, here is the result,

The Cyber Clock” , A rechargeable battery-powered 1.51-inch transparent OLED clock with accurate internet time and date. It can also measure your room temperature and humidity from an onboard sensor. It can show the battery percentage as well, with a fully 3D-printed Cybertruck body. It also comes with rollable wheels so you can play with it. It is also possible to lock the wheels by tightening screws so it wouldn't fall from the desk by accident !.

also, I would like to mention that I took inspiration from Flipper Zero UI to design this watch face 

Through these instructables, we will learn how to build one yourself and customize it to your time zone. so let's get started with the cyber clock build.

Supplies

Parts

Tools 

  • Allen key
  • Soldering iron kit
  • Wirecutter
  • Glue gun

Used 3D printer

Used 3D printing filaments

Step 1: Modeling in Autodesk Fusion 360


I used Fusion 360 to plan and design my project. I designed this project with two things in mind: easy assembly and 3D printability. I divided the project body into multiple pieces to achieve this. By importing accurate electronic component models, I could pre-determine their positions on the body. This ensured that everything would fit and work together even before I built the prototype, saving a lot of time and effort. All design files are provided below.

Step 2: 3d Printing

After exporting all the models into.STL files, I 3D printed them using my Anycubic printer. For this project, I used three different coloured PLA filaments: black, grey, and white. You can find the.STL files from step 1. The top body requires some support material when being printed.

Step 3: Flashing Code to Xiao ES32C3

I always like to upload the code to the microcontroller before assembly. I am using Arduino IDE for flashing the code. follow these tutorials for setting up IDE for Seeed Studio XIAO ESP32C3 and learn more about this board

Make sure to install all required libraries into Arduino IDE

libraries for DHT11

How to install library tutorial video link

How to set your wifi password 

We need an internal connection to get time data from NTP servers. so we need to connect this clock to your wifi network. you can enter your wifi credentials on 46,47 lines 

const char* ssid     = "ssid";
const char* password = "password";

How to set your time zone 

The time zone is configured using your GMT offset in seconds. My GMT offset is GMT +5:30, now we need to convert it into seconds which is 19800sec. Just google it for easy conversion 

You can enter your GMT offset in seconds in line 65 of the code 

NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800, 0);

The complete code 

#include <WiFi.h>
#include <SPI.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define ANALOG_PIN D1      // Analog pin connected to the voltage divider
#define ADC_MAX 4095      // 12-bit ADC
#define REF_VOLTAGE 2.8   // Reference voltage of ADC (3.3V for Xiao ESP32C3)
// WiFi credentials
const char* ssid     = "ssid";//your ssid
const char* password = "password";//your wifi password
// OLED display SPI pins
#define OLED_MOSI    D10
#define OLED_CLK     D8
#define OLED_DC      D4
#define OLED_CS      D7
#define OLED_RESET   D5
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#define DHTPIN D0
#define DHTTYPE    DHT11
DHT dht(DHTPIN, DHTTYPE);
// NTP client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800, 0);
//bit ui bitmap
static const unsigned char PROGMEM image_paint_1_bits[] = {0x60,0xf0,0x90,0x90,0x90,0x90,0xf0};
static const unsigned char PROGMEM image_paint_0_bits[] = {0x7e,0x3f,0x3f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfc,0xfc,0x7e,0xc1,0x3e,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x7c,0x83,0xd5,0x3c,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x3c,0xab,0xd5,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9c,0xab,0xd5,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0xab,0x95,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x24,0xa9,0x25,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0xa4,0x49,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x92,0x93,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xc9,0xa6,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x65,0xac,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x35,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x15,0xa8,0x26,0x04,0x92,0x48,0x3f,0xff,0xff,0xff,0xff,0xfc,0x12,0x49,0x20,0x64,0x15,0x88,0x29,0x09,0x24,0x90,0x7f,0xff,0xff,0xff,0xff,0xfe,0x09,0x24,0x90,0x94,0x11,0xa8,0x29,0x12,0x49,0x20,0xf0,0x00,0x00,0x00,0x00,0x0f,0x04,0x92,0x48,0x94,0x15,0x88,0x26,0x24,0x92,0x41,0xe0,0x00,0x00,0x00,0x00,0x07,0x82,0x49,0x24,0x64,0x11,0xa8,0x10,0x00,0x00,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xc0,0x00,0x00,0x08,0x15,0x88,0x0f,0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0xff,0xf0,0x11,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x2f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xf4,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x00,0x80,0x00,0x00,0x00,0x00,0x02,0x08,0x20,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x01,0xc0,0x00,0x00,0x00,0x00,0x01,0x10,0x50,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x02,0xe0,0x00,0x00,0x00,0x00,0x00,0xa0,0x50,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x03,0xe0,0x00,0x00,0x00,0x00,0x00,0x40,0x50,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x05,0xf0,0x00,0x00,0x00,0x00,0x00,0x40,0x50,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x05,0xf0,0x00,0x00,0x00,0x00,0x00,0x40,0x88,0x00,0x00,0x00,0x00,0x00,0x15,0xa8,0x03,0xe0,0x00,0x00,0x00,0x00,0x00,0x40,0xf8,0x00,0x00,0x00,0x00,0x00,0x15,0xac,0x01,0xc0,0x00,0x00,0x00,0x00,0x00,0x40,0xf8,0x00,0x00,0x00,0x00,0x00,0x35,0xa6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x70,0x00,0x00,0x00,0x00,0x00,0x65,0xa3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xc5,0x91,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x89,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x92};
float readBatteryVoltage() {
  int analogValue = analogRead(ANALOG_PIN);
  float voltage = (analogValue / (float)ADC_MAX) * REF_VOLTAGE;
  // Voltage divider scaling
  voltage = voltage * (10.5 + 10) / 10.0; // (R1 + R2) / R2
  return voltage;
}
// Function to calculate battery percentage
float calculateBatteryPercentage(float voltage) {
  float percentage;
  // Assuming a linear relationship between voltage and percentage
  if (voltage >= 4.2) {
    percentage = 100.0;
  } else if (voltage <= 2.5) {
    percentage = 0.0;
  } else {
    percentage = (voltage - 2.5) / (4.2 - 2.5) * 100.0;
  }
  return percentage;
}
void setup() {
  // Start serial communication
  Serial.begin(115200);
  dht.begin();
  analogReadResolution(12); // Set ADC resolution to 12-bit
  // Initialize OLED display
  if(!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever;
  }
  //display.display();
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
  // Initialize time client
  timeClient.begin();
}

void loop() {
   
  // Update time from NTP server
  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();
  struct tm *ptm = gmtime ((time_t *)&epochTime);
  int monthDay = ptm->tm_mday;
  int currentMonth = ptm->tm_mon+1;
  int currentYear = ptm->tm_year+1900;
  int currentHour = ptm->tm_hour;
  int currentMinute = ptm->tm_min;
  int currentSecond = ptm->tm_sec;
 float batteryVoltage = readBatteryVoltage();
  float batteryPercentage = calculateBatteryPercentage(batteryVoltage);
  // Get temperature and humidity
float t = dht.readTemperature();
  float h = dht.readHumidity();

  // Convert time to 12-hour format
  String period = "AM";
  if (currentHour >= 12) {
    period = "PM";
    if (currentHour > 12) {
      currentHour -= 12;
    }
  } else if (currentHour == 0) {
    currentHour = 12;
  }

  // Display data on OLED
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  // Display time
  display.setCursor(19,6);
  display.setTextSize(3);
  if (currentHour < 10) display.print('0');
  display.print(currentHour);
  display.print(':');
  if (currentMinute < 10) display.print('0');
  display.print(currentMinute);
  display.setTextSize(1);
  display.setCursor(108, 39);
  display.print(period);
  // Display date
  display.setCursor(36, 39);
  if (monthDay < 10) display.print('0');
  display.print(monthDay);
  display.print('/');
  if (currentMonth < 10) display.print('0');
  display.print(currentMonth);
  display.print('/');
  display.print(currentYear);
  // Display temperature
  display.setCursor(79, 53);
  display.print(t);
  display.print("C");
  // Display humidity
  display.setCursor(22, 53);
  display.print(h);
  display.print("%");
  display.drawBitmap(0, 0, image_paint_0_bits, 128, 64, 1);
  display.setCursor(14, 39);
  display.print(batteryPercentage,0);
  display.drawBitmap(8, 39, image_paint_1_bits, 4, 7, 1);
  display.display();
  // Delay before the next update
  delay(1000);
}

Step 4: Wiring Diagram

It seems like the wiring is a little complicated right? but just follow the colours of the wire connection and the labels on the endpoints. also, the wire colours used on the driver board and schematic are the same So grab your tools let's start the build

XIAO ESP32C3 Supports lithium battery charge and discharge management . Which means BMS is built in . So there is no need for an external BMS. You can charge the battery through USB port

Step 5: Assembly and Wiring

We can start by creating a voltage divider in the back side of the Xiao

Step 5.1

First, we need to put some Kapton tape under the Xiao battery terminate 

Step 5.2

Solder the 10k SMD resistors to the two battery inputs. Then, connect the other ends of the resistors to the D1 pin using a small gauge wire to measure the battery voltage. 

Step 5.3

Connect the positive terminal of the battery to one of the terminals of the power switch, and connect the other terminal of the switch to the BAT+ of the Xiao. Also, connect the negative terminal of the battery directly to the BAT- of the Xiao. also, make sure that all the wires are at the proper length by placing them in the underframe 

Step 5.4

Glue the battery, xiao, and power switch using glue. I already designed slots for the components to fit in. Make sure to get the wire under the xiao through the gap. 

Step 5.5

Cut the terminals of the DHT11 to the appropriate length. Solder a 10k resistor between the VCC and DATA lines of the DTH11. Also, solder 3 wires to VCC, GND, and DATA with the length that can reach the Xiao pins. I am using a through-hole 10k resistor here 

Step 5.6

Place the DHT11 upside down into the small window. Now, glue the DHT11 using our B-7000 Multi-Purpose Glue. (We are not using hot glue here because the heat might damage the sensor.)

Step 5.7

Solder the DHT11 sensor wires into Xiao, Also I did some wire management with Kapton tape. now we are done with most of the work on the down frame let's move on to the upper body

Step 5.8

Be very careful during this step as we install the OLED screen into the main body. Do not apply too much pressure when handling the display, as it can easily break. We will be using B-7000 Multi-Purpose Glue to attach the display to the 3D print. Make sure that the orientation of the display is correct.

Make sure to check the orientation of the flex cable when you test the display with the driver board.

apply glue to the inside, bottom, and left and right corners of the frame. Don't use too much glue it will spill from the edges 

Now carefully place the display into the frame also make sure the orientation is right. 

Leave the glue to dry for 20 minutes.

Step 5.9

Put the car interior inside the main body and glue it with superglue 

Step 5.10

Glue the OLED driver board in the back side of the interior body and connect the flux cable 

Step 5.11

Now push in the light bar 3d print in the little gap on the front. You can glue it if you need 

Step 5.12 

Glue the Xiao esp32 antenna inside the body

Step 5.13

Connect the JST connector and Cut the display driver wires to the appropriate length, then solder them to the Xiao. also connect the antenna port.

Step 5.14

Put the upper body into the underframe and screw it with two M3 screws.

Step 5.15

Screw all four wheels into the front and back axials using m3 screws. if your wheels need to be rotated don't tighten the screw that much. if you need to lock the wheels just tighten the screws

Step 5.16 

Next using some super glue we will glue the front and back axial under the underframe

Step 5.17

This will be the last step of the build, gluing two rooftop components, on top of the main body 

Yes the assembly of the project is completed 

Now power on the clock using the slide switch on the back. you can also recharge the battery through Xiao USB

Wait for a few seconds. It will power on. The time will update automatically. if not check your entered wifi password and ssid is correct  

Step 6: Troubleshooting

The wheels are not spinning freely: check if the wheel screws are not too tight. also, the hole of the wheels needs to bit larger than the screw. Try to clear the wheel holes after the 3d printing 

Time is not updating: During boot-up, the system may initially display the incorrect time, such as the year 1971, but it quickly updates to the correct time. If it not working it will be a network issue also check the Wi-Fi password and SSID you entered during the programming

Battery not charging: the battery can be only charged during the power switch is on. the battery managed through the BMS inside the Xiao

Random sensor data: if the battery is below 20% the sensor might not give data due to low voltage so recharge your battery

Step 7: Final Thought

I believe this project is perfect for car enthusiasts. You can easily build and gift it to friends and family who love cars. It's a great addition to any desk setup, offering a fantastic viewing angle from dusk. This project is truly unique and I think something similar is not available on the market. If you need suggestions for other car models, let me know. There's a lot of potential in this project. We can enhance the software by displaying YouTube subscriber counts, reminders, and Bitcoin value... etc If you're interested in creating more pages for this project, just let me know. I've created a small canvas for you, and now your imagination is the only limit.

thanks

Gokux