{"id":855908,"date":"2026-04-30T17:32:50","date_gmt":"2026-04-30T17:32:50","guid":{"rendered":"https:\/\/elektrobox.ch\/?page_id=855908"},"modified":"2026-06-02T16:44:10","modified_gmt":"2026-06-02T16:44:10","slug":"elektroclock","status":"publish","type":"page","link":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/","title":{"rendered":"Elektroclock"},"content":{"rendered":"\n<style data-wp-block-html=\"css\">\n\/* =========================================================\n   ElektroBox Project Page CSS\n   Shared layout for all project pages\n   Matches the ElektroClock restructured HTML form\n   Paste once into Appearance -> Customize -> Additional CSS\n   ========================================================= *\/\n\n.ebox-project-page,\n.ebox-project-page *,\n.ebox-project-page *::before,\n.ebox-project-page *::after {\n  box-sizing: border-box;\n}\n\n.ebox-project-page {\n  width: 100%;\n  max-width: 1080px;\n  margin: 0 auto;\n  padding: 0 20px;\n  font-family: inherit;\n  line-height: 1.7;\n  color: #1f2933;\n  overflow-x: hidden;\n}\n\n.ebox-project-page h1,\n.ebox-project-page h2,\n.ebox-project-page h3 {\n  color: #111827;\n  line-height: 1.25;\n  overflow-wrap: break-word;\n}\n\n.ebox-project-page h1 {\n  margin: 0 0 16px;\n  font-size: clamp(2.15rem, 4.5vw, 3.5rem);\n  letter-spacing: -0.035em;\n}\n\n.ebox-project-page h2 {\n  margin: 52px 0 16px;\n  padding-bottom: 8px;\n  border-bottom: 2px solid #e5e7eb;\n  font-size: clamp(1.45rem, 2.6vw, 2.05rem);\n}\n\n.ebox-project-page h3 {\n  margin: 28px 0 10px;\n  font-size: 1.2rem;\n}\n\n.ebox-project-page p {\n  margin: 0 0 18px;\n}\n\n.ebox-project-page ul,\n.ebox-project-page ol {\n  margin: 0 0 20px 22px;\n  padding: 0;\n}\n\n.ebox-project-page li {\n  margin: 6px 0;\n}\n\n.ebox-project-page code {\n  padding: 0.08em 0.28em;\n  border-radius: 6px;\n  background: #eef2f7;\n  color: #111827;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 0.92em;\n}\n\n\/* Hero *\/\n\n.ebox-hero {\n  margin: 28px 0 34px;\n  padding: clamp(24px, 4vw, 44px);\n  border: 1px solid #e5e7eb;\n  border-radius: 22px;\n  background: linear-gradient(135deg, #f8fafc 0%, #ffffff 55%, #eef6ff 100%);\n  box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);\n}\n\n.ebox-hero-with-image {\n  display: grid;\n  grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.9fr);\n  align-items: center;\n  gap: 32px;\n}\n\n.ebox-hero-text {\n  min-width: 0;\n}\n\n.ebox-hero-label {\n  display: inline-block;\n  margin-bottom: 14px;\n  padding: 6px 14px;\n  border-radius: 999px;\n  background: #e0f2fe;\n  color: #075985;\n  font-size: 0.92rem;\n  font-weight: 800;\n}\n\n.ebox-hero-image {\n  min-width: 0;\n  width: 100%;\n}\n\n.ebox-hero-image img {\n  display: block;\n  width: 100%;\n  height: auto;\n  border-radius: 18px;\n  object-fit: cover;\n  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.14);\n}\n\n\/* Video *\/\n\n.ebox-video-section {\n  margin: 42px 0 46px;\n}\n\n.ebox-video-card,\n.ebox-video-box {\n  margin: 20px 0 12px;\n  padding: clamp(12px, 2vw, 18px);\n  border: 1px solid #dce3ea;\n  border-radius: 22px;\n  background: #f8fafc;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n}\n\n.ebox-video-frame,\n.ebox-video-wrapper {\n  position: relative;\n  width: 100%;\n  aspect-ratio: 16 \/ 9;\n  overflow: hidden;\n  border-radius: 18px;\n  background: #0f172a;\n}\n\n.ebox-video-frame iframe,\n.ebox-video-wrapper iframe {\n  position: absolute;\n  inset: 0;\n  display: block;\n  width: 100%;\n  height: 100%;\n  border: 0;\n}\n\n\/* Cards and grids *\/\n\n.ebox-card-grid,\n.ebox-summary-grid,\n.ebox-check-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));\n  gap: 16px;\n  margin: 20px 0 28px;\n}\n\n.ebox-info-card,\n.ebox-check-card {\n  min-width: 0;\n  padding: 18px;\n  border: 1px solid #e5e7eb;\n  border-radius: 16px;\n  background: #ffffff;\n  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);\n}\n\n.ebox-info-card strong,\n.ebox-check-card strong {\n  display: block;\n  margin-bottom: 6px;\n  color: #111827;\n}\n\n\/* Sections and images *\/\n\n.ebox-step {\n  margin: 24px 0;\n  padding: clamp(18px, 3vw, 26px);\n  border: 1px solid #e5e7eb;\n  border-radius: 18px;\n  background: #ffffff;\n  box-shadow: 0 8px 22px rgba(15, 23, 42, 0.05);\n}\n\n.ebox-step h2 {\n  margin-top: 0;\n}\n\n.ebox-section-grid {\n  display: grid;\n  grid-template-columns: minmax(0, 1.1fr) minmax(260px, 0.9fr);\n  gap: 28px;\n  align-items: center;\n  margin: 20px 0 30px;\n}\n\n.ebox-image-card,\n.ebox-svg-preview {\n  min-width: 0;\n  margin: 28px 0 34px;\n  padding: clamp(12px, 2vw, 20px);\n  border: 1px solid #dce3ea;\n  border-radius: 18px;\n  background: #f8fafc;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n  overflow: hidden;\n  text-align: center;\n}\n\n.ebox-image-card img,\n.ebox-svg-preview img {\n  display: block;\n  width: 100%;\n  max-width: 100%;\n  height: auto;\n  max-height: 72vh;\n  margin: 0 auto;\n  border-radius: 14px;\n  object-fit: contain;\n}\n\n.ebox-image-card .ebox-img-tall {\n  width: auto;\n  max-height: 520px;\n}\n\n.ebox-caption,\n.ebox-svg-caption {\n  margin: 12px 0 0;\n  color: #475569;\n  font-size: 0.95rem;\n  text-align: center;\n}\n\n\/* Notes *\/\n\n.ebox-note,\n.ebox-warning {\n  margin: 22px 0;\n  padding: 18px 20px;\n  border-radius: 12px;\n}\n\n.ebox-note {\n  border-left: 5px solid #0ea5e9;\n  background: #f0f9ff;\n}\n\n.ebox-warning {\n  border-left: 5px solid #f97316;\n  background: #fff7ed;\n}\n\n\/* Flow *\/\n\n.ebox-flow {\n  display: grid;\n  gap: 10px;\n  margin: 20px 0 26px;\n}\n\n.ebox-flow-step {\n  padding: 14px 16px;\n  border-radius: 14px;\n  background: #111827;\n  color: #ffffff;\n  font-weight: 700;\n}\n\n\/* Collapsible details *\/\n\n.ebox-details {\n  margin: 24px 0 34px;\n  border: 1px solid #e5e7eb;\n  border-radius: 18px;\n  background: #ffffff;\n  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);\n  overflow: hidden;\n}\n\n.ebox-details summary {\n  cursor: pointer;\n  padding: 16px 18px;\n  color: #111827;\n  font-weight: 800;\n  background: #f8fafc;\n  border-bottom: 1px solid #e5e7eb;\n}\n\n.ebox-details-content {\n  padding: 18px;\n}\n\n\/* Code *\/\n\n.ebox-code-block {\n  margin: 20px 0 26px;\n  border: 1px solid #d1d5db;\n  border-radius: 16px;\n  overflow: hidden;\n  background: #0b1020;\n  box-shadow: 0 10px 28px rgba(15, 23, 42, 0.16);\n}\n\n.ebox-code-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 12px;\n  padding: 12px 14px;\n  background: #111827;\n  color: #ffffff;\n}\n\n.ebox-code-title {\n  font-size: 0.95rem;\n  font-weight: 800;\n  word-break: break-word;\n}\n\n.ebox-copy-button {\n  appearance: none;\n  border: 0;\n  border-radius: 999px;\n  padding: 8px 13px;\n  background: #dbeafe;\n  color: #0f172a;\n  cursor: pointer;\n  font-weight: 800;\n  font-size: 0.85rem;\n  white-space: nowrap;\n}\n\n.ebox-copy-button:hover {\n  background: #bfdbfe;\n}\n\n.ebox-code-textarea {\n  display: block;\n  width: 100%;\n  min-height: 360px;\n  max-height: 620px;\n  padding: 18px;\n  border: 0;\n  outline: none;\n  resize: vertical;\n  background: #0b1020;\n  color: #e5e7eb;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 13px;\n  line-height: 1.55;\n  white-space: pre;\n  overflow: auto;\n}\n\n.ebox-code-textarea.small {\n  min-height: 190px;\n}\n\n.ebox-code-textarea.medium {\n  min-height: 300px;\n}\n\n.ebox-code-textarea.large {\n  min-height: 520px;\n}\n\n.ebox-small-code-box {\n  margin: 18px 0 28px;\n}\n\n.ebox-small-code {\n  width: 100%;\n  min-height: 90px;\n  max-height: 500px;\n  height: 220px;\n  padding: 16px;\n  resize: vertical;\n  overflow: auto;\n  border: 1px solid #334155;\n  border-radius: 12px;\n  background-color: #111827;\n  color: #f1f5f9;\n  font-family: Consolas, Monaco, \"Courier New\", monospace;\n  font-size: 13px;\n  line-height: 1.45;\n  white-space: pre;\n  box-sizing: border-box;\n}\n\n\/* Tables, galleries, FAQ *\/\n\n.ebox-table {\n  width: 100%;\n  border-collapse: collapse;\n  margin: 18px 0 28px;\n}\n\n.ebox-table th,\n.ebox-table td {\n  border: 1px solid #d1d5db;\n  padding: 10px;\n  text-align: left;\n  vertical-align: top;\n}\n\n.ebox-table th {\n  background: #f2f2f2;\n}\n\n.ebox-gallery {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));\n  gap: 18px;\n  margin: 22px 0 32px;\n}\n\n.ebox-faq-list {\n  display: grid;\n  gap: 14px;\n  margin: 20px 0 34px;\n}\n\n.ebox-faq-list details {\n  border: 1px solid #e5e7eb;\n  border-radius: 16px;\n  background: #ffffff;\n  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);\n  overflow: hidden;\n}\n\n.ebox-faq-list summary {\n  cursor: pointer;\n  padding: 16px 18px;\n  color: #111827;\n  font-weight: 800;\n}\n\n.ebox-faq-content {\n  padding: 0 18px 18px;\n  color: #4b5563;\n}\n\n\/* Links and final box *\/\n\n.ebox-download-link,\n.ebox-schematic-link {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  margin: 10px 0 18px;\n  padding: 10px 16px;\n  border-radius: 999px;\n  background: #dbe8ee;\n  color: #1f3a46;\n  font-size: 0.92rem;\n  font-weight: 800;\n  text-decoration: none;\n}\n\n.ebox-download-link:hover,\n.ebox-schematic-link:hover {\n  background: #c8dce5;\n  color: #132a34;\n}\n\n.ebox-final-box {\n  margin: 42px 0 24px;\n  padding: clamp(20px, 3vw, 28px);\n  border: 1px solid #e5e7eb;\n  border-radius: 20px;\n  background: #f8fafc;\n}\n\n\/* Optional project-specific accent hooks *\/\n\n.ebox-project-page--elektroclock .ebox-hero-label,\n.ebox-project-page--matrix .ebox-hero-label {\n  background: #e0f2fe;\n  color: #075985;\n}\n\n.ebox-project-page--lightbox .ebox-hero-label {\n  background: #fef3c7;\n  color: #92400e;\n}\n\n.ebox-project-page--rgb .ebox-hero-label {\n  background: #fae8ff;\n  color: #86198f;\n}\n\n.ebox-project-page--pcmon .ebox-hero-label {\n  background: #dcfce7;\n  color: #166534;\n}\n\n.ebox-project-page--ttm .ebox-hero-label {\n  background: #ffedd5;\n  color: #9a3412;\n}\n\n\/* Responsive *\/\n\n@media (max-width: 820px) {\n  .ebox-hero-with-image,\n  .ebox-section-grid {\n    grid-template-columns: 1fr;\n  }\n\n  .ebox-hero-image {\n    margin-top: 8px;\n  }\n}\n\n@media (max-width: 640px) {\n  .ebox-project-page {\n    padding: 0 16px;\n  }\n\n  .ebox-hero {\n    padding: 22px 18px;\n  }\n\n  .ebox-code-header {\n    flex-direction: column;\n    align-items: flex-start;\n  }\n\n  .ebox-copy-button {\n    width: 100%;\n  }\n\n  .ebox-code-textarea {\n    min-height: 320px;\n    font-size: 12.5px;\n  }\n\n  .ebox-image-card .ebox-img-tall {\n    width: 100%;\n    max-height: none;\n  }\n}\n<\/style>\n\n\n\n\n<div class=\"ebox-project-page ebox-project-page--elektroclock\">\n\n  <section class=\"ebox-hero ebox-hero-with-image\">\n    <div class=\"ebox-hero-text\">\n      <span class=\"ebox-hero-label\">ESP32 Project<\/span>\n      <h1>Elektroclock<\/h1>\n      <p>\n        In this project, I build a DIY smart clock using an ESP32, a P4 RGB LED matrix display, Wi-Fi time synchronization, weather data, and a BH1750 light sensor for automatic brightness control.\n      <\/p>\n      <p>\n        The clock shows the current time, date, weather icon, and temperature. It refreshes the weather data once per hour, and switches to a dimmer night mode when the room is dark.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-hero-image\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png\" alt=\"DIY Elektroclock with ESP32 and P4 RGB LED matrix display\">\n    <\/div>\n  <\/section>\n\n  <div class=\"ebox-video-card\">\n    <div class=\"ebox-video-frame\">\n      <iframe src=\"https:\/\/www.youtube.com\/embed\/Gng5UYoKUC4\" title=\"Elektroclock project video\" allowfullscreen><\/iframe>\n    <\/div>\n  <\/div>\n\n  <h2>1. Required Parts<\/h2>\n  <p>For this project, I used the following parts:<\/p>\n\n  <div class=\"ebox-card-grid\">\n    <div class=\"ebox-info-card\"><strong>Controller<\/strong>ESP32 development board<\/div>\n    <div class=\"ebox-info-card\"><strong>Display<\/strong>P4 RGB LED matrix display<\/div>\n    <div class=\"ebox-info-card\"><strong>Display wiring<\/strong>Display power cable and HUB75 ribbon cable or jumper wires<\/div>\n    <div class=\"ebox-info-card\"><strong>Prototype wiring<\/strong>Breadboard, jumper wires, and male pin headers<\/div>\n    <div class=\"ebox-info-card\"><strong>Light sensor<\/strong>BH1750 light sensor module<\/div>\n    <div class=\"ebox-info-card\"><strong>Power<\/strong>5 V power supply<\/div>\n    <div class=\"ebox-info-card\"><strong>Optional<\/strong>Custom PCB<\/div>\n    <div class=\"ebox-info-card\"><strong>Optional<\/strong>Enclosure<\/div>\n  <\/div>\n\n  <p>\n    For the first prototype, the display can be connected with jumper wires. For the final version, I recommend using a PCB and the ribbon cable that comes with the LED matrix display. This makes the wiring much cleaner and more reliable.\n  <\/p>\n\n  <h2>2. Connect the LED Matrix Display<\/h2>\n\n  <div class=\"ebox-section-grid\">\n    <div>\n      <p>\n        Start by flipping the P4 LED matrix display over so the input connector is visible. Connect the display input pins to the ESP32 according to your wiring diagram.\n      <\/p>\n      <p>\n        For the prototype, I used jumper wires and a breadboard. I recommend connecting all ground wires to one common ground rail first. This makes the wiring easier to check and ensures that the ESP32, LED matrix, and power supply share the same reference ground.\n      <\/p>\n      <p>\n        The display uses a HUB75 interface, so several signal wires are required for the RGB color channels, row selection, latch, output enable, and clock signal.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" class=\"ebox-img-tall\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/Wiring-Diagramm-Connector.png\" alt=\"Wiring diagram for ESP32 and HUB75 P4 LED matrix connector\" loading=\"lazy\">\n      <p class=\"ebox-caption\">ESP32 to HUB75 LED matrix wiring concept.<\/p>\n    <\/div>\n  <\/div>\n\n  <h2>3. Power the Display<\/h2>\n  <p>\n    The LED matrix display requires a stable 5 V supply. For very simple tests at low brightness, the display may work when powered from the ESP32 5 V pin, but this is not recommended for the final version.\n  <\/p>\n\n  <div class=\"ebox-warning\">\n    <strong>Important:<\/strong> For reliable operation, use an external 5 V power supply with enough current capacity. The display ground and the ESP32 ground must be connected together.\n  <\/div>\n\n  <h2>4. Test the Display<\/h2>\n  <p>\n    Before adding Wi-Fi, weather data, or sensors, test the LED matrix display with a simple Arduino sketch. This confirms that the wiring, display library, and ESP32 pin configuration are working correctly.\n  <\/p>\n  <p>\n    The test can show a simple text or color pattern on the display. Once this works, the hardware connection is ready for the smart clock software.\n  <\/p>\n\n  <details class=\"ebox-details\">\n    <summary>RGB Matrix Test Code<\/summary>\n    <div class=\"ebox-details-content\">\n      <p>\n        This example sketch is used to test an ESP32 with a HUB75 RGB LED matrix panel.\n        The program has two test modes. First, it cycles through 31 solid colors, changing\n        the display color once per second. After that, it switches to a shape animation mode,\n        where different geometric shapes are drawn on the matrix.\n      <\/p>\n\n      <p>\n        The pin numbers and panel resolution can be adjusted in the configuration section\n        of the code to match your own wiring and LED matrix size.\n      <\/p>\n\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">RGB Matrix Test Code<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('hub75-code', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"hub75-code\" class=\"ebox-code-textarea\" readonly>\/*\n  ESP32 + HUB75 RGB Matrix:\n  &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-\n  Test 1: cycles through 31 solid colors, changing once per second.\n  Test 2: draws a different geometric shape every second.\n\n  Wiring: update the pin numbers to match your panel\/adapter.\n*\/\n\n#include <Arduino.h>\n#include <math.h>  \/\/ for sinf\/cosf\/fmodf\n#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>  \/\/ main display library\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Pins (edit to match your wiring) &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;*\/\n#define R1_PIN 25\n#define G1_PIN 26\n#define B1_PIN 27\n#define R2_PIN 14\n#define G2_PIN 12\n#define B2_PIN 13\n#define A_PIN  23\n#define B_PIN  19\n#define C_PIN  5\n#define D_PIN  17\n#define E_PIN  -1\n#define LAT_PIN 4\n#define OE_PIN  15\n#define CLK_PIN 16\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Panel configuration &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*\/\n#define PANEL_RES_X 64\n#define PANEL_RES_Y 32\n#define PANEL_CHAIN  1\n\nHUB75_I2S_CFG mxconfig(PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN);\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Globals &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*\/\nMatrixPanel_I2S_DMA *dma_display = nullptr;\n\nstatic const uint8_t NUM_COLORS = 31;\nuint16_t PALETTE[NUM_COLORS];\n\nenum Mode : uint8_t { MODE_COLORS = 0, MODE_SHAPES = 1 };\nMode mode = MODE_COLORS;\n\nuint32_t lastTick = 0;\nuint8_t  stepIdx  = 0;\nstatic const uint8_t STEPS_PER_MODE = NUM_COLORS;\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Small helpers &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-*\/\nstatic inline uint16_t rgb888_to_565(uint8_t r, uint8_t g, uint8_t b) {\n  return ((r &#038; 0xF8) << 8) | ((g &#038; 0xFC) << 3) | (b >> 3);\n}\n\nstatic void hsv_to_rgb(float h, float s, float v, uint8_t &#038;r, uint8_t &#038;g, uint8_t &#038;b) {\n  float c = v * s;\n  float hh = fmodf(h, 360.0f) \/ 60.0f;\n  float x = c * (1.0f &#8211; fabsf(fmodf(hh, 2.0f) &#8211; 1.0f));\n  float m = v &#8211; c;\n\n  float rf = 0, gf = 0, bf = 0;\n\n  if      (0 <= hh &#038;&#038; hh < 1) { rf = c; gf = x; bf = 0; }\n  else if (1 <= hh &#038;&#038; hh < 2) { rf = x; gf = c; bf = 0; }\n  else if (2 <= hh &#038;&#038; hh < 3) { rf = 0; gf = c; bf = x; }\n  else if (3 <= hh &#038;&#038; hh < 4) { rf = 0; gf = x; bf = c; }\n  else if (4 <= hh &#038;&#038; hh < 5) { rf = x; gf = 0; bf = c; }\n  else                        { rf = c; gf = 0; bf = x; }\n\n  r = (uint8_t)((rf + m) * 255.0f + 0.5f);\n  g = (uint8_t)((gf + m) * 255.0f + 0.5f);\n  b = (uint8_t)((bf + m) * 255.0f + 0.5f);\n}\n\nstatic void build_palette() {\n  for (uint8_t i = 0; i < NUM_COLORS; i++) {\n    float h = (360.0f \/ NUM_COLORS) * i;\n    uint8_t r, g, b;\n    hsv_to_rgb(h, 1.0f, 1.0f, r, g, b);\n    PALETTE[i] = rgb888_to_565(r, g, b);\n  }\n}\n\nstatic inline int W() { return dma_display->width(); }\nstatic inline int H() { return dma_display->height(); }\n\nstatic uint16_t randomColor() {\n  return PALETTE[random(NUM_COLORS)];\n}\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Shape animation &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;*\/\nstatic void draw_shape_step(uint8_t k) {\n  dma_display->fillScreen(0);\n\n  int w = W();\n  int h = H();\n  int mind = (w < h ? w : h);\n\n  switch (k % 8) {\n    case 0: {\n      int rw = 16 + (k * 3) % (w - 16);\n      int rh = 12 + (k * 5) % (h - 12);\n\n      if (rw < 4) rw = 4;\n      if (rh < 4) rh = 4;\n\n      int x = (w - rw) \/ 2;\n      int y = (h - rh) \/ 2;\n\n      dma_display->drawRect(x, y, rw, rh, randomColor());\n      break;\n    }\n\n    case 1: {\n      int rw = 10 + (k * 4) % (w &#8211; 10);\n      int rh = 10 + (k * 2) % (h &#8211; 10);\n\n      rw = max(4, rw);\n      rh = max(4, rh);\n\n      int x = (k * 7) % max(1, (w &#8211; rw));\n      int y = (k * 5) % max(1, (h &#8211; rh));\n\n      dma_display->fillRect(x, y, rw, rh, randomColor());\n      break;\n    }\n\n    case 2: {\n      int r  = 6 + (k * 3) % max(6, (mind \/ 2 &#8211; 2));\n      int cx = 8 + (k * 4) % max(1, (w &#8211; 16));\n      int cy = 8 + (k * 6) % max(1, (h &#8211; 16));\n\n      dma_display->drawCircle(cx, cy, r, randomColor());\n      break;\n    }\n\n    case 3: {\n      int r  = 6 + (k * 5) % max(6, (mind \/ 2 &#8211; 4));\n      int cx = (w \/ 2) + ((int)k % 9) &#8211; 4;\n      int cy = (h \/ 2) + ((int)k % 11) &#8211; 5;\n\n      dma_display->fillCircle(cx, cy, r, randomColor());\n      break;\n    }\n\n    case 4: {\n      int x0 = (k * 7) % w;\n      int y0 = (k * 3) % h;\n      int x1 = (x0 + 20 + (k * 5) % 28) % w;\n      int y1 = (y0 + 15 + (k * 4) % 25) % h;\n      int x2 = (x0 + 10 + (k * 6) % 30) % w;\n      int y2 = (y0 + 25 + (k * 2) % 20) % h;\n\n      dma_display->drawTriangle(x0, y0, x1, y1, x2, y2, randomColor());\n      break;\n    }\n\n    case 5: {\n      int x0 = w \/ 2;\n      int y0 = 6 + (k * 3) % max(6, (h &#8211; 12));\n      int x1 = 4 + (k * 5) % max(6, (w &#8211; 8));\n      int y1 = 4;\n      int x2 = w &#8211; 5;\n      int y2 = h &#8211; 5;\n\n      dma_display->fillTriangle(x0, y0, x1, y1, x2, y2, randomColor());\n      break;\n    }\n\n    case 6: {\n      int rw = 18 + (k * 2) % max(18, (w &#8211; 4));\n      int rh = 12 + (k * 3) % max(12, (h &#8211; 4));\n\n      rw = min(rw, w &#8211; 2);\n      rh = min(rh, h &#8211; 2);\n\n      int x = (w &#8211; rw) \/ 2;\n      int y = (h &#8211; rh) \/ 2;\n      int rr = 4 + (k % 8);\n\n      dma_display->drawRoundRect(x, y, rw, rh, rr, randomColor());\n      break;\n    }\n\n    default: {\n      int cx = w \/ 2;\n      int cy = h \/ 2;\n      int R  = (mind \/ 2) &#8211; 2;\n\n      for (int i = 0; i < 8; i++) {\n        float ang = (TWO_PI \/ 8.0f) * (i + k * 0.25f);\n        int x = cx + (int)(R * cosf(ang));\n        int y = cy + (int)(R * sinf(ang));\n\n        dma_display->drawLine(cx, cy, x, y, PALETTE[(i + k) % NUM_COLORS]);\n      }\n\n      break;\n    }\n  }\n}\n\n\/*&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; Arduino setup\/loop &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;*\/\nvoid setup() {\n  Serial.begin(115200);\n  delay(150);\n\n  mxconfig.gpio.r1 = R1_PIN;\n  mxconfig.gpio.g1 = G1_PIN;\n  mxconfig.gpio.b1 = B1_PIN;\n\n  mxconfig.gpio.r2 = R2_PIN;\n  mxconfig.gpio.g2 = G2_PIN;\n  mxconfig.gpio.b2 = B2_PIN;\n\n  mxconfig.gpio.a = A_PIN;\n  mxconfig.gpio.b = B_PIN;\n  mxconfig.gpio.c = C_PIN;\n  mxconfig.gpio.d = D_PIN;\n  mxconfig.gpio.e = E_PIN;\n\n  mxconfig.gpio.lat = LAT_PIN;\n  mxconfig.gpio.oe  = OE_PIN;\n  mxconfig.gpio.clk = CLK_PIN;\n\n  \/\/ mxconfig.driver = HUB75_I2S_CFG::FM6126A;\n\n  dma_display = new MatrixPanel_I2S_DMA(mxconfig);\n  dma_display->begin();\n\n  dma_display->setBrightness8(140);\n  dma_display->fillScreen(0);\n\n  build_palette();\n  randomSeed((uint32_t)micros());\n\n  lastTick = millis();\n  stepIdx  = 0;\n  mode     = MODE_COLORS;\n}\n\nvoid loop() {\n  uint32_t now = millis();\n\n  if (now &#8211; lastTick >= 1000) {\n    lastTick = now;\n\n    if (mode == MODE_COLORS) {\n      dma_display->fillScreen(PALETTE[stepIdx]);\n      stepIdx++;\n\n      if (stepIdx >= STEPS_PER_MODE) {\n        stepIdx = 0;\n        mode = MODE_SHAPES;\n      }\n    } else {\n      draw_shape_step(stepIdx);\n      stepIdx++;\n\n      if (stepIdx >= STEPS_PER_MODE) {\n        stepIdx = 0;\n        mode = MODE_COLORS;\n      }\n    }\n  }\n}\n  <\/textarea>\n      <\/div>\n\n      <h3>How the code works<\/h3>\n\n      <p>\n        The sketch starts by defining the GPIO pins used to connect the ESP32 to the HUB75\n        RGB matrix panel. These pins must match your own wiring. The panel size is defined\n        with <code>PANEL_RES_X<\/code> and <code>PANEL_RES_Y<\/code>. For a typical 64 \u00d7 32 matrix,\n        the values are set to 64 and 32.\n      <\/p>\n\n      <p>\n        The function <code>build_palette()<\/code> creates 31 different colors. These colors are\n        generated using HSV color values and then converted into RGB565 format, which is\n        the color format used by the matrix display library.\n      <\/p>\n\n      <p>\n        In the main loop, the display changes once per second. In the first mode, the whole\n        matrix is filled with one solid color. After all 31 colors have been shown, the\n        program switches to the second mode, where it draws different shapes such as\n        rectangles, circles, triangles, rounded rectangles, and starburst lines.\n      <\/p>\n\n      <p>\n        The loop is non-blocking because it uses <code>millis()<\/code> instead of <code>delay()<\/code>.\n        This makes it easier to add other features later, such as buttons, sensors,\n        Wi-Fi, or animations.\n      <\/p>\n    <\/div>\n  <\/details>\n\n  <h2>5. Designing the Clock Layout<\/h2>\n\n  <div class=\"ebox-section-grid\">\n    <div>\n      <p>\n        After the display was working, I tested different layout ideas for the clock. The goal was to make the most important information easy to read, even from a distance.\n      <\/p>\n      <p>The display can show several types of information:<\/p>\n      <ul>\n        <li>Current time<\/li>\n        <li>Date<\/li>\n        <li>Temperature<\/li>\n        <li>Weather icon<\/li>\n      <\/ul>\n      <p>\n        Because the display resolution is limited, the layout has to be simple and clear. Large digits work best for the time, while smaller text can be used for weather or status information.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV2_No_backGround-1024x1024.png\" alt=\"Elektroclock LED matrix display layout\" loading=\"lazy\">\n      <p class=\"ebox-caption\">Clock layout with time, date, temperature, and weather icon.<\/p>\n    <\/div>\n  <\/div>\n\n  <h2>6. Wi-Fi, Time, and Weather Data<\/h2>\n\n  <p>\n    The next step was to connect the ESP32 to Wi-Fi. Once connected, the ESP32 can synchronize the time and download weather data.\n  <\/p>\n  <p>\n    For the weather information, I used OpenWeatherMap. To use this service, an API key and a location are required. These values are entered in the code as placeholders.\n  <\/p>\n\n  <div class=\"ebox-flow\">\n    <div class=\"ebox-flow-step\">ESP32 connects to Wi-Fi<\/div>\n    <div class=\"ebox-flow-step\">NTP synchronizes the local time<\/div>\n    <div class=\"ebox-flow-step\">OpenWeatherMap provides weather data<\/div>\n    <div class=\"ebox-flow-step\">The LED matrix shows time, date, weather icon, and temperature<\/div>\n  <\/div>\n\n  <p>\n    The final Elektroclock code downloads the time and weather data at startup. The weather information is then updated automatically every 60 minutes. This is enough for a clock project because weather data does not need to be refreshed every few seconds.\n  <\/p>\n\n  <h2>7. Automatic Brightness Control with BH1750<\/h2>\n\n  <div class=\"ebox-section-grid\">\n    <div>\n      <p>\n        During testing, I noticed that the LED matrix was too bright in a dark room. To solve this, I added a BH1750 light sensor. This sensor measures the ambient light level and allows the ESP32 to adjust the display brightness automatically.\n      <\/p>\n      <p>The BH1750 communicates via I2C, so only a few wires are required:<\/p>\n      <ul>\n        <li>VCC<\/li>\n        <li>GND<\/li>\n        <li>SDA<\/li>\n        <li>SCL<\/li>\n      <\/ul>\n      <p>\n        The brightness value can then be calculated from the measured light level. In a bright room, the display brightness increases. In a dark room, the brightness is reduced to make the clock more comfortable to look at.\n      <\/p>\n    <\/div>\n\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Wiring_LighSensor-1024x764.png\" alt=\"BH1750 light sensor wiring for Elektroclock\" loading=\"lazy\">\n      <p class=\"ebox-caption\">BH1750 wiring for automatic brightness control.<\/p>\n    <\/div>\n  <\/div>\n\n  <h2>8. Final Software Structure<\/h2>\n\n  <p>The final ElektroClock software is split into several logical parts:<\/p>\n\n  <div class=\"ebox-card-grid\">\n    <div class=\"ebox-info-card\">\n      <strong>Main Loop<\/strong>\n      The loop updates the display once per second. Weather data is refreshed approximately once per hour. This structure is important because the clock combines multiple functions: display control, Wi-Fi communication, online weather data, time handling, and sensor input.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Configuration<\/strong>\n      This section contains the display pin mapping, matrix size, Wi-Fi settings, OpenWeatherMap settings, NTP server, and timezone information.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Wi-Fi Connection<\/strong>\n      The ESP32 scans available networks, selects the correct access point, and connects to Wi-Fi.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Time Synchronization<\/strong>\n      The clock uses NTP to get the current local time. The timezone is configured for Central European Time and daylight saving time.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Display Drawing Functions<\/strong>\n      Custom helper functions draw bitmap icons, custom digits, the time, date, year, weather icon, and temperature.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Weather Update<\/strong>\n      The ESP32 sends an HTTP request to OpenWeatherMap, reads the JSON response, extracts the weather ID and temperature, and selects the correct icon.\n    <\/div>\n    <div class=\"ebox-info-card\">\n      <strong>Brightness and Night Mode<\/strong>\n      The BH1750 light sensor measures the room brightness. The code then adjusts the LED matrix brightness and switches to dimmer colors at night.\n    <\/div>\n  <\/div>\n\n  <p>\n    You can find the full project code below. Create a new Arduino IDE project and name it <strong>ElektroClock<\/strong>. Paste the code from <strong>ElektroClock.ino<\/strong> into the main Arduino sketch file. Then create two header files named <strong>smart_clock.h<\/strong> and <strong>custom_fonts.h<\/strong>. Copy and paste the corresponding code into each header file. After that, you can compile and upload the project using the Arduino IDE.\n  <\/p>\n\n  <details class=\"ebox-details\" open>\n    <summary>ElektroClock.ino<\/summary>\n    <div class=\"ebox-details-content\">\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">ElektroClock.ino<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('elektroclock-ino', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"elektroclock-ino\" class=\"ebox-code-textarea\" readonly>#include &lt;Arduino.h&gt;\n#include &lt;ESP32-HUB75-MatrixPanel-I2S-DMA.h&gt;\n\n#include &lt;smart_clock.h&gt;\n#include &lt;custom_fonts.h&gt;\n\n#include &lt;WiFi.h&gt;\n#include &lt;HTTPClient.h&gt;\n#include &#8220;time.h&#8221;\n\n#include &lt;BLEDevice.h&gt;\n#include &lt;BLEUtils.h&gt;\n#include &lt;BLEServer.h&gt;\n\n#include &lt;string.h&gt;\n#include &lt;ArduinoJson.h&gt;\n\n#include &lt;Wire.h&gt;\n#include &lt;BH1750.h&gt;\n\n\/\/ &#8212;&#8212;&#8212;- I2C \/ Sensor &#8212;&#8212;&#8212;-\nTwoWire I2C_1 = TwoWire(0);\nBH1750 lightMeter;\n\n\/\/ BH1750\n#define BH1750_I2CADDR 0x23\n#define BH1750_POWER_DOWN 0x00\n#define BH1750_POWER_ON 0x01\n#define BH1750_RESET 0x07\n#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10\n\n\/\/ &#8212;&#8212;&#8212;- RGB DISPLAY PINS &#8212;&#8212;&#8212;-\n#define R1_PIN 25\n#define G1_PIN 26\n#define B1_PIN 27\n#define R2_PIN 14\n#define G2_PIN 12\n#define B2_PIN 13\n#define A_PIN 23\n#define B_PIN 19\n#define C_PIN 5\n#define D_PIN 17\n#define E_PIN -1\n#define LAT_PIN 4\n#define OE_PIN 15\n#define CLK_PIN 16\n\n\/\/ &#8212;&#8212;&#8212;- MATRIX CONFIG &#8212;&#8212;&#8212;-\n#define PANEL_RES_X 64\n#define PANEL_RES_Y 32\n#define PANEL_CHAIN 1\n\n\/\/ &#8212;&#8212;&#8212;- COLORS &#8212;&#8212;&#8212;-\n#define TFT_BLACK 0x0000\n#define TFT_NAVY 0x000F\n#define TFT_DARKGREEN 0x03E0\n#define TFT_DARKCYAN 0x03EF\n#define TFT_MAROON 0x7800\n#define TFT_PURPLE 0x780F\n#define TFT_OLIVE 0x7BE0\n#define TFT_LIGHTGREY 0xD69A\n#define TFT_DARKGREY 0x7BEF\n#define TFT_BLUE 0x001F\n#define TFT_GREEN 0x07E0\n#define TFT_CYAN 0x07FF\n#define TFT_RED 0xF800\n#define TFT_MAGENTA 0xF81F\n#define TFT_YELLOW 0xFFE0\n#define TFT_WHITE 0xFFFF\n#define TFT_ORANGE 0xFDA0\n#define TFT_GREENYELLOW 0xB7E0\n#define TFT_PINK 0xFE19\n#define TFT_BROWN 0x9A60\n#define TFT_GOLD 0xFEA0\n#define TFT_SILVER 0xC618\n#define TFT_SKYBLUE 0x867D\n#define TFT_VIOLET 0x915C\n\nMatrixPanel_I2S_DMA *dma_display = nullptr;\nHUB75_I2S_CFG mxconfig(PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN);\n\n\/\/ &#8212;&#8212;&#8212;- Sprites\/Positionen &#8212;&#8212;&#8212;-\nuint8_t spriteHeight = 7;\nuint8_t spriteWidht  = 7;\nuint8_t spriteScale  = 3;\nuint16_t bitmap_offset_x  = 0;\nuint16_t bitmap_offset_y  = 0;\nuint16_t bitmap_offset_x2 = 24;\nuint16_t bitmap_offset_y2 = 0;\nuint8_t move_amount = 0;\n\n\/\/ &#8212;&#8212;&#8212;- WiFi \/ API &#8212;&#8212;&#8212;-\nconst char *ssid = &#8220;wifi_name&#8221;;\nconst char *password = &#8220;Wifi_password&#8221;;\n\n\/\/ OpenWeatherMap\nconst char *weatherApiKey = &#8220;open_weather_map_api_key&#8221;;\nconst char *weatherLocation = &#8220;Town,country&#8221;;\n\n\/\/ &#8212;&#8212;&#8212;- NTP &#8212;&#8212;&#8212;-\nconst char *ntpServer = &#8220;pool.ntp.org&#8221;;\nconst char *TZ_INFO   = &#8220;CET-1CEST,M3.5.0\/2,M10.5.0\/3&#8221;;\n\n\/\/ &#8212;&#8212;&#8212;- Global Variables &#8212;&#8212;&#8212;-\nchar timeString[6];\nuint8_t dth = 0;\n\nuint32_t weather_update_cnt = 0;\nString payload;\n\nint id = 800;\nint temperature = 0;\nbool weatherValid = false;\n\nfloat lightVal = 0;\n\nuint16_t color_xbm565      = TFT_CYAN;\nuint16_t color_daymon      = TFT_BLUE;\nuint16_t color_temperature = TFT_YELLOW;\n\n\/\/ =====================================================\n\/\/ DST \/ Time Helpers\n\/\/ =====================================================\n\nbool isSummertime() {\n  struct tm t;\n  if (!getLocalTime(&amp;t)) return false;\n  return (t.tm_isdst &gt; 0);\n}\n\nconst char* tzAbbrev() {\n  static char buf[8];\n  struct tm t;\n  if (!getLocalTime(&amp;t)) {\n    buf[0] = 0;\n    return buf;\n  }\n\n  strftime(buf, sizeof(buf), &#8220;%Z&#8221;, &amp;t);\n  return buf;\n}\n\n\/\/ =====================================================\n\/\/ WiFi Helper Functions\n\/\/ =====================================================\n\nString authModeToText(wifi_auth_mode_t auth) {\n  switch (auth) {\n    case WIFI_AUTH_OPEN: return &#8220;OPEN&#8221;;\n    case WIFI_AUTH_WEP: return &#8220;WEP&#8221;;\n    case WIFI_AUTH_WPA_PSK: return &#8220;WPA&#8221;;\n    case WIFI_AUTH_WPA2_PSK: return &#8220;WPA2&#8221;;\n    case WIFI_AUTH_WPA_WPA2_PSK: return &#8220;WPA\/WPA2&#8221;;\n    case WIFI_AUTH_WPA2_ENTERPRISE: return &#8220;WPA2 Enterprise&#8221;;\n    case WIFI_AUTH_WPA3_PSK: return &#8220;WPA3&#8221;;\n    case WIFI_AUTH_WPA2_WPA3_PSK: return &#8220;WPA2\/WPA3&#8221;;\n    default: return &#8220;UNKNOWN&#8221;;\n  }\n}\n\nString wifiStatusToText(wl_status_t status) {\n  switch (status) {\n    case WL_IDLE_STATUS: return &#8220;WL_IDLE_STATUS&#8221;;\n    case WL_NO_SSID_AVAIL: return &#8220;WL_NO_SSID_AVAIL&#8221;;\n    case WL_SCAN_COMPLETED: return &#8220;WL_SCAN_COMPLETED&#8221;;\n    case WL_CONNECTED: return &#8220;WL_CONNECTED&#8221;;\n    case WL_CONNECT_FAILED: return &#8220;WL_CONNECT_FAILED&#8221;;\n    case WL_CONNECTION_LOST: return &#8220;WL_CONNECTION_LOST&#8221;;\n    case WL_DISCONNECTED: return &#8220;WL_DISCONNECTED&#8221;;\n    default: return &#8220;UNKNOWN&#8221;;\n  }\n}\n\nbool connectWiFiLocked(uint32_t timeout_ms = 30000) {\n  Serial.println();\n  Serial.println(&#8220;Starting locked WiFi connection&#8230;&#8221;);\n  Serial.print(&#8220;Target SSID: &#8220;);\n  Serial.println(ssid);\n\n  WiFi.mode(WIFI_STA);\n  WiFi.setSleep(false);\n  WiFi.persistent(false);\n\n  WiFi.disconnect(true);\n  delay(1000);\n\n  Serial.println(&#8220;Scanning WiFi networks&#8230;&#8221;);\n\n  int n = WiFi.scanNetworks(false, true);\n\n  if (n &lt;= 0) {\n    Serial.println(&#8220;No WiFi networks found.&#8221;);\n    return false;\n  }\n\n  int targetIndex = -1;\n  int bestRSSI = -999;\n\n  for (int i = 0; i &lt; n; i++) {\n    Serial.print(i);\n    Serial.print(&#8221; | SSID: &#8220;);\n    Serial.print(WiFi.SSID(i));\n    Serial.print(&#8221; | RSSI: &#8220;);\n    Serial.print(WiFi.RSSI(i));\n    Serial.print(&#8221; dBm | CH: &#8220;);\n    Serial.print(WiFi.channel(i));\n    Serial.print(&#8221; | Auth: &#8220;);\n    Serial.print(authModeToText(WiFi.encryptionType(i)));\n    Serial.print(&#8221; | BSSID: &#8220;);\n    Serial.println(WiFi.BSSIDstr(i));\n\n    if (WiFi.SSID(i) == ssid &amp;&amp; WiFi.RSSI(i) &gt; bestRSSI) {\n      targetIndex = i;\n      bestRSSI = WiFi.RSSI(i);\n    }\n  }\n\n  if (targetIndex &lt; 0) {\n    Serial.println();\n    Serial.println(&#8220;Target SSID not found during scan.&#8221;);\n    WiFi.scanDelete();\n    return false;\n  }\n\n  int32_t targetChannel = WiFi.channel(targetIndex);\n\n  uint8_t targetBSSID[6];\n  memcpy(targetBSSID, WiFi.BSSID(targetIndex), 6);\n\n  Serial.println();\n  Serial.println(&#8220;Selected target access point:&#8221;);\n  Serial.print(&#8220;SSID: &#8220;);\n  Serial.println(WiFi.SSID(targetIndex));\n  Serial.print(&#8220;Channel: &#8220;);\n  Serial.println(targetChannel);\n  Serial.print(&#8220;BSSID: &#8220;);\n  Serial.println(WiFi.BSSIDstr(targetIndex));\n  Serial.print(&#8220;RSSI: &#8220;);\n  Serial.print(WiFi.RSSI(targetIndex));\n  Serial.println(&#8221; dBm&#8221;);\n  Serial.print(&#8220;Auth: &#8220;);\n  Serial.println(authModeToText(WiFi.encryptionType(targetIndex)));\n\n  WiFi.scanDelete();\n  delay(500);\n\n  Serial.println();\n  Serial.println(&#8220;Connecting to locked BSSID\/channel&#8230;&#8221;);\n\n  WiFi.begin(ssid, password, targetChannel, targetBSSID, true);\n\n  uint32_t startAttempt = millis();\n\n  while (WiFi.status() != WL_CONNECTED &amp;&amp; millis() &#8211; startAttempt &lt; timeout_ms) {\n    Serial.print(&#8220;.&#8221;);\n    delay(500);\n  }\n\n  Serial.println();\n\n  if (WiFi.status() == WL_CONNECTED) {\n    Serial.println(&#8220;WiFi connected successfully.&#8221;);\n    Serial.print(&#8220;IP address: &#8220;);\n    Serial.println(WiFi.localIP());\n    Serial.print(&#8220;Gateway: &#8220;);\n    Serial.println(WiFi.gatewayIP());\n    Serial.print(&#8220;DNS: &#8220;);\n    Serial.println(WiFi.dnsIP());\n    Serial.print(&#8220;RSSI: &#8220;);\n    Serial.print(WiFi.RSSI());\n    Serial.println(&#8221; dBm&#8221;);\n    return true;\n  }\n\n  Serial.println(&#8220;WiFi connection failed.&#8221;);\n  Serial.print(&#8220;Final WiFi status: &#8220;);\n  Serial.println(wifiStatusToText(WiFi.status()));\n\n  return false;\n}\n\n\/\/ =====================================================\n\/\/ Display Helper Functions\n\/\/ =====================================================\n\nvoid drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t color = 0) {\n  if (color == 0) color = color_xbm565;\n\n  if (width % 8 != 0) {\n    width = ((width \/ 8) + 1) * 8;\n  }\n\n  for (int i = 0; i &lt; width * height \/ 8; i++) {\n    unsigned char charColumn = pgm_read_byte(xbm + i);\n\n    for (int j = 0; j &lt; 8; j++) {\n      int targetX = (i * 8 + j) % width + x;\n      int targetY = (8 * i \/ width) + y;\n\n      if (bitRead(charColumn, j)) {\n        dma_display-&gt;drawPixel(targetX, targetY, color);\n      }\n    }\n  }\n}\n\nvoid draw_digit(int x, int y, int width, int height, const char *xbm, uint16_t color = 0) {\n  if (color == 0) color = color_xbm565;\n\n  if (width % 8 != 0) {\n    width = ((width \/ 8) + 1) * 8;\n  }\n\n  for (int i = 0; i &lt; width * height \/ 8; i++) {\n    unsigned char charColumn = pgm_read_byte(xbm + i);\n\n    for (int j = 0; j &lt; 8; j++) {\n      int targetX = (i * 8 + j) % width + x;\n      int targetY = (8 * i \/ width) + y;\n\n      if (bitRead(charColumn, j)) {\n        dma_display-&gt;drawPixel(targetX, targetY, color);\n      }\n    }\n  }\n}\n\n\/\/ =====================================================\n\/\/ Time Display\n\/\/ =====================================================\n\nvoid draw_time_clock() {\n  struct tm timeinfo;\n\n  if (!getLocalTime(&amp;timeinfo)) {\n    return;\n  }\n\n  strftime(timeString, sizeof(timeString), &#8220;%H:%M&#8221;, &amp;timeinfo);\n\n  uint8_t hour   = atoi(timeString);\n  uint8_t minute = atoi(timeString + 3);\n\n  dth = hour;\n\n  int digith[2] = {0, 0};\n  int digitm[2] = {0, 0};\n\n  uint8_t cnt_h = 0;\n  uint8_t cnt_m = 0;\n\n  if (hour == 0) {\n    digith[0] = 0;\n    digith[1] = 0;\n  }\n\n  while (hour &gt; 0) {\n    digith[cnt_h++] = hour % 10;\n    hour \/= 10;\n  }\n\n  if (minute == 0) {\n    digitm[0] = 0;\n    digitm[1] = 0;\n  }\n\n  while (minute &gt; 0) {\n    digitm[cnt_m++] = minute % 10;\n    minute \/= 10;\n  }\n\n  if (digith[1] == 0) draw_digit(24, 0, 8, 12, zero);\n  if (digith[1] == 1) draw_digit(24, 0, 8, 12, one);\n  if (digith[1] == 2) draw_digit(24, 0, 8, 12, two);\n  if (digith[1] == 3) draw_digit(24, 0, 8, 12, three);\n  if (digith[1] == 4) draw_digit(24, 0, 8, 12, four);\n  if (digith[1] == 5) draw_digit(24, 0, 8, 12, five);\n  if (digith[1] == 6) draw_digit(24, 0, 8, 12, six);\n  if (digith[1] == 7) draw_digit(24, 0, 8, 12, seven);\n  if (digith[1] == 8) draw_digit(24, 0, 8, 12, eight);\n  if (digith[1] == 9) draw_digit(24, 0, 8, 12, nine);\n\n  if (digith[0] == 0) draw_digit(32, 0, 8, 12, zero);\n  if (digith[0] == 1) draw_digit(32, 0, 8, 12, one);\n  if (digith[0] == 2) draw_digit(32, 0, 8, 12, two);\n  if (digith[0] == 3) draw_digit(32, 0, 8, 12, three);\n  if (digith[0] == 4) draw_digit(32, 0, 8, 12, four);\n  if (digith[0] == 5) draw_digit(32, 0, 8, 12, five);\n  if (digith[0] == 6) draw_digit(32, 0, 8, 12, six);\n  if (digith[0] == 7) draw_digit(32, 0, 8, 12, seven);\n  if (digith[0] == 8) draw_digit(32, 0, 8, 12, eight);\n  if (digith[0] == 9) draw_digit(32, 0, 8, 12, nine);\n\n  draw_digit(40, 0, 8, 12, colon);\n\n  if (digitm[1] == 0) draw_digit(48, 0, 8, 12, zero);\n  if (digitm[1] == 1) draw_digit(48, 0, 8, 12, one);\n  if (digitm[1] == 2) draw_digit(48, 0, 8, 12, two);\n  if (digitm[1] == 3) draw_digit(48, 0, 8, 12, three);\n  if (digitm[1] == 4) draw_digit(48, 0, 8, 12, four);\n  if (digitm[1] == 5) draw_digit(48, 0, 8, 12, five);\n  if (digitm[1] == 6) draw_digit(48, 0, 8, 12, six);\n  if (digitm[1] == 7) draw_digit(48, 0, 8, 12, seven);\n  if (digitm[1] == 8) draw_digit(48, 0, 8, 12, eight);\n  if (digitm[1] == 9) draw_digit(48, 0, 8, 12, nine);\n\n  if (digitm[0] == 0) draw_digit(56, 0, 8, 12, zero);\n  if (digitm[0] == 1) draw_digit(56, 0, 8, 12, one);\n  if (digitm[0] == 2) draw_digit(56, 0, 8, 12, two);\n  if (digitm[0] == 3) draw_digit(56, 0, 8, 12, three);\n  if (digitm[0] == 4) draw_digit(56, 0, 8, 12, four);\n  if (digitm[0] == 5) draw_digit(56, 0, 8, 12, five);\n  if (digitm[0] == 6) draw_digit(56, 0, 8, 12, six);\n  if (digitm[0] == 7) draw_digit(56, 0, 8, 12, seven);\n  if (digitm[0] == 8) draw_digit(56, 0, 8, 12, eight);\n  if (digitm[0] == 9) draw_digit(56, 0, 8, 12, nine);\n}\n\nvoid draw_time_daymon() {\n  dma_display-&gt;setTextSize(1);\n  dma_display-&gt;setTextWrap(false);\n  dma_display-&gt;setCursor(32, 13);\n  dma_display-&gt;setTextColor(color_daymon);\n\n  struct tm timeinfo;\n\n  if (!getLocalTime(&amp;timeinfo)) {\n    return;\n  }\n\n  dma_display-&gt;println(&amp;timeinfo, &#8220;%d %m %Y&#8221;);\n}\n\nvoid draw_time_year() {\n  dma_display-&gt;setTextSize(1);\n  dma_display-&gt;setTextWrap(false);\n  dma_display-&gt;setCursor(36, 21);\n  dma_display-&gt;setTextColor(color_daymon);\n\n  struct tm timeinfo;\n\n  if (!getLocalTime(&amp;timeinfo)) {\n    return;\n  }\n\n  dma_display-&gt;println(&amp;timeinfo, &#8220;%Y&#8221;);\n}\n\n\/\/ =====================================================\n\/\/ Weather\n\/\/ =====================================================\n\nString makeWeatherUrl() {\n  String url = &#8220;http:\/\/api.openweathermap.org\/data\/2.5\/weather?q=&#8221;;\n  url += weatherLocation;\n  url += &#8220;&amp;APPID=&#8221;;\n  url += weatherApiKey;\n  return url;\n}\n\nbool fetchWeatherData() {\n  if (WiFi.status() != WL_CONNECTED) {\n    Serial.println(&#8220;WiFi disconnected before weather update. Reconnecting&#8230;&#8221;);\n\n    if (!connectWiFiLocked()) {\n      Serial.println(&#8220;Reconnect failed. Skipping weather update.&#8221;);\n      return false;\n    }\n  }\n\n  String url = makeWeatherUrl();\n\n  HTTPClient http;\n  http.begin(url);\n\n  int httpCode = http.GET();\n\n  if (httpCode &lt;= 0) {\n    Serial.print(&#8220;Error in HTTP request. Code: &#8220;);\n    Serial.println(httpCode);\n    http.end();\n    return false;\n  }\n\n  payload = http.getString();\n\n  Serial.println(&#8220;Weather data:&#8221;);\n  Serial.println(payload);\n\n  http.end();\n\n  StaticJsonDocument&lt;1024&gt; doc;\n  DeserializationError error = deserializeJson(doc, payload);\n\n  if (error) {\n    Serial.print(&#8220;Error parsing JSON data: &#8220;);\n    Serial.println(error.c_str());\n    return false;\n  }\n\n  float tempKelvin = doc[&#8220;main&#8221;][&#8220;temp&#8221;] | 273.15;\n  temperature = (int)(tempKelvin &#8211; 273.15 + 0.5);\n\n  id = doc[&#8220;weather&#8221;][0][&#8220;id&#8221;] | 800;\n\n  weatherValid = true;\n\n  Serial.print(&#8220;Weather ID: &#8220;);\n  Serial.println(id);\n  Serial.print(&#8220;Temperature: &#8220;);\n  Serial.print(temperature);\n  Serial.println(&#8221; C&#8221;);\n\n  return true;\n}\n\nvoid draw_weather() {\n  if (!weatherValid) {\n    return;\n  }\n\n  if ((id &gt;= 200) &amp;&amp; (id &lt;= 299)) {\n    drawXbm565(1, 1, 16, 16, thunder_bits);\n  }\n\n  if ((id &gt;= 300) &amp;&amp; (id &lt;= 599)) {\n    drawXbm565(1, 1, 16, 16, rain_bits);\n  }\n\n  if ((id &gt;= 600) &amp;&amp; (id &lt;= 699)) {\n    drawXbm565(1, 1, 16, 16, snow_bits);\n  }\n\n  if ((id &gt;= 700) &amp;&amp; (id &lt;= 799)) {\n    drawXbm565(1, 1, 16, 16, mist_bits);\n  }\n\n  if ((id &gt;= 801) &amp;&amp; (id &lt;= 899)) {\n    drawXbm565(1, 1, 16, 16, clouds_bits);\n  }\n\n  if ((id == 800) &amp;&amp; (dth &lt; 20) &amp;&amp; (dth &gt;= 5)) {\n    drawXbm565(1, 1, 16, 16, sun_bits);\n  }\n\n  if ((id == 800) &amp;&amp; ((dth &gt;= 20) || (dth &lt; 5))) {\n    drawXbm565(1, 1, 16, 16, moon_bits);\n  }\n\n  dma_display-&gt;setTextSize(1);\n  dma_display-&gt;setTextWrap(false);\n  dma_display-&gt;setCursor(4, 21);\n  dma_display-&gt;setTextColor(color_temperature);\n  dma_display-&gt;println(temperature);\n\n  dma_display-&gt;setCursor(17, 21);\n  dma_display-&gt;println(&#8220;C&#8221;);\n}\n\nvoid get_weather() {\n  if (weather_update_cnt == 0) {\n    fetchWeatherData();\n  }\n\n  draw_weather();\n\n  weather_update_cnt++;\n\n  if (weather_update_cnt &gt;= 3600) {\n    weather_update_cnt = 0;\n  }\n}\n\n\/\/ =====================================================\n\/\/ Brightness \/ Color\n\/\/ =====================================================\n\nvoid set_colour() {\n  uint8_t brightness = 90;\n\n  if (lightVal &lt; 100) {\n    brightness = 15;\n  } else if (lightVal &lt; 200) {\n    brightness = 25;\n  } else if (lightVal &lt; 300) {\n    brightness = 35;\n  } else if (lightVal &lt; 400) {\n    brightness = 50;\n  } else if (lightVal &lt; 500) {\n    brightness = 70;\n  } else {\n    brightness = 90;\n  }\n\n  dma_display-&gt;setBrightness8(brightness);\n\n  if ((dth &lt; 20) &amp;&amp; (dth &gt;= 8)) {\n    color_xbm565      = TFT_CYAN;\n    color_daymon      = TFT_BLUE;\n    color_temperature = TFT_YELLOW;\n  } else {\n    uint16_t dim = dma_display-&gt;color565(0, 25, 50);\n\n    color_xbm565      = dim;\n    color_daymon      = dim;\n    color_temperature = dim;\n  }\n\n  Serial.printf(&#8220;Mode: %s | Brightness: %d | DST: %s (%s)\\n&#8221;,\n                (dth &lt; 20 &amp;&amp; dth &gt;= 5) ? &#8220;DAY&#8221; : &#8220;NIGHT&#8221;,\n                brightness,\n                isSummertime() ? &#8220;Summertime&#8221; : &#8220;Wintertime&#8221;,\n                tzAbbrev());\n}\n\n\/\/ =====================================================\n\/\/ SETUP\n\/\/ =====================================================\n\nvoid setup() {\n  Serial.begin(115200);\n  delay(1000);\n\n  Serial.println();\n  Serial.println(&#8220;Smart Clock starting&#8230;&#8221;);\n  Serial.println(&#8220;=======================&#8221;);\n\n  \/\/ WiFi first, using scan + locked BSSID\/channel\n  bool wifiOK = connectWiFiLocked();\n\n  \/\/ BH1750\n  I2C_1.begin(21, 22);\n\n  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &amp;I2C_1)) {\n    Serial.println(&#8220;Error initializing BH1750&#8221;);\n  } else {\n    Serial.println(&#8220;BH1750 initialized&#8221;);\n  }\n\n  \/\/ Display\n  Serial.println(&#8220;&#8230;Starting Display&#8221;);\n\n  dma_display = new MatrixPanel_I2S_DMA(mxconfig);\n  dma_display-&gt;begin();\n  dma_display-&gt;setBrightness8(50);\n  dma_display-&gt;fillScreen(TFT_BLACK);\n\n  if (!wifiOK) {\n    Serial.println(&#8220;WiFi failed. Clock will continue, but NTP and weather will not work.&#8221;);\n    return;\n  }\n\n  \/\/ NTP\n  Serial.println(&#8220;Starting NTP time sync&#8230;&#8221;);\n\n  configTzTime(TZ_INFO, ntpServer, &#8220;time.google.com&#8221;, &#8220;time.cloudflare.com&#8221;);\n\n  struct tm timeinfo;\n  uint32_t ntpStart = millis();\n\n  while (!getLocalTime(&amp;timeinfo) &amp;&amp; millis() &#8211; ntpStart &lt; 20000) {\n    Serial.println(&#8220;Waiting for time&#8230;&#8221;);\n    delay(500);\n  }\n\n  if (getLocalTime(&amp;timeinfo)) {\n    Serial.printf(&#8220;Time synced: %02d:%02d  %s  (%s)\\n&#8221;,\n                  timeinfo.tm_hour,\n                  timeinfo.tm_min,\n                  isSummertime() ? &#8220;Summertime&#8221; : &#8220;Wintertime&#8221;,\n                  tzAbbrev());\n  } else {\n    Serial.println(&#8220;NTP time sync failed.&#8221;);\n  }\n\n  \/\/ Initial weather request\n  fetchWeatherData();\n}\n\n\/\/ =====================================================\n\/\/ LOOP\n\/\/ =====================================================\n\nvoid loop() {\n  lightVal = lightMeter.readLightLevel();\n\n  Serial.print(&#8220;Light value: &#8220;);\n  Serial.println(lightVal);\n\n  dma_display-&gt;fillScreen(TFT_BLACK);\n\n  draw_time_clock();\n  set_colour();\n\n  draw_time_daymon();\n  draw_time_year();\n\n  get_weather();\n\n  delay(1000);\n}<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <details class=\"ebox-details\">\n    <summary>smart_clock.h<\/summary>\n    <div class=\"ebox-details-content\">\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">smart_clock.h<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('smart-clock-h', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"smart-clock-h\" class=\"ebox-code-textarea\" readonly>#pragma\n\/*smart_clock.h\nused to create all Datastructures and save the BitmapIcons\n*\/\n\n\/*&#8212;&#8212;&#8212;WEATHER BITMAPS&#8212;&#8212;&#8211;*\/           \n#define small_widht 16\n#define small_height 16\n\/*SUN*\/\nstatic char sun_bits[] = {\n0x00,0x00,0x00,0x01,0x00,0x01,0x08,0x21,0x10,0x11,0xa0,0x0b,\n 0x40,0x04,0x20,0x08,0x3e,0xf8,0x20,0x08,0x40,0x04,0xa0,0x0b,\n 0x10,0x11,0x08,0x21,0x00,0x01,0x00,0x01,};\n\/*MOON*\/\nstatic char moon_bits[] = {\n 0x00,0x00,0x80,0x00,0x60,0x00,0x50,0x00,0x68,0x00,0x2c,0x00,\n 0x24,0x00,0x44,0x00,0x44,0x00,0x84,0x41,0x0c,0x7f,0x08,0x20,\n 0x10,0x10,0x60,0x0e,0x80,0x03,0x00,0x00,};\n\n\/*CLOUDS*\/\nstatic char clouds_bits[] = {\n0x00,0x1c,0x00,0x36,0x00,0x63,0xc0,0x81,0x20,0x80,0x20,0x40,\n 0xc0,0x3f,0x00,0x00,0xe0,0x00,0xb0,0x01,0x18,0x07,0x0e,0x08,\n 0x01,0x08,0x01,0x04,0xfe,0x03,0x00,0x00,};\n\n\/*RAIN*\/\nstatic char rain_bits[] = {\n 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0x03,0x60,0x06,\n 0x30,0x1c,0x18,0x30,0x04,0x20,0x02,0x20,0xa2,0x2a,0xd6,0x37,\n 0x5c,0x1d,0xa8,0x04,0xa8,0x04,0x88,0x00,};\n\n\/*SNOW*\/\nstatic char snow_bits[] = {\n 0x00,0x00,0xc0,0x03,0x60,0x06,0x30,0x1c,0x18,0x30,0x04,0x20,\n 0x02,0x20,0x02,0x20,0x06,0x30,0xfc,0x1f,0x00,0x00,0x08,0x02,\n 0x14,0x05,0x48,0x12,0xa0,0x28,0x40,0x10,};\n\n\/*Thunder*\/\nstatic char thunder_bits[] = {\n 0x00,0x00,0x00,0x00,0xc0,0x03,0x60,0x06,0x30,0x1c,0x18,0x30,\n 0x04,0x20,0x82,0x20,0x82,0x20,0x46,0x30,0xdc,0x1b,0x00,0x03,\n 0x80,0x01,0xc0,0x00,0x40,0x00,0x00,0x00,};\n\n\/*MIST*\/\nstatic char mist_bits[] = {\n0x00,0x00,0xf0,0x0f,0x00,0x00,0xfc,0x1f,0x00,0x00,0xf8,0x3f,\n0x00,0x00,0xfe,0x1f,0x00,0x00,0xf8,0x3f,0x00,0x00,0xfc,0x1f,\n0x00,0x00,0xf0,0x0f,0x00,0x00,0x00,0x00,};\n\n\/*&#8212;&#8212;&#8211;MATRIX COLORS&#8212;&#8212;&#8211;*\/\n#define MTX_BLACK       0x0000      \/*   0,   0,   0 *\/\n#define MTX_NAVY        0x000F      \/*   0,   0, 128 *\/\n#define MTX_DARKGREEN   0x03E0      \/*   0, 128,   0 *\/\n#define MTX_DARKCYAN    0x03EF      \/*   0, 128, 128 *\/\n#define MTX_MAROON      0x7800      \/* 128,   0,   0 *\/\n#define MTX_PURPLE      0x780F      \/* 128,   0, 128 *\/\n#define MTX_OLIVE       0x7BE0      \/* 128, 128,   0 *\/\n#define MTX_LIGHTGREY   0xD69A      \/* 211, 211, 211 *\/\n#define MTX_DARKGREY    0x7BEF      \/* 128, 128, 128 *\/\n#define MTX_BLUE        0x001F      \/*   0,   0, 255 *\/\n#define MTX_GREEN       0x07E0      \/*   0, 255,   0 *\/\n#define MTX_CYAN        0x07FF      \/*   0, 255, 255 *\/\n#define MTX_RED         0xF800      \/* 255,   0,   0 *\/\n#define MTX_MAGENTA     0xF81F      \/* 255,   0, 255 *\/\n#define MTX_YELLOW      0xFFE0      \/* 255, 255,   0 *\/\n#define MTX_WHITE       0xFFFF      \/* 255, 255, 255 *\/\n#define MTX_ORANGE      0xFDA0      \/* 255, 180,   0 *\/\n#define MTX_GREENYELLOW 0xB7E0      \/* 180, 255,   0 *\/\n#define MTX_PINK        0xFE19      \/* 255, 192, 203 *\/ \/\/Lighter pink, was 0xFC9F\n#define MTX_BROWN       0x9A60      \/* 150,  75,   0 *\/\n#define MTX_GOLD        0xFEA0      \/* 255, 215,   0 *\/\n#define MTX_SILVER      0xC618      \/* 192, 192, 192 *\/\n#define MTX_SKYBLUE     0x867D      \/* 135, 206, 235 *\/\n#define MTX_VIOLET      0x915C      \/* 180,  46, 226 *\/\n\n\/\/Day Mode\nuint16_t COLOR_TIME    =  0x07FF;\/\/BLE Bitmask 0x1\nuint16_t COLOR_DATE    =  0x03EF;\/\/BLE Bitmask 0x2\nuint16_t COLOR_BITMAP  =  0xFFFF;\/\/BLE Bitmask 0x4\nuint16_t COLOR_TEMP    =  0xFFE0;\/\/BLE Bitmask 0x8\n\/\/Night Mode\nuint16_t NIGHT_COLOR   =  0x5000;\n\n#pragma<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <details class=\"ebox-details\">\n    <summary>custom_fonts.h<\/summary>\n    <div class=\"ebox-details-content\">\n      <div class=\"ebox-code-block\">\n        <div class=\"ebox-code-header\">\n          <span class=\"ebox-code-title\">custom_fonts.h<\/span>\n          <button class=\"ebox-copy-button\" onclick=\"eboxCopyCode('custom-fonts-h', this)\">Copy code<\/button>\n        <\/div>\n        <textarea id=\"custom-fonts-h\" class=\"ebox-code-textarea small\" readonly>#pragma\n\/*Custom Fonts BitMaps*\/\n\n#define number_width 8\n#define number_height 12\n\n\/*0 8px x 12px*\/\nstatic char zero[] = {0x7E,0x7E,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x7E,0x7E,};\n\n\/*1 8px x 12px*\/\nstatic char one[] = {0x70,0x78,0x7C,0x6E,0x66,0x60,0x60,0x60,0x60,0x60,0x60,0x60,};\n\n\/*2 8px x 12px*\/\nstatic char two[] = {0x3C,0x7E,0x66,0x66,0x60,0x70,0x38,0x1C,0x0E,0x06,0x7E,0x7E,};\n\n\/*3 8px x 12px*\/\nstatic char three[] = {0x3E,0x7E,0x70,0x60,0x70,0x3E,0x3E,0x70,0x60,0x70,0x7E,0x3E,};\n    \n\/*4 8px x 12px*\/\nstatic char four[] = {0x30,0x38,0x3C,0x36,0x33,0x31,0x7F,0x7F,0x30,0x30,0x30,0x30,};\n\n\/*5 8px x 12px*\/\nstatic char five[] = {0x7E,0x7E,0x06,0x06,0x06,0x3E,0x7C,0x60,0x60,0x66,0x7E,0x3C,};\n\n\/*6 8px x 12px*\/\nstatic char six[] = {0x7E,0x7E,0x06,0x06,0x06,0x7E,0x7E,0x66,0x66,0x66,0x7E,0x7E,};\n\n\/*7 8px x 12px*\/\nstatic char seven[] = {0x7F,0x7F,0x60,0x60,0x70,0x38,0x1C,0x0E,0x06,0x07,0x03,0x03,};\n\n\n\/*8 8px x 12px*\/\nstatic char eight[] = {0x3C,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x66,0x66,0x66,0x7E,0x3C,};\n\n\n\/*9 8px x 12px*\/\nstatic char nine[] = {0x7E,0x7E,0x66,0x66,0x66,0x7E,0x7E,0x60,0x60,0x66,0x7E,0x7E,};\n\n\/*: 4px x 12px*\/\n#define colon_with 8\n#define colon_height 12\nstatic char colon[] = {0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00,};\n\n#pragma<\/textarea>\n      <\/div>\n    <\/div>\n  <\/details>\n\n  <details class=\"ebox-details\">\n    <summary>Full Code Explanation<\/summary>\n    <div class=\"ebox-details-content\">\n      <div class=\"ebox-code-explanation\">\n\n  <h2>Basic Configuration<\/h2>\n\n  <p>\n    At the beginning of the code, the required libraries are included. The project uses the HUB75 matrix library for the LED display, Wi-Fi and HTTP libraries for online data, ArduinoJson for parsing the weather response, and the BH1750 library for the light sensor.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n#include <Arduino.h>\n#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>\n\n#include <smart_clock.h>\n#include <custom_fonts.h>\n\n#include <WiFi.h>\n#include <HTTPClient.h>\n#include &#8220;time.h&#8221;\n\n#include <ArduinoJson.h>\n\n#include <Wire.h>\n#include <BH1750.h><\/textarea>\n  <\/div>\n\n  <p>\n    The display pins are defined according to the wiring between the ESP32 and the P4 LED matrix display. These pin numbers must match your own wiring.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n\/\/ &#8212;&#8212;&#8212;- RGB DISPLAY PINS &#8212;&#8212;&#8212;-\n#define R1_PIN 25\n#define G1_PIN 26\n#define B1_PIN 27\n\n#define R2_PIN 14\n#define G2_PIN 12\n#define B2_PIN 13\n\n#define A_PIN 23\n#define B_PIN 19\n#define C_PIN 5\n#define D_PIN 17\n#define E_PIN -1\n\n#define LAT_PIN 4\n#define OE_PIN 15\n#define CLK_PIN 16<\/textarea>\n  <\/div>\n\n  <p>\n    The display size is also defined in the code. In this project, a 64 \u00d7 32 pixel LED matrix panel is used.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n\/\/ &#8212;&#8212;&#8212;- MATRIX CONFIG &#8212;&#8212;&#8212;-\n#define PANEL_RES_X 64\n#define PANEL_RES_Y 32\n#define PANEL_CHAIN 1\n\nMatrixPanel_I2S_DMA *dma_display = nullptr;\nHUB75_I2S_CFG mxconfig(PANEL_RES_X, PANEL_RES_Y, PANEL_CHAIN);<\/textarea>\n  <\/div>\n\n  <h2>Wi-Fi, Time, and Weather Settings<\/h2>\n\n  <p>\n    The smart clock needs Wi-Fi access to synchronize the time and download weather data. For the website version of the code, the Wi-Fi credentials and API key should always be shown as placeholders.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n\/\/ &#8212;&#8212;&#8212;- Wi-Fi &#8212;&#8212;&#8212;-\nconst char *ssid = &#8220;YOUR_WIFI_NAME&#8221;;\nconst char *password = &#8220;YOUR_WIFI_PASSWORD&#8221;;\n\n\/\/ &#8212;&#8212;&#8212;- OpenWeatherMap &#8212;&#8212;&#8212;-\nconst char *weatherApiKey = &#8220;YOUR_OPENWEATHERMAP_API_KEY&#8221;;\nconst char *weatherLocation = &#8220;YOUR_CITY,COUNTRY_CODE&#8221;;\n\n\/\/ &#8212;&#8212;&#8212;- NTP \/ Timezone &#8212;&#8212;&#8212;-\nconst char *ntpServer = &#8220;pool.ntp.org&#8221;;\nconst char *TZ_INFO = &#8220;CET-1CEST,M3.5.0\/2,M10.5.0\/3&#8221;;<\/textarea>\n  <\/div>\n\n  <p>\n    The timezone string is configured for Switzerland and other Central European regions. It automatically handles the switch between winter time and summertime.\n  <\/p>\n\n  <h2>What smart_clock.h Does<\/h2>\n\n  <p>\n    The file <code>smart_clock.h<\/code> stores the small weather icon bitmaps and the matrix color definitions. Keeping these icons in a separate header file makes the main <code>ElektroClock.ino<\/code> file easier to read. Instead of placing long bitmap arrays directly inside the main program, the main sketch only includes the file with <code>#include &lt;smart_clock.h&gt;<\/code> and can then use the icon names directly.\n  <\/p>\n\n  <p>\n    In this project, the weather icons are stored as 16 \u00d7 16 pixel bitmap arrays. Each icon is saved as hexadecimal values. The code later reads these values bit by bit and draws the active pixels on the LED matrix. Examples are <code>sun_bits<\/code>, <code>moon_bits<\/code>, <code>clouds_bits<\/code>, <code>rain_bits<\/code>, <code>snow_bits<\/code>, <code>thunder_bits<\/code>, and <code>mist_bits<\/code>.\n  <\/p>\n\n  <p>\n    The same file also stores color constants in RGB565 format. RGB565 is a compact 16-bit color format that is commonly used for small displays and LED matrix projects. The file also contains day and night color variables, for example for the time, date, bitmap icons, and temperature.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n#pragma\n\n\/*\n  smart_clock.h\n  Used to store weather bitmap icons and matrix color values.\n*\/\n\n\/*&#8212;&#8212;&#8212; WEATHER BITMAPS &#8212;&#8212;&#8211;*\/\n#define small_widht 16\n#define small_height 16\n\n\/* SUN *\/\nstatic char sun_bits[] = {\n  0x00,0x00,0x00,0x01,0x00,0x01,0x08,0x21,\n  0x10,0x11,0xa0,0x0b,0x40,0x04,0x20,0x08,\n  0x3e,0xf8,0x20,0x08,0x40,0x04,0xa0,0x0b,\n  0x10,0x11,0x08,0x21,0x00,0x01,0x00,0x01,\n};\n\n\/* MOON *\/\nstatic char moon_bits[] = {\n  0x00,0x00,0x80,0x00,0x60,0x00,0x50,0x00,\n  0x68,0x00,0x2c,0x00,0x24,0x00,0x44,0x00,\n  0x44,0x00,0x84,0x41,0x0c,0x7f,0x08,0x20,\n  0x10,0x10,0x60,0x0e,0x80,0x03,0x00,0x00,\n};\n\n\/* CLOUDS *\/\nstatic char clouds_bits[] = {\n  0x00,0x1c,0x00,0x36,0x00,0x63,0xc0,0x81,\n  0x20,0x80,0x20,0x40,0xc0,0x3f,0x00,0x00,\n  0xe0,0x00,0xb0,0x01,0x18,0x07,0x0e,0x08,\n  0x01,0x08,0x01,0x04,0xfe,0x03,0x00,0x00,\n};\n\n\/*&#8212;&#8212;&#8212; MATRIX COLORS &#8212;&#8212;&#8211;*\/\n#define MTX_BLACK  0x0000\n#define MTX_BLUE   0x001F\n#define MTX_CYAN   0x07FF\n#define MTX_YELLOW 0xFFE0\n#define MTX_WHITE  0xFFFF\n\nuint16_t COLOR_TIME   = 0x07FF;\nuint16_t COLOR_DATE   = 0x03EF;\nuint16_t COLOR_BITMAP = 0xFFFF;\nuint16_t COLOR_TEMP   = 0xFFE0;\n\nuint16_t NIGHT_COLOR  = 0x5000;\n\n#pragma<\/textarea>\n  <\/div>\n\n  <h2>What custom_fonts.h Does<\/h2>\n\n  <p>\n    The file <code>custom_fonts.h<\/code> stores the custom number font used for the clock display. Instead of using a standard text font, every digit from 0 to 9 is saved as a small bitmap. This gives full control over the shape, width, and readability of the numbers on the 64 \u00d7 32 LED matrix.\n  <\/p>\n\n  <p>\n    Each number is 8 pixels wide and 12 pixels high. The file also contains a custom colon bitmap for the time separator. The main sketch can then draw the time by selecting the correct digit array and passing it to the drawing function.\n  <\/p>\n\n  <p>\n    This approach is useful because normal fonts can look too small, too thin, or badly aligned on low-resolution LED matrix displays. With custom bitmaps, the clock digits can be designed exactly for the available pixel grid.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n#pragma\n\n\/*\n  custom_fonts.h\n  Custom digit bitmaps for the smart clock.\n*\/\n\n#define number_width 8\n#define number_height 12\n\n\/* 0 &#8211; 8px x 12px *\/\nstatic char zero[] = {\n  0x7E,0x7E,0x66,0x66,0x66,0x66,\n  0x66,0x66,0x66,0x66,0x7E,0x7E,\n};\n\n\/* 1 &#8211; 8px x 12px *\/\nstatic char one[] = {\n  0x70,0x78,0x7C,0x6E,0x66,0x60,\n  0x60,0x60,0x60,0x60,0x60,0x60,\n};\n\n\/* 2 &#8211; 8px x 12px *\/\nstatic char two[] = {\n  0x3C,0x7E,0x66,0x66,0x60,0x70,\n  0x38,0x1C,0x0E,0x06,0x7E,0x7E,\n};\n\n\/* 3 &#8211; 8px x 12px *\/\nstatic char three[] = {\n  0x3E,0x7E,0x70,0x60,0x70,0x3E,\n  0x3E,0x70,0x60,0x70,0x7E,0x3E,\n};\n\n\/* Colon &#8211; 8px x 12px *\/\n#define colon_with 8\n#define colon_height 12\n\nstatic char colon[] = {\n  0x00,0x00,0x00,0x18,0x18,0x00,\n  0x00,0x18,0x18,0x00,0x00,0x00,\n};\n\n#pragma<\/textarea>\n  <\/div>\n\n  <h2>Drawing Custom Bitmaps<\/h2>\n\n  <p>\n    The ElektroClock uses small bitmap arrays for the weather icons and the custom number font. The weather icons are stored in <code>smart_clock.h<\/code>, while the number digits and colon are stored in <code>custom_fonts.h<\/code>. The weather icons are 16 \u00d7 16 pixel bitmaps, and the custom digits are 8 \u00d7 12 pixel bitmaps.\n  <\/p>\n\n  <p>\n    A bitmap is a compact way of saving a small image in code. Each bit represents one pixel. If the bit is 1, the pixel is drawn. If the bit is 0, the pixel stays transparent. This makes it possible to store simple icons such as sun, moon, clouds, rain, snow, thunder, mist, and custom number shapes directly in the program memory.\n  <\/p>\n\n  <p>\n    The function <code>drawXbm565()<\/code> reads the bitmap data byte by byte. Each byte contains eight pixels. The code checks every bit, calculates the correct display position, and draws a pixel only when the bit is active.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid drawXbm565(int x, int y, int width, int height, const char *xbm, uint16_t color = 0) {\n  if (color == 0) color = color_xbm565;\n\n  if (width % 8 != 0) {\n    width = ((width \/ 8) + 1) * 8;\n  }\n\n  for (int i = 0; i < width * height \/ 8; i++) {\n    unsigned char charColumn = pgm_read_byte(xbm + i);\n\n    for (int j = 0; j < 8; j++) {\n      int targetX = (i * 8 + j) % width + x;\n      int targetY = (8 * i \/ width) + y;\n\n      if (bitRead(charColumn, j)) {\n        dma_display->drawPixel(targetX, targetY, color);\n      }\n    }\n  }\n}<\/textarea>\n  <\/div>\n\n  <p>\n    For example, a weather icon can be drawn like this:\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\ndrawXbm565(1, 1, 16, 16, sun_bits);<\/textarea>\n  <\/div>\n\n  <p>\n    This draws the sun icon at position x = 1, y = 1 with a size of 16 \u00d7 16 pixels.\n  <\/p>\n\n  <h2>Drawing the Time with Custom Digits<\/h2>\n\n  <p>\n    Instead of using a standard text font for the time, the clock uses custom digit bitmaps. This gives better control over the look of the time display and makes the digits easier to read on the LED matrix.\n  <\/p>\n\n  <p>\n    The time is first read from the ESP32 local time. Then the hour and minute values are split into individual digits. Each digit is drawn with the <code>draw_digit()<\/code> function.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid draw_digit(int x, int y, int width, int height, const char *xbm, uint16_t color = 0) {\n  if (color == 0) color = color_xbm565;\n\n  if (width % 8 != 0) {\n    width = ((width \/ 8) + 1) * 8;\n  }\n\n  for (int i = 0; i < width * height \/ 8; i++) {\n    unsigned char charColumn = pgm_read_byte(xbm + i);\n\n    for (int j = 0; j < 8; j++) {\n      int targetX = (i * 8 + j) % width + x;\n      int targetY = (8 * i \/ width) + y;\n\n      if (bitRead(charColumn, j)) {\n        dma_display->drawPixel(targetX, targetY, color);\n      }\n    }\n  }\n}<\/textarea>\n  <\/div>\n\n  <p>\n    The digits are placed on the right side of the display. The weather icon and temperature are shown on the left side.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\n\/\/ Example positions for the clock digits\ndraw_digit(24, 0, 8, 12, zero);\ndraw_digit(32, 0, 8, 12, one);\ndraw_digit(40, 0, 8, 12, colon);\ndraw_digit(48, 0, 8, 12, three);\ndraw_digit(56, 0, 8, 12, five);<\/textarea>\n  <\/div>\n\n  <h2>Weather Data and Icon Selection<\/h2>\n\n  <p>\n    The weather data is downloaded from OpenWeatherMap. The code creates a request URL using the selected location and API key. The response is returned as JSON data.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nString makeWeatherUrl() {\n  String url = &#8220;http:\/\/api.openweathermap.org\/data\/2.5\/weather?q=&#8221;;\n  url += weatherLocation;\n  url += &#8220;&#038;APPID=&#8221;;\n  url += weatherApiKey;\n  return url;\n}<\/textarea>\n  <\/div>\n\n  <p>\n    After the HTTP request, the JSON response is parsed. The temperature is converted from Kelvin to Celsius, and the weather ID is saved. This ID is later used to choose the correct weather icon.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nfloat tempKelvin = doc[&#8220;main&#8221;][&#8220;temp&#8221;] | 273.15;\ntemperature = (int)(tempKelvin &#8211; 273.15 + 0.5);\n\nid = doc[&#8220;weather&#8221;][0][&#8220;id&#8221;] | 800;\nweatherValid = true;<\/textarea>\n  <\/div>\n\n  <p>\n    The icon is selected based on the OpenWeatherMap weather ID. For example, thunderstorm IDs are in the 200 range, rain IDs are between 300 and 599, and clear sky uses ID 800.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid draw_weather() {\n  if (!weatherValid) {\n    return;\n  }\n\n  if ((id >= 200) &#038;&#038; (id <= 299)) {\n    drawXbm565(1, 1, 16, 16, thunder_bits);\n  }\n\n  if ((id >= 300) &#038;&#038; (id <= 599)) {\n    drawXbm565(1, 1, 16, 16, rain_bits);\n  }\n\n  if ((id >= 600) &#038;&#038; (id <= 699)) {\n    drawXbm565(1, 1, 16, 16, snow_bits);\n  }\n\n  if ((id >= 700) &#038;&#038; (id <= 799)) {\n    drawXbm565(1, 1, 16, 16, mist_bits);\n  }\n\n  if ((id >= 801) &#038;&#038; (id <= 899)) {\n    drawXbm565(1, 1, 16, 16, clouds_bits);\n  }\n\n  if ((id == 800) &#038;&#038; (dth < 20) &#038;&#038; (dth >= 5)) {\n    drawXbm565(1, 1, 16, 16, sun_bits);\n  }\n\n  if ((id == 800) &#038;&#038; ((dth >= 20) || (dth < 5))) {\n    drawXbm565(1, 1, 16, 16, moon_bits);\n  }\n}<\/textarea>\n  <\/div>\n\n  <p>\n    The weather update does not run every second. Instead, the code uses a counter. When the counter reaches 3600, it is reset and new weather data is downloaded.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid get_weather() {\n  if (weather_update_cnt == 0) {\n    fetchWeatherData();\n  }\n\n  draw_weather();\n\n  weather_update_cnt++;\n\n  if (weather_update_cnt >= 3600) {\n    weather_update_cnt = 0;\n  }\n}<\/textarea>\n  <\/div>\n\n  <h2>Automatic Brightness and Night Mode<\/h2>\n\n  <p>\n    During testing, I noticed that the LED matrix can be too bright in a dark room. To solve this, I added a BH1750 light sensor. The sensor measures the ambient light level in lux. The code then chooses a suitable brightness level for the display.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid set_colour() {\n  uint8_t brightness = 90;\n\n  if (lightVal < 100) {\n    brightness = 15;\n  } else if (lightVal < 200) {\n    brightness = 25;\n  } else if (lightVal < 300) {\n    brightness = 35;\n  } else if (lightVal < 400) {\n    brightness = 50;\n  } else if (lightVal < 500) {\n    brightness = 70;\n  } else {\n    brightness = 90;\n  }\n\n  dma_display->setBrightness8(brightness);\n}<\/textarea>\n  <\/div>\n\n  <p>\n    The code also changes the display colors depending on the time of day. During the day, the clock uses brighter colors. At night, it switches to a dim blue color so the display is less distracting.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nif ((dth < 20) &#038;&#038; (dth >= 8)) {\n  color_xbm565      = TFT_CYAN;\n  color_daymon      = TFT_BLUE;\n  color_temperature = TFT_YELLOW;\n} else {\n  uint16_t dim = dma_display->color565(0, 25, 50);\n\n  color_xbm565      = dim;\n  color_daymon      = dim;\n  color_temperature = dim;\n}<\/textarea>\n  <\/div>\n\n  <h2>Main Setup Function<\/h2>\n\n  <p>\n    The <code>setup()<\/code> function prepares all hardware and online services. First, the serial monitor is started for debugging. Then the ESP32 connects to Wi-Fi, initializes the BH1750 sensor, starts the display, synchronizes the time over NTP, and performs the first weather request.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid setup() {\n  Serial.begin(115200);\n  delay(1000);\n\n  bool wifiOK = connectWiFiLocked();\n\n  I2C_1.begin(21, 22);\n\n  if (!lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE, 0x23, &#038;I2C_1)) {\n    Serial.println(&#8220;Error initializing BH1750&#8221;);\n  } else {\n    Serial.println(&#8220;BH1750 initialized&#8221;);\n  }\n\n  dma_display = new MatrixPanel_I2S_DMA(mxconfig);\n  dma_display->begin();\n  dma_display->setBrightness8(50);\n  dma_display->fillScreen(TFT_BLACK);\n\n  if (!wifiOK) {\n    Serial.println(&#8220;WiFi failed. Clock will continue, but NTP and weather will not work.&#8221;);\n    return;\n  }\n\n  configTzTime(TZ_INFO, ntpServer, &#8220;time.google.com&#8221;, &#8220;time.cloudflare.com&#8221;);\n\n  fetchWeatherData();\n}<\/textarea>\n  <\/div>\n\n  <h2>Main Loop<\/h2>\n\n  <p>\n    The main loop runs continuously. Every second, the ESP32 reads the light level, clears the display, draws the time, updates the brightness and color mode, draws the date and year, and displays the weather information.\n  <\/p>\n\n  <div class=\"ebox-small-code-box\">\n    <textarea class=\"ebox-small-code\" readonly>\nvoid loop() {\n  lightVal = lightMeter.readLightLevel();\n\n  dma_display->fillScreen(TFT_BLACK);\n\n  draw_time_clock();\n  set_colour();\n\n  draw_time_daymon();\n  draw_time_year();\n\n  get_weather();\n\n  delay(1000);\n}<\/textarea>\n  <\/div>\n\n  <p>\n    This simple loop structure makes the project easy to understand. The display is redrawn once per second, while the weather request is handled separately inside <code>get_weather()<\/code> so the API is not called too often.\n  <\/p>\n\n\n\n<\/div>\n    <\/div>\n  <\/details>\n\n  <h2>9. From Prototype to PCB<\/h2>\n\n  <p>\n    The breadboard prototype worked, but the wiring was messy. A P4 matrix display needs many signal connections, so Dupont wires can quickly become difficult to manage.\n  <\/p>\n  <p>\n    To make the project cleaner and more reliable, I designed a custom PCB. The main advantage of the PCB is that the original ribbon cable from the display can be used. This reduces the number of loose wires and makes the assembly much easier.\n  <\/p>\n  <p>\n    I also added a pin header to the PCB so that unused ESP32 pins remain accessible. This makes future upgrades possible, for example adding buttons, extra sensors, or a different enclosure design.\n  <\/p>\n\n  <div class=\"ebox-gallery\">\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/schaltung_elektroclock_p-1024x849.png\" alt=\"Elektroclock schematic\" loading=\"lazy\">\n      <p class=\"ebox-caption\">Elektroclock schematic.<\/p>\n    <\/div>\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/pcb_elektroclock_p.png\" alt=\"Elektroclock PCB layout\" loading=\"lazy\">\n      <p class=\"ebox-caption\">Elektroclock PCB layout.<\/p>\n    <\/div>\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/04\/IMG_2557-1024x768.jpg\" alt=\"Elektroclock assembled PCB prototype\" loading=\"lazy\">\n      <p class=\"ebox-caption\">PCB-based prototype.<\/p>\n    <\/div>\n  <\/div>\n\n  <h2>10. Enclosure and Final Assembly<\/h2>\n\n  <p>\n    After the electronics were working, I built an enclosure for the clock. The enclosure protects the electronics and gives the project a cleaner look.\n  <\/p>\n  <p>\n    For one version, I used a simple prototype case. For another version, I created a laser-cut enclosure. Both approaches work, and the best option depends on the tools available.\n  <\/p>\n  <p>When designing the enclosure, it is important to leave enough space for:<\/p>\n\n  <ul>\n    <li>The LED matrix display<\/li>\n    <li>The ESP32 and PCB<\/li>\n    <li>The power connector<\/li>\n    <li>The light sensor<\/li>\n    <li>Cable routing<\/li>\n    <li>Ventilation, if required<\/li>\n  <\/ul>\n\n  <div class=\"ebox-note\">\n    <strong>Sensor placement:<\/strong> The BH1750 light sensor should be placed where it can measure the room brightness correctly. It should not be hidden inside the enclosure or placed directly behind the bright LED matrix.\n  <\/div>\n\n  <div class=\"ebox-final-box\">\n    <h2>11. Final Result<\/h2>\n    <p>\n      The final Elektroclock is a compact smart clock based on an ESP32 and a P4 LED matrix display. It shows the current time and weather information, updates the weather automatically, and adjusts its brightness depending on the room lighting.\n    <\/p>\n    <p>\n      This project is a good example of how a complex electronics project can be developed step by step. First, the display is tested on its own. Then Wi-Fi, time synchronization, weather data, and brightness control are added. Finally, the prototype is turned into a cleaner PCB-based version with an enclosure.\n    <\/p>\n    <p>\n      The full code, schematic, parts list, and additional project files are available on the Elektroclock project page.\n    <\/p>\n    <p>\n      Feel free to recreate this project and modify it for your own setup. You can change the display layout, add more weather information, use a different enclosure, or expand the PCB with additional features.\n    <\/p>\n\n    <div class=\"ebox-image-card\">\n      <img decoding=\"async\" src=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png\" alt=\"Final Elektroclock project result\" loading=\"lazy\">\n    <\/div>\n  <\/div>\n\n<\/div>\n\n<script>\n  function eboxCopyCode(id, button) {\n    const textarea = document.getElementById(id);\n    if (!textarea) return;\n\n    textarea.focus();\n    textarea.select();\n    textarea.setSelectionRange(0, 999999);\n\n    const oldText = button.textContent;\n\n    if (navigator.clipboard && window.isSecureContext) {\n      navigator.clipboard.writeText(textarea.value).then(function () {\n        button.textContent = 'Copied!';\n        setTimeout(function () {\n          button.textContent = oldText;\n        }, 1400);\n      }).catch(function () {\n        document.execCommand('copy');\n        button.textContent = 'Copied!';\n        setTimeout(function () {\n          button.textContent = oldText;\n        }, 1400);\n      });\n    } else {\n      document.execCommand('copy');\n      button.textContent = 'Copied!';\n      setTimeout(function () {\n        button.textContent = oldText;\n      }, 1400);\n    }\n  }\n<\/script>\n\n","protected":false},"excerpt":{"rendered":"<p>ESP32 Project Elektroclock In this project, I build a DIY smart clock using an ESP32, a P4 RGB LED matrix display, Wi-Fi time synchronization, weather data, and a BH1750 light sensor for automatic brightness control. The clock shows the current time, date, weather icon, and temperature. It refreshes the weather data once per hour, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-855908","page","type-page","status-publish","hentry"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":7}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Elektroclock - ElektroBox<\/title>\n<meta name=\"description\" content=\"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/elektrobox.ch\/index.php\/elektroclock\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Elektroclock - ElektroBox\" \/>\n<meta property=\"og:description\" content=\"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/elektrobox.ch\/index.php\/elektroclock\/\" \/>\n<meta property=\"og:site_name\" content=\"ElektroBox\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-02T16:44:10+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/\",\"name\":\"Elektroclock - ElektroBox\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/FAV1.png\",\"datePublished\":\"2026-04-30T17:32:50+00:00\",\"dateModified\":\"2026-06-02T16:44:10+00:00\",\"description\":\"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/#primaryimage\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/FAV1.png\",\"contentUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2025\\\/10\\\/FAV1.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/index.php\\\/elektroclock\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/elektrobox.ch\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Elektroclock\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#website\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/\",\"name\":\"ElektroBox\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#\\\/schema\\\/person\\\/878bd55e17d9d0b1a53523fa5e90f6b2\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/elektrobox.ch\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/#\\\/schema\\\/person\\\/878bd55e17d9d0b1a53523fa5e90f6b2\",\"name\":\"Jacob Jarosz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"url\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"contentUrl\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\",\"width\":2560,\"height\":2560,\"caption\":\"Jacob Jarosz\"},\"logo\":{\"@id\":\"https:\\\/\\\/elektrobox.ch\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Logo_Color-Kopie-scaled.png\"},\"sameAs\":[\"http:\\\/\\\/elektrobox.ch\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Elektroclock - ElektroBox","description":"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/","og_locale":"en_US","og_type":"article","og_title":"Elektroclock - ElektroBox","og_description":"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.","og_url":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/","og_site_name":"ElektroBox","article_modified_time":"2026-06-02T16:44:10+00:00","og_image":[{"url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png","type":"","width":"","height":""}],"twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/","url":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/","name":"Elektroclock - ElektroBox","isPartOf":{"@id":"https:\/\/elektrobox.ch\/#website"},"primaryImageOfPage":{"@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/#primaryimage"},"image":{"@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/#primaryimage"},"thumbnailUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png","datePublished":"2026-04-30T17:32:50+00:00","dateModified":"2026-06-02T16:44:10+00:00","description":"Elektroclock! Build a DIY smart Clock with an ESP32, P4 RGB LED matrix, Wi-Fi time sync, weather data, and automatic brightness control.","breadcrumb":{"@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/elektrobox.ch\/index.php\/elektroclock\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/#primaryimage","url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png","contentUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2025\/10\/FAV1.png"},{"@type":"BreadcrumbList","@id":"https:\/\/elektrobox.ch\/index.php\/elektroclock\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/elektrobox.ch\/"},{"@type":"ListItem","position":2,"name":"Elektroclock"}]},{"@type":"WebSite","@id":"https:\/\/elektrobox.ch\/#website","url":"https:\/\/elektrobox.ch\/","name":"ElektroBox","description":"","publisher":{"@id":"https:\/\/elektrobox.ch\/#\/schema\/person\/878bd55e17d9d0b1a53523fa5e90f6b2"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/elektrobox.ch\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/elektrobox.ch\/#\/schema\/person\/878bd55e17d9d0b1a53523fa5e90f6b2","name":"Jacob Jarosz","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","url":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","contentUrl":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png","width":2560,"height":2560,"caption":"Jacob Jarosz"},"logo":{"@id":"https:\/\/elektrobox.ch\/wp-content\/uploads\/2026\/05\/Logo_Color-Kopie-scaled.png"},"sameAs":["http:\/\/elektrobox.ch"]}]}},"_links":{"self":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855908","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/comments?post=855908"}],"version-history":[{"count":22,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855908\/revisions"}],"predecessor-version":[{"id":856304,"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/pages\/855908\/revisions\/856304"}],"wp:attachment":[{"href":"https:\/\/elektrobox.ch\/index.php\/wp-json\/wp\/v2\/media?parent=855908"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}