Rebecca Jiang
Published

Shattered Clock with UNIHIKER K10

Broken Clock — UNIHIKER K10 Animated Shattered Clock

BeginnerFull instructions provided4 hours59

Things used in this project

Hardware components

DFRobot UNIHIKER K10 AI Coding Board
×1
USB Cable, USB Type C Plug
USB Cable, USB Type C Plug
×1

Software apps and online services

PlatformIO IDE
PlatformIO IDE
Arduino IDE
Arduino IDE
VS Code
Microsoft VS Code

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Code

clock.cpp

C/C++
#include "unihiker_k10.h"
#include <WiFi.h>
#include <WiFiManager.h> //  WiFiManager 
// #include <WebServer.h>
// #include <DNSServer.h> // DNS  -> 
#include <time.h>
#include <cmath> // 

UNIHIKER_K10 k10;
AHT20 aht20;
// WebServer server(80);
// DNSServer dnsServer; // DNS  -> 

//  3
uint8_t screen_dir = 3; 
// 
const int SCREEN_W = 320;
const int SCREEN_H = 240;

const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
const int ALS_COVER_THRESHOLD = 40;

// 
// :
// 4 x 7 x 5 = 140
//  2 x 2 = 4
// : 0-143 (144)
// : 144-163 (20)
// : 164-203 (40)
const int PARTICLE_COUNT = 250;
float partOffX[PARTICLE_COUNT] = {0};
float partOffY[PARTICLE_COUNT] = {0};
// 
float partVelX[PARTICLE_COUNT] = {0};
float partVelY[PARTICLE_COUNT] = {0};

// HSV -> RGB helper
void hsv2rgb(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
	float hh = fmod(h, 360.0f) / 60.0f;
	int i = (int)floor(hh);
	float f = hh - i;
	float p = v * (1.0f - s);
	float q = v * (1.0f - s * f);
	float t = v * (1.0f - s * (1.0f - f));
	float R=0,G=0,B=0;
	switch(i) {
		case 0: R=v; G=t; B=p; break;
		case 1: R=q; G=v; B=p; break;
		case 2: R=p; G=v; B=t; break;
		case 3: R=p; G=q; B=v; break;
		case 4: R=t; G=p; B=v; break;
		case 5:
		default: R=v; G=p; B=q; break;
	}
	r = (uint8_t)round(R * 255.0f);
	g = (uint8_t)round(G * 255.0f);
	b = (uint8_t)round(B * 255.0f);
}

// 
// splits: 
// offX, offY: 
void drawFragmentedRect(int x, int y, int w, int h, bool isVertical, int splits, uint32_t color, float* offX, float* offY) {
    if (isVertical) {
        float segH = (float)h / splits;
        for(int i=0; i<splits; i++) {
            int curY = y + (int)(i * segH);
            int curH = (int)((i + 1) * segH) - (int)(i * segH); // 
            k10.canvas->canvasRectangle(x + (int)offX[i], curY + (int)offY[i], w, curH, color, color, true);
        }
    } else {
        float segW = (float)w / splits;
        for(int i=0; i<splits; i++) {
            int curX = x + (int)(i * segW);
            int curW = (int)((i + 1) * segW) - (int)(i * segW);
            k10.canvas->canvasRectangle(curX + (int)offX[i], y + (int)offY[i], curW, h, color, color, true);
        }
    }
}

//  7  (5)
// offX, offY:  35 (7*5) float
void drawBigDigit(int x, int y, int num, int scale, uint32_t color, float* offX, float* offY) {
    // 7: A,B,C,D,E,F,G (0=off, 1=on)
    const uint8_t segs[10] = {
        0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B
    };

    uint8_t pattern = segs[num % 10];
    int w = 12 * scale;  // 
    int h = 20 * scale;  //  ()
    int t = 2 * scale;   // 
    const int FRAGS = 5; // 5

    // Segment A (Top) - Horizontal [Idx 0-4]
    if (pattern & 0x40) drawFragmentedRect(x + t, y, w - 2*t, t, false, FRAGS, color, &offX[0], &offY[0]);
    
    // Segment B (Top-Right) - Vertical [Idx 5-9]
    if (pattern & 0x20) drawFragmentedRect(x + w - t, y + t, t, h - 2*t, true, FRAGS, color, &offX[5], &offY[5]);

    // Segment C (Bottom-Right) - Vertical [Idx 10-14]
    if (pattern & 0x10) drawFragmentedRect(x + w - t, y + h + t, t, h - 2*t, true, FRAGS, color, &offX[10], &offY[10]);

    // Segment D (Bottom) - Horizontal [Idx 15-19]
    if (pattern & 0x08) drawFragmentedRect(x + t, y + 2*h, w - 2*t, t, false, FRAGS, color, &offX[15], &offY[15]);

    // Segment E (Bottom-Left) - Vertical [Idx 20-24]
    if (pattern & 0x04) drawFragmentedRect(x, y + h + t, t, h - 2*t, true, FRAGS, color, &offX[20], &offY[20]);

    // Segment F (Top-Left) - Vertical [Idx 25-29]
    if (pattern & 0x02) drawFragmentedRect(x, y + t, t, h - 2*t, true, FRAGS, color, &offX[25], &offY[25]);

    // Segment G (Middle) - Horizontal [Idx 30-34]
    if (pattern & 0x01) drawFragmentedRect(x + t, y + h, w - 2*t, t, false, FRAGS, color, &offX[30], &offY[30]);
}

//  (22)
// offX, offY:  4 
void drawBigColon(int x, int y, int scale, uint32_t color, float* offX, float* offY) {
    int h = 20 * scale;
    //  [Idx 0-1]
    drawFragmentedRect(x, y + h/2, 2*scale, 2*scale, false, 2, color, &offX[0], &offY[0]);
    //  [Idx 2-3]
    drawFragmentedRect(x, y + h + h/2, 2*scale, 2*scale, false, 2, color, &offX[2], &offY[2]);
}

// 
// charW: 
void drawTextMosaic(String text, int startX, int startY, uint32_t color, Canvas::eFontSize_t font, int charW, int startIdx) {
    int cursorX = startX;
    for (int i = 0; i < text.length(); i++) {
        String ch = text.substring(i, i+1);
        int pIdx = startIdx + i;
        if (pIdx >= PARTICLE_COUNT) pIdx = PARTICLE_COUNT - 1; // 
        
        // autoClean=false, 
        k10.canvas->canvasText(ch, cursorX + (int)partOffX[pIdx], startY + (int)partOffY[pIdx], color, font, charW + 5, false);
        cursorX += charW;
    }
}

void setup() {
	Serial.begin(115200);
	k10.begin();
	k10.initScreen(screen_dir);
	k10.creatCanvas();
    // 
	k10.setScreenBackground(0x000000);
	delay(200);

    // ---  WiFi  WiFiManager ---
	WiFi.mode(WIFI_STA);
    
    // 
    k10.canvas->canvasText("Connecting WiFi...", 30, SCREEN_H/2 - 20, 0xFFFFFF, k10.canvas->eCNAndENFont24, SCREEN_W, true);

    //  WiFiManager
    {
        WiFiManager wm;
        //  wm.setConfigPortalTimeout(180);
        //  false
        bool ok = wm.autoConnect("K10-Clock-Setup");
        if (!ok) {
            // 
            k10.canvas->canvasText("WiFi Setup Failed", 30, SCREEN_H/2 + 10, 0xFF0000, k10.canvas->eCNAndENFont24, SCREEN_W, true);
        }
    }

    //  WiFiManager 
	k10.canvas->canvasRectangle(0, 0, 320, 240, 0x000000, 0x000000, true);
	k10.canvas->canvasText("WiFi Connected!", 50, 100, 0x00FF00, k10.canvas->eCNAndENFont24, 300, true);
    delay(1000);

	configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org", "time.google.com");
	delay(200);

	k10.rgb->brightness(9);
	k10.rgb->write(-1, 0,0,0);
}

void loop() {
	struct tm timeinfo;
	bool timeReady = getLocalTime(&timeinfo);
	float tempC = aht20.getData(AHT20::eAHT20TempC);
	float hum   = aht20.getData(AHT20::eAHT20HumiRH);
	int als = k10.readALS();
    int strength = k10.getStrength(); // 
    
    // 
    int rawX = k10.getAccelerometerX();
    int rawY = k10.getAccelerometerY();
    
    // 3()YXXY
    // (+/-)/
    float shakeDirX = rawY / 20.0f;  
    float shakeDirY = -rawX / 20.0f;

	// 
	k10.canvas->canvasRectangle(0, 0, 320, 320, 0x000000, 0x000000, true);

    // ---  ---
    //  1000  (1g)
    // 
    // 1.  < 50: 
    // 2. 50 <=  < 800: 
    // 3.  >= 800: 
    int diff = abs(strength - 1000);

    // ()
    for(int i=0; i<PARTICLE_COUNT; i++) {
        if (diff >= 800) { 
            // 
            float accX = random(-150, 151) / 2.0f;
            float accY = random(-150, 151) / 2.0f;
            
            // 
            partVelX[i] += accX + shakeDirX;
            partVelY[i] += accY + shakeDirY;

            // 
            partVelX[i] *= 0.995f; 
            partVelY[i] *= 0.995f;
        } 
        else if (diff >= 50) { 
            // 
            float accX = random(-60, 61) / 4.0f; 
            float accY = random(-60, 61) / 4.0f;
            
            // 
            partVelX[i] += accX + (shakeDirX * 0.3f);
            partVelY[i] += accY + (shakeDirY * 0.3f);

            // 
            float k = 0.015f; 
            partVelX[i] += -k * partOffX[i];
            partVelY[i] += -k * partOffY[i];

            // 
            partVelX[i] *= 0.96f; 
            partVelY[i] *= 0.96f;
        }
        else {
            // 
            float k = 0.35f; // 
            float forceX = -k * partOffX[i];
            float forceY = -k * partOffY[i];

            partVelX[i] += forceX;
            partVelY[i] += forceY;

            //  ()
            partVelX[i] *= 0.60f; 
            partVelY[i] *= 0.60f;

            // 
            if (abs(partOffX[i]) < 1.0f && abs(partVelX[i]) < 0.5f) {
                partOffX[i] = 0; 
                partVelX[i] = 0;
            }
            if (abs(partOffY[i]) < 1.0f && abs(partVelY[i]) < 0.5f) {
                partOffY[i] = 0; 
                partVelY[i] = 0;
            }
        }
        
        //  += 
        partOffX[i] += partVelX[i];
        partOffY[i] += partVelY[i];
    }

    if (timeReady) {
        // --- 1.  (Offset Index 144+) ---
        char dateBuf[32];
        const char* wds[] = {"SUN","MON","TUE","WED","THU","FRI","SAT"};
        snprintf(dateBuf, sizeof(dateBuf), "%04d-%02d-%02d %s", 
            timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, wds[timeinfo.tm_wday]);
        
        // Font24  14px
        // Offset start: 144
        drawTextMosaic(String(dateBuf), 55, 20, 0xAAAAAA, k10.canvas->eCNAndENFont24, 14, 144);

        // --- 2.  (Offset Index 0..143) ---
        // : Digit(35) | Digit(35) | Colon(4) | Digit(35) | Digit(35)
        int scale = 3;
        int digitW = 12 * scale + 6; 
        int colonW = 4 * scale + 6;
        int totalW = (4 * digitW) + colonW; 
        
        int startX = (SCREEN_W - totalW) / 2;
        int startY = (SCREEN_H - (40 * scale)) / 2; // 
        
        int h1 = timeinfo.tm_hour / 10;
        int h2 = timeinfo.tm_hour % 10;
        int m1 = timeinfo.tm_min / 10;
        int m2 = timeinfo.tm_min % 10;
        
        uint32_t tColor = 0xFFFFFF; // 

        // Offset 0
        drawBigDigit(startX, startY, h1, scale, tColor, &partOffX[0], &partOffY[0]);
        // Offset 35
        drawBigDigit(startX + digitW, startY, h2, scale, tColor, &partOffX[35], &partOffY[35]);
        // Offset 70
        drawBigColon(startX + digitW * 2, startY, scale, tColor, &partOffX[70], &partOffY[70]);
        // Offset 74
        drawBigDigit(startX + digitW * 2 + colonW, startY, m1, scale, tColor, &partOffX[74], &partOffY[74]);
        // Offset 109
        drawBigDigit(startX + digitW * 3 + colonW, startY, m2, scale, tColor, &partOffX[109], &partOffY[109]);

        // --- 3.  (Offset Index 164+) ---
        int lblY = SCREEN_H - 55;
        int valY = SCREEN_H - 35;
        
        // : Label(50), Value(60)
        int tempX = 50;
        drawTextMosaic("TEMP", tempX, lblY, 0x888888, k10.canvas->eCNAndENFont16, 9, 164);
        drawTextMosaic(String(tempC, 1) + "C", tempX - 10, valY, 0xFFB36B, k10.canvas->eCNAndENFont24, 14, 174);

        // : Label(70), Value(80)
        int humX = 220;
        drawTextMosaic("HUM", humX, lblY, 0x888888, k10.canvas->eCNAndENFont16, 9, 184);
        drawTextMosaic(String(hum, 0) + "%", humX, valY, 0x66CCFF, k10.canvas->eCNAndENFont24, 14, 194);
        
    } else {
        k10.canvas->canvasText("WIFI CONNECTING...", 60, SCREEN_H/2, 0xFFFFFF, k10.canvas->eCNAndENFont24, SCREEN_W, true);
    }

	// 
	if (als >= 0 && als < ALS_COVER_THRESHOLD) {
		unsigned long ms = millis();
		float phase = (ms % 1000) / 1000.0f;
		float breath = 0.6f * (0.5f * (1.0f + sinf(2.0f * 3.1415926f * phase - 3.1415926f/2.0f))) + 0.2f;
		float hue = fmod(ms / 10.0f, 360.0f);
		uint8_t r,g,b;
		hsv2rgb(hue, 1.0f, breath, r, g, b);
		k10.rgb->brightness(9);
		k10.rgb->write(-1, r, g, b);
	} else {
		k10.rgb->write(-1, 0, 0, 0);
	}

	k10.canvas->updateCanvas();
	delay(30);
}

3D Print Case and Battery Management PCB Design

Credits

Rebecca Jiang
2 projects • 3 followers
Mushroom Cloud Maker Space Operation Manager

Comments