45浏览
查看: 45|回复: 2

[项目] 【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟

[复制链接]
添加了 sketch v6 的草稿,可以针对 nodeMCU/ESP8266 进行编译。它已添加到步骤 6 中。有关详细信息/信息,请查看我的 S7ripClock 的第 11 步。
这件事背后的想法是避免像在经典矩阵中那样焊接多个 LED 灯条。要做到这一点,有 6 个 LED “浪费”了,但它使构建网格的速度大大加快。
基本上这与我之前的 Lazy Grid Clock 相同 - 它现在终于更新了,提供了更轻松的构建并使用更少的材料,例如螺丝。由于有一些混音/修改,我保留了网格的方向/起点,因此草图可以在 LGCv1 和 v2 上使用。
Arduino sketch 基于 S7ripClock。因此,虽然看起来有些不同,但视频中的说明/功能也适用于此。
如果你正在寻找一个更大的网格,并且不关心做一些焊接工作,你可能想看看我的 “Grid Clock v2”: https://www.thingiverse.com/thing:3433644

材料
1 个 Arduino Pro Mini(5v,328p)或 Arduino Nano
83 个 WS2812B LED,60 米条,5V,每个 LED 可单独寻址,10mm 宽(IP65/67,涂层/橡胶的不适合!
1 个 DS3231 RTC 模块(ZS-042、用于 Pi 的 DS3231 或类似模块)
2 个 6x6 毫米按钮(按钮长度并不重要,3-6 毫米效果很好)
1 根 USB 数据线 / USB 壁式充电器(最小 500mA,推荐 1A)
5 个 M3 螺丝(长度 8 毫米 - 12 毫米,没关系,推荐 8 毫米)
一些电线(建议使用 AWG 26 分钟)
2x 120mm x 188mm 漫射材料(我一直在使用 Folex 喷墨胶片,非常薄的纸或类似材料也可以使用)
您需要一个有效的 Arduino IDE 来上传 sketch。此外,您还应该了解编译和上传 sketch 或安装所需库之间的区别。如果您对 LED/Arduino 完全陌生,我建议您先完成 Adafruits Neopixel Guide 之类的内容。
笔记:
草图正在使用 FastLED 库。因此,可以使用其他 LED,但此 instructable 将不包括此类修改。使用没有 logic level shifters 和 WS2812B 的 ESP8266也是如此。
对于 RTC 通信,使用 JChristensen 的 DS3232 库。所以支持其他模型 (DS1307),我只是还没有遇到没有大量漂移的模型。^^

电子产品非常简单。只需 2 个按钮、电源和 LED 灯条即可连接。请查看 S7ripClock 以获取详细的图片和原理图。线材颜色与图片中可以看到的相同。
https://www.instructables.com/id/S7ripClock-Basic-...
该草图也基于 S7ripClock,因此它提供了完全相同的功能。根据您选择的电源,您可能希望在草图之上提高 500mA 的默认功率限制。
通过将 “enableDot” 更改为 “true”,您可以根据需要在网格中心启用一个闪烁的点。
基本使用说明:
按钮 A:选择亮度
按钮 A(长按):切换颜色模式(每个数字/每个 LED)
按钮 B:选择调色板
按钮 B(长按):切换 12 小时 / 24 小时模式
按钮 A + B:进入设置
在设置中:按钮 B -> 增加 +1,按钮 A -> 接受/下一步。

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图1

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图2

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图3

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图4


【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图5

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图6

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图8

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图7

【Arduino 动手做】 LGCv2:使用 LED 灯条的 7x11 LED 矩阵时钟图9

驴友花雕  中级技神
 楼主|

发表于 3 天前

【Arduino 动手做】 LGCv2:使用单个 LED 灯条的 7x11 LED 矩...

项目代码

  1. /* -[ClockSketch v7.4]----------------------------------------------------------------------------------------
  2.    https://www.instructables.com/ClockSketch-V7-Part-I/
  3.    
  4.    pre-configured for:
  5.    Lazy Grid Clock v2 (7x11)
  6.    https://www.instructables.com/Lazy-Grid-Clock-V2/
  7.    
  8.    Arduino UNO/Nano/Pro Mini (AtMega328, 5V, 16 MHz), DS3231 RTC
  9.    December 2023 - Daniel Cikic
  10.    Serial Baud Rates:
  11.    Arduino: 57600
  12.    nodeMCU: 74880
  13. -------------------------------------------------------------------------------------------------------------- */
  14. // comment below to disable serial in-/output and free some RAM
  15. #define DEBUG
  16. // nodeMCU - uncomment to compile this sketch for nodeMCU 1.0 / ESP8266, make sure to select the proper board
  17. // type inside the IDE! This mode is NOT supported and only experimental!
  18. // #define NODEMCU
  19. // useWiFi - enable WiFi support, WPS setup only! If no WPS support is available on a router check settings
  20. // further down, set useWPS to false and enter ssid/password there
  21. // #define USEWIFI
  22. // useNTP - enable NTPClient, requires NODEMCU and USEWIFI. This will also enforce AUTODST.
  23. // Configure a ntp server further down below!
  24. // #define USENTP
  25. // RTC selection - uncomment the one you're using, comment all others and make sure pin assignemts for
  26. // DS1302 are correct in the parameters section further down!
  27. // #define RTC_DS1302
  28. // #define RTC_DS1307
  29. #define RTC_DS3231
  30. // autoDST - uncomment to enable automatic DST switching, check Time Change Rules below!
  31. // #define AUTODST
  32. // FADING - uncomment to enable fading effects for dots/digits, other parameters further down below
  33. // #define FADING
  34. // autoBrightness - uncomment to enable automatic brightness adjustments by using a photoresistor/LDR
  35. // #define AUTOBRIGHTNESS
  36. // customDisplay - uncomment this to enable displayMyStuff(). It's an example of how to display values
  37. // at specified times, like temperature readouts
  38. // #define CUSTOMDISPLAY
  39. // FastForward will speed up things and advance time, this is only for testing purposes!
  40. // Disables AUTODST, USENTP and USERTC.
  41. // #define FASTFORWARD
  42. /* ----------------------------------------------------------------------------------------------------- */
  43. #include <TimeLib.h>                                 // "Time" by Michael Margolis, used in all configs
  44. #include <EEPROM.h>                                  // required for reading/saving settings to eeprom
  45. /* Start RTC config/parameters--------------------------------------------------------------------------
  46.    Check pin assignments for DS1302 (SPI), others are I2C (A4/A5 on Arduino by default)                  
  47.    Currently all types are using the "Rtc by Makuna" library                                             */
  48. #ifdef RTC_DS1302
  49.   #include <ThreeWire.h>
  50.   #include <RtcDS1302.h>
  51.   ThreeWire myWire(7, 6, 8);                                     // IO/DAT, SCLK, CE/RST
  52.   RtcDS1302<ThreeWire> Rtc(myWire);
  53.   #define RTCTYPE "DS1302"
  54.   #define USERTC
  55. #endif
  56. #ifdef RTC_DS1307
  57.   #include <Wire.h>
  58.   #include <RtcDS1307.h>
  59.   RtcDS1307<TwoWire> Rtc(Wire);
  60.   #define RTCTYPE "DS1307"
  61.   #define USERTC
  62. #endif
  63. #ifdef RTC_DS3231
  64.   #include <Wire.h>
  65.   #include <RtcDS3231.h>
  66.   RtcDS3231<TwoWire> Rtc(Wire);
  67.   #define RTCTYPE "DS3231"
  68.   #define USERTC
  69. #endif
  70. #if !defined ( USERTC )
  71.   #pragma message "No RTC selected, check definitions on top of the sketch!"
  72. #endif
  73. /* End RTC config/parameters---------------------------------------------------------------------------- */
  74. /* Start WiFi config/parameters------------------------------------------------------------------------- */
  75. #ifdef USEWIFI
  76.   const bool useWPS = true;          // set to false to disable WPS and use credentials below
  77.   const char* wifiSSID = "maWhyFhy";
  78.   const char* wifiPWD = "5up3r1337r0xX0r!";
  79. #endif
  80. /* End WiFi config/parameters--------------------------------------------------------------------------- */
  81. /* Start NTP config/parameters--------------------------------------------------------------------------
  82.    Using NTP will enforce autoDST, so check autoDST/time zone settings below!                            */
  83. #ifdef USENTP
  84.   /* I recommend using a local ntp service (many routers offer them), don't spam public ones with dozens
  85.      of requests a day, get a rtc! ^^                                                                    */
  86.   //#define NTPHOST "europe.pool.ntp.org"
  87.   #define NTPHOST "192.168.2.1"
  88.   #ifndef AUTODST
  89.     #define AUTODST
  90.   #endif
  91. #endif
  92. /* End NTP config/parameters---------------------------------------------------------------------------- */
  93. /* Start autoDST config/parameters ----------------------------------------------------------------------
  94.    Comment/uncomment/add TimeChangeRules as needed, only use 2 (tcr1, tcr2), comment out unused ones!     
  95.    Enabling/disabling autoDST will require to set time again, clock will be running in UTC time if autoDST
  96.    is enabled, only display times are adjusted (check serial monitor with DEBUG defined!)               
  97.    This will also add options for setting the date (Year/Month/Day) when setting time on the clock!      */
  98. #ifdef AUTODST
  99.   #include <Timezone.h>                                          // "Timezone" by Jack Christensen
  100.   TimeChangeRule *tcr;
  101.   //-----------------------------------------------
  102.   /* US */
  103.   // TimeChangeRule tcr1 = {"tcr1", First, Sun, Nov, 2, -360};   // utc -6h, valid from first sunday of november at 2am
  104.   // TimeChangeRule tcr2 = {"tcr2", Second, Sun, Mar, 2, -300};  // utc -5h, valid from second sunday of march at 2am
  105.   //-----------------------------------------------
  106.   /* Europe */
  107.   TimeChangeRule tcr1 = {"tcr1", Last, Sun, Oct, 3, 60};         // standard/winter time, valid from last sunday of october at 3am, UTC + 1 hour (+60 minutes) (negative value like -300 for utc -5h)
  108.   TimeChangeRule tcr2 = {"tcr2", Last, Sun, Mar, 2, 120};        // daylight/summer time, valid from last sunday of march at 2am, UTC + 2 hours (+120 minutes)
  109.   //-----------------------------------------------
  110.   Timezone myTimeZone(tcr1, tcr2);
  111. #endif
  112. /* End autoDST config/parameters ----------------------------------------------------------------------- */
  113. /* Start autoBrightness config/parameters -------------------------------------------------------------- */
  114. uint8_t upperLimitLDR = 180;                      // everything above this value will cause max brightness (according to current level) to be used (if it's higher than this)
  115. uint8_t lowerLimitLDR = 50;                       // everything below this value will cause minBrightness to be used
  116. uint8_t minBrightness = 30;                       // anything below this avgLDR value will be ignored
  117. const bool nightMode = false;                     // nightmode true -> if minBrightness is used, colorizeOutput() will use a single color for everything, using HSV
  118. const uint8_t nightColor[2] = { 0, 90 };          // hue 0 = red, fixed brightness of 90, https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors
  119. float factorLDR = 1.0;                            // try 0.5 - 2.0, compensation value for avgLDR. Set dbgLDR true & define DEBUG and watch the serial monitor. Looking...
  120. const bool dbgLDR = false;                        // ...for values roughly in the range of 120-160 (medium room light), 40-80 (low light) and 0 - 20 in the dark
  121. #ifdef NODEMCU
  122.   uint8_t pinLDR = 0;                             // LDR connected to A0 (nodeMCU only offers this one)
  123. #else
  124.   uint8_t pinLDR = 1;                             // LDR connected to A1 (in case somebody flashes this sketch on arduino and already has an ldr connected to A1)
  125. #endif
  126. uint8_t intervalLDR = 75;                         // read value from LDR every 75ms (most LDRs have a minimum of about 30ms - 50ms)
  127. uint16_t avgLDR = 0;                              // we will average this value somehow somewhere in readLDR();
  128. uint16_t lastAvgLDR = 0;                          // last average LDR value we got
  129. /* End autoBrightness config/parameters ---------------------------------------------------------------- */
  130. #define SKETCHNAME "ClockSketch v7.4"
  131. #define CLOCKNAME "Lazy Grid Clock v2"
  132. /* Start button config/pins----------------------------------------------------------------------------- */
  133. #ifdef NODEMCU
  134.   const uint8_t buttonA = 13;                                    // momentary push button, 1 pin to gnd, 1 pin to d7 / GPIO_13
  135.   const uint8_t buttonB = 14;                                    // momentary push button, 1 pin to gnd, 1 pin to d5 / GPIO_14
  136. #else
  137.   const uint8_t buttonA = 3;                                     // momentary push button, 1 pin to gnd, 1 pin to d3
  138.   const uint8_t buttonB = 4;                                     // momentary push button, 1 pin to gnd, 1 pin to d4
  139. #endif
  140. /* End button config/pins------------------------------------------------------------------------------- */
  141. /* Start basic appearance config------------------------------------------------------------------------ */
  142. const bool dotsBlinking = true;                                  // false will completely disable any dots on LGCv2 (except in setup, 12h mode am/pm indication)
  143. const bool leadingZero = false;                                  // true = enable a leading zero, 9:00 -> 09:00, 1:30 -> 01:30...
  144. uint8_t displayMode = 0;                                         // 0 = 24h mode, 1 = 12h mode ("1" will also override setting that might be written to EEPROM!)
  145. uint8_t colorMode = 0;                                           // different color modes, setting this to anything else than zero will overwrite values written to eeprom, as above
  146. uint16_t colorSpeed = 750;                                       // controls how fast colors change, smaller = faster (interval in ms at which color moves inside colorizeOutput();)
  147. const bool colorPreview = true;                                  // true = preview selected palette/colorMode using "8" on all positions for 3 seconds
  148. const uint8_t colorPreviewDuration = 3;                          // duration in seconds for previewing palettes/colorModes if colorPreview is enabled/true
  149. const bool reverseColorCycling = false;                          // true = reverse color movements
  150. const uint8_t brightnessLevels[3] {100, 140, 240};               // 0 - 255, brightness Levels (min, med, max) - index (0-2) will be saved to eeprom
  151. uint8_t brightness = brightnessLevels[0];                        // default brightness if none saved to eeprom yet / first run
  152. #ifdef FADING
  153.   uint8_t fadePixels = 2;                                        // fade pixels, 0 = disabled, 1 = only fade out pixels turned off, 2 = fade old out and fade new in
  154.   uint8_t fadeDelay = 20;                                        // milliseconds between each fading step, 5-25 should work okay-ish
  155. #endif
  156. /* End basic appearance config-------------------------------------------------------------------------- */
  157. /* End of basic config/parameters section */
  158. /* End of feature/parameter section, unless changing advanced things/modifying the sketch there's absolutely nothing to do further down! */
  159. /* library, wifi and ntp stuff depending on above config/parameters */
  160. #ifdef NODEMCU
  161.   #if defined ( USENTP ) && !defined ( USEWIFI )                 // enforce USEWIFI when USENTP is defined
  162.     #define USEWIFI
  163.     #pragma warning "USENTP without USEWIFI, enabling WiFi"
  164.   #endif
  165.   #ifdef USEWIFI
  166.     #include <ESP8266WiFi.h>
  167.     #include <WiFiUdp.h>
  168.   #endif
  169. #endif
  170. #ifdef USENTP
  171.   #include <NTPClient.h>
  172.   WiFiUDP ntpUDP;
  173.   NTPClient timeClient(ntpUDP, NTPHOST, 0, 60000);
  174. #endif
  175. /* end library stuff */
  176. /* setting feature combinations/options */
  177. #if defined ( FASTFORWARD )
  178.   bool firstLoop = true;
  179.   #ifdef USERTC
  180.     #undef USERTC
  181.   #endif
  182.   #ifdef USEWIFI
  183.     #undef USEWIFI
  184.   #endif
  185.   #ifdef USENTP
  186.     #undef USENTP
  187.   #endif
  188.   #ifdef AUTODST
  189.     #undef AUTODST
  190.   #endif
  191. #endif
  192. /* setting feature combinations/options */
  193. /* Start of FastLED/clock stuff */
  194. #define LEDSTUFF
  195. #ifdef LEDSTUFF
  196.   #ifdef NODEMCU
  197.     #define FASTLED_ESP8266_RAW_PIN_ORDER                        // this means we'll be using the raw esp8266 pin order -> GPIO_12, which is d6 on nodeMCU
  198.     #define LED_PIN 12                                           // led data in connected to GPIO_12 (d6/nodeMCU)
  199.   #else
  200.     #define FASTLED_ALLOW_INTERRUPTS 0                           // AVR + WS2812 + IRQ = https://github.com/FastLED/FastLED/wiki/Interrupt-problems
  201.     #define LED_PIN 6                                            // led data in connected to d6 (arduino)
  202.   #endif
  203.   
  204.   #define LED_PWR_LIMIT 500                                      // 500mA - Power limit in mA (voltage is set in setup() to 5v)
  205.   #define LED_DIGITS 4                                           // 4 or 6 digits, HH:MM or HH:MM:SS
  206.   #define LED_COUNT 83                                           // Total number of leds, 83 on LGCv2 (7x11)
  207.   #if ( LED_DIGITS == 6 )
  208.     #define LED_COUNT 83                                         // same here, no 6d version available for LGCv2
  209.   #endif
  210.   #if ( LED_DIGITS == 6 )
  211.     #define RES_X 7
  212.   #else
  213.     #define RES_X 7
  214.   #endif
  215.   #define RES_Y 11
  216.   #define CHAR_X 3
  217.   #define CHAR_Y 5
  218.   
  219.   #include <FastLED.h>
  220.   
  221.   uint8_t markerHSV[3] = { 0, 127, 30 };                         // this color will be used to "flag" leds for coloring later on while updating the leds
  222.   CRGB leds[LED_COUNT];
  223.   CRGBPalette16 currentPalette;
  224. #endif
  225. // start clock specific config/parameters
  226. #if ( LED_DIGITS == 4 )
  227.   const uint8_t digitPositions[4] = { 0, 4, 0, 4 };              // x coordinates of HH:MM, y offsets for LGCv2 will be added where needed
  228. #endif
  229. #if ( LED_DIGITS == 6 )
  230.   const uint8_t digitPositions[6] = { 0, 4, 0, 4, 7, 11 };    // x coordinates of HH:MM:SS, useless on LGCv2
  231. #endif
  232. const uint8_t digitYPosition = 0;
  233. const uint8_t characters[20][CHAR_X * CHAR_Y] PROGMEM = {
  234.   { 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1 },        // 0
  235.   { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 },        // 1
  236.   { 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1 },        // 2
  237.   { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1 },        // 3
  238.   { 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1 },        // 4
  239.   { 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1 },        // 5
  240.   { 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1 },        // 6
  241.   { 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 },        // 7
  242.   { 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 },        // 8
  243.   { 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1 },        // 9
  244.   { 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 },        // T - some letters from here on (index 10, so won't interfere with digits 0-9)
  245.   { 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0 },        // r
  246.   { 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1 },        // y
  247.   { 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 },        // d
  248.   { 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1 },        // C
  249.   { 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0 },        // F
  250.   { 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1 },        // m1 - will be drawn 2 times when used with an offset of +2 on x
  251.   { 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 },        // °
  252.   { 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1 },        // H
  253.   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }         // "blank"
  254. };
  255. uint8_t clockStatus = 1;                  // Used for various things, don't mess around with it! 1 = startup
  256.                                           // 0 = regular mode, 1 = startup, 9x = setup modes (90, 91, 92, 93...)
  257. /* these values will be saved to EEPROM:
  258.   0 = index for selected palette
  259.   1 = index for selected brightness level
  260.   2 = displayMode, 12h/24h mode
  261.   3 = colorMode */
  262. /* End of FastLED/clock stuff */
  263. // End clock specific configs/parameters
  264. /* other variables */
  265. uint8_t btnRepeatCounter = 0;         // keeps track of how often a button press has been repeated
  266. /* */
  267. /* -- this is where the fun parts start -------------------------------------------------------------------------------------------------------- */
  268. void setup() {
  269.   #ifdef DEBUG
  270.     while ( millis() < 300 ) {  // safety delay for serial output
  271.       #ifdef NODEMCU
  272.         yield();
  273.       #endif
  274.     }
  275.     #ifdef NODEMCU
  276.       Serial.begin(74880); Serial.println(F("  "));
  277.     #else
  278.       Serial.begin(57600);  Serial.println(F("  "));
  279.     #endif
  280.     #ifdef SKETCHNAME
  281.       Serial.print(SKETCHNAME); Serial.println(F(" starting up..."));
  282.     #endif
  283.     #ifdef CLOCKNAME
  284.       Serial.print("Clock Type: "); Serial.println(CLOCKNAME);
  285.     #endif
  286.     #ifdef RTCTYPE
  287.       Serial.print(F("Configured RTC: ")); Serial.println(RTCTYPE);
  288.     #endif
  289.     #ifdef LEDSTUFF
  290.       Serial.print(F("LED power limit: ")); Serial.print(LED_PWR_LIMIT); Serial.println(F(" mA"));
  291.       Serial.print(F("Total LED count: ")); Serial.println(LED_COUNT);
  292.       Serial.print(F("LED digits: ")); Serial.println(LED_DIGITS);
  293.     #endif
  294.     #ifdef AUTODST
  295.       Serial.println(F("autoDST enabled"));
  296.     #endif
  297.     #ifdef NODEMCU
  298.       Serial.println(F("Configured for nodeMCU"));
  299.       #ifdef USEWIFI
  300.         Serial.println(F("WiFi enabled"));
  301.       #endif
  302.       #ifdef USENTP
  303.         Serial.print(F("NTP enabled, NTPHOST: ")); Serial.println(NTPHOST);
  304.       #endif
  305.     #else
  306.       Serial.println(F("Configured for Arduino"));
  307.     #endif
  308.     #ifdef FASTFORWARD
  309.       Serial.println(F("!! FASTFORWARD defined !!"));
  310.     #endif
  311.     while ( millis() < 600 ) {  // safety delay for serial output
  312.       #ifdef NODEMCU
  313.         yield();
  314.       #endif
  315.     }
  316.   #endif
  317.   
  318.   #ifdef AUTOBRIGHTNESS
  319.     #ifdef DEBUG
  320.       Serial.print(F("autoBrightness enabled, LDR using pin: ")); Serial.println(pinLDR);
  321.     #endif
  322.     pinMode(pinLDR, INPUT);
  323.   #endif
  324.   
  325.   pinMode(buttonA, INPUT_PULLUP);
  326.   pinMode(buttonB, INPUT_PULLUP);
  327.   #ifdef DEBUG
  328.     if ( digitalRead(buttonA) == LOW || digitalRead(buttonB) == LOW ) {
  329.       if ( digitalRead(buttonA) == LOW ) {
  330.         Serial.println(F("buttonA is LOW / pressed - check wiring!"));
  331.       }
  332.       if ( digitalRead(buttonB) == LOW ) {
  333.         Serial.println(F("buttonB is LOW / pressed - check wiring!"));
  334.       }
  335.     }
  336.   #endif
  337.   #ifdef LEDSTUFF
  338.     FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, LED_COUNT).setCorrection(TypicalSMD5050).setTemperature(DirectSunlight).setDither(1);
  339.     FastLED.setMaxPowerInVoltsAndMilliamps(5, LED_PWR_LIMIT);
  340.     FastLED.clear();
  341.     FastLED.show();
  342.     #ifdef DEBUG
  343.       Serial.println(F("setup(): Lighting up some leds..."));
  344.     #endif
  345.     for ( uint8_t i = 0; i < LED_DIGITS; i++ ) {
  346.       setPixel(0, i);
  347.     }
  348.     FastLED.show();
  349.   #endif
  350.   #ifdef NODEMCU                                                                              // if building for nodeMCU...
  351.     #ifdef USEWIFI                                                                            // ...and if using WiFi.....
  352.       #ifdef DEBUG
  353.         Serial.println(F("Starting up WiFi..."));
  354.       #endif
  355.       WiFi.mode(WIFI_STA);                                                                    // set WiFi mode to STA...
  356.       if ( useWPS ) {
  357.         WiFi.begin(WiFi.SSID().c_str(),WiFi.psk().c_str());                                   // ...and start connecting using saved credentials...
  358.         #ifdef DEBUG
  359.           Serial.println(F("Using WPS setup / saved credentials"));
  360.         #endif
  361.       } else {
  362.         WiFi.begin(wifiSSID, wifiPWD);                                                        // ...or credentials defined in the USEWIFI config section
  363.         #ifdef DEBUG
  364.           Serial.println(F("Using credentials from sketch"));
  365.         #endif
  366.       }
  367.       unsigned long startTimer = millis();
  368.       uint8_t wlStatus = 0;
  369.       uint8_t counter = 6;
  370.       #ifdef DEBUG
  371.         Serial.print(F("Waiting for WiFi connection... "));
  372.       #endif
  373.       while ( wlStatus == 0 ) {
  374.         if ( WiFi.status() != WL_CONNECTED ) wlStatus = 0; else wlStatus = 1;
  375.         #ifdef LEDSTUFF
  376.           if ( millis() - startTimer >= 1000 ) {
  377.             FastLED.clear();
  378.             showChar(counter, digitPositions[3], digitYPosition);
  379.             FastLED.show();
  380.             if ( counter > 0 ) counter--; else wlStatus = 2;
  381.             startTimer = millis();
  382.             #ifdef DEBUG
  383.                 Serial.print(F("."));
  384.             #endif
  385.           }
  386.         #endif
  387.         #ifdef NODEMCU
  388.           yield();
  389.         #endif
  390.       }
  391.       if ( WiFi.status() == WL_CONNECTED ) {                                                  // if status is connected...
  392.         #ifdef USENTP                                                                         // ...and USENTP defined...
  393.           timeClient.begin();                                                                 // ...start timeClient
  394.         #endif
  395.       }
  396.       #ifdef DEBUG
  397.         Serial.println();
  398.         if ( WiFi.status() != 0 ) {
  399.           Serial.print(F("setup(): Connected to SSID: ")); Serial.println(WiFi.SSID());
  400.         } else Serial.println(F("setup(): WiFi connection failed."));
  401.       #endif
  402.     #endif
  403.     EEPROM.begin(512);
  404.   #endif
  405.   #ifdef USERTC
  406.     Rtc.Begin();
  407.     if ( Rtc.GetIsRunning() == false ) {
  408.       #ifdef DEBUG
  409.         Serial.println(F("setup(): RTC not running, trying to start..."));
  410.       #endif
  411.       Rtc.SetIsRunning(true);
  412.     }
  413.     #ifdef DEBUG
  414.       Serial.println(F("setup(): RTC.begin(), 2 second safety delay before"));
  415.       Serial.println(F("         doing any read/write actions!"));
  416.     #endif
  417.     unsigned long tmp_time = millis();
  418.     while ( millis() - tmp_time < 2000 ) {
  419.       #ifdef NODEMCU
  420.         yield();
  421.       #endif
  422.     }
  423.     #ifdef DEBUG
  424.       Serial.println(F("setup(): RTC initialized"));
  425.     #endif
  426.   #else
  427.     #ifdef DEBUG
  428.       Serial.println(F("setup(): No RTC defined!"));
  429.       setTime(12, 0, 0, 1, 1, 2000);
  430.     #endif
  431.   #endif
  432.   #ifdef LEDSTUFF
  433.     FastLED.clear();
  434.     FastLED.show();
  435.     /* eeprom settings */
  436.     #ifdef nodeMCU
  437.       EEPROM.begin(512);
  438.     #endif
  439.     paletteSwitcher();
  440.     brightnessSwitcher();
  441.     colorModeSwitcher();
  442.     displayModeSwitcher();
  443.   #endif
  444.   #ifdef FASTFORWARD
  445.     setTime(21, 59, 50, 31, 1, 2023);            // h, m, s, d, m, y to set the clock to when using FASTFORWARD
  446.   #endif
  447.   #ifdef USENTP
  448.     syncHelper();
  449.   #endif
  450.   clockStatus = 0;       // change from 1 (startup) to 0 (running mode)
  451.   #ifdef DEBUG
  452.     printTime();
  453.     Serial.println(F("setup() done"));
  454.     Serial.println(F("------------------------------------------------------"));
  455.   #endif
  456. }
  457. /* MAIN LOOP */
  458. void loop() {
  459.   static uint8_t lastInput = 0;                     // != 0 if any button press has been detected
  460.   static uint8_t lastSecondDisplayed = 0;           // This keeps track of the last second when the display was updated (HH:MM and HH:MM:SS)
  461.   static unsigned long lastCheckRTC = millis();     // This will be used to read system time in case no RTC is defined (not supported!)
  462.   static bool doUpdate = false;                     // Update led content whenever something sets this to true. Coloring will always happen at fixed intervals!
  463.   #ifdef USERTC
  464.     static RtcDateTime rtcTime = Rtc.GetDateTime(); // Get time from rtc
  465.   #else
  466.     static time_t sysTime = now();                  // if no rtc is defined, get local system time
  467.   #endif
  468.   #ifdef LEDSTUFF
  469.     static uint8_t refreshDelay = 5;                // refresh leds every 5ms
  470.     static long lastRefresh = millis();             // Keeps track of the last led update/FastLED.show() inside the loop
  471.     #ifdef AUTOBRIGHTNESS
  472.     static long lastReadLDR = millis();
  473.     #endif
  474.   #endif
  475.   #ifdef FASTFORWARD
  476.     static unsigned long lastFFStep = millis();     // Keeps track of last time increment if FASTFORWARD is defined
  477.   #endif
  478.   
  479.   if ( lastInput != 0 ) {                                                                  // If any button press is detected...
  480.     if ( btnRepeatCounter < 1 ) {                                                          // execute short/single press function(s)
  481.       #ifdef DEBUG
  482.         Serial.print(F("loop(): ")); Serial.print(lastInput); Serial.println(F(" (short press)"));
  483.       #endif
  484.       if ( lastInput == 1 ) {                                                              // short press button A
  485.         #ifdef LEDSTUFF
  486.           brightnessSwitcher();
  487.         #endif
  488.       }
  489.       if ( lastInput == 2 ) {                                                              // short press button B
  490.         #ifdef LEDSTUFF
  491.           paletteSwitcher();
  492.         #endif
  493.       }
  494.       if ( lastInput == 3 ) {                                                              // short press button A + button B
  495.       }
  496.     } else if ( btnRepeatCounter > 8 ) {                                                   // execute long press function(s)...
  497.       btnRepeatCounter = 1;                                                                // ..reset btnRepeatCounter to stop this from repeating
  498.       #ifdef DEBUG
  499.         Serial.print(F("loop(): ")); Serial.print(lastInput); Serial.println(F(" (long press)"));
  500.       #endif
  501.       if ( lastInput == 1 ) {                                                              // long press button A
  502.         #ifdef LEDSTUFF
  503.           colorModeSwitcher();
  504.         #endif
  505.       }
  506.       if ( lastInput == 2 ) {                                                              // long press button B
  507.         #ifdef LEDSTUFF
  508.           displayModeSwitcher();
  509.         #endif
  510.       }
  511.       if ( lastInput == 3) {                                                               // long press button A + button B
  512.         #ifdef USEWIFI                                                                     // if USEWIFI is defined and...
  513.           if ( useWPS ) {                                                                  // ...if useWPS is true...
  514.             connectWPS();                                                                  // connect WiFi using WPS
  515.           }
  516.         #else                                                                              // if USEWIFI is not defined...
  517.           #ifdef LEDSTUFF
  518.             FastLED.clear();
  519.             FastLED.show();
  520.             setupClock();                                                                  // start date/time setup
  521.           #endif
  522.         #endif
  523.       }
  524.       while ( digitalRead(buttonA) == LOW || digitalRead(buttonB) == LOW ) {               // wait until buttons are released again
  525.         #ifdef LEDSTUFF
  526.           if ( millis() % 50 == 0 ) {                                                      // Refresh leds every 50ms to give optical feedback
  527.             colorizeOutput(colorMode);
  528.             FastLED.show();
  529.           }
  530.         #endif
  531.         #ifdef NODEMCU
  532.           yield();
  533.         #endif
  534.       }
  535.     }
  536.   }
  537.   
  538.   #ifdef FASTFORWARD                                                                       // if FASTFORWARD is defined...
  539.     if ( millis() - lastFFStep >= 250 ) {                                                  // ...and 250ms have passed...
  540.       adjustTime(5);                                                                       // ...add 5 seconds to current time
  541.       lastFFStep = millis();
  542.     }
  543.   #endif
  544.   if ( millis() - lastCheckRTC >= 50 ) {                                                   // check rtc/system time every 50ms
  545.     #ifdef USERTC
  546.       rtcTime = Rtc.GetDateTime();
  547.       if ( lastSecondDisplayed != rtcTime.Second() ) doUpdate = true;
  548.     #else
  549.       sysTime = now();
  550.       if ( lastSecondDisplayed != second(sysTime) ) doUpdate = true;
  551.     #endif
  552.     lastCheckRTC = millis();
  553.   }
  554.   if ( doUpdate ) {                                      // this will update the led array if doUpdate is true because of a new second from the rtc
  555.     #ifdef USERTC
  556.       setTime(rtcTime.Hour(), rtcTime.Minute(), rtcTime.Second(),
  557.               rtcTime.Day(), rtcTime.Month(), rtcTime.Year() );    // sync system time to rtc every second
  558.       #ifdef LEDSTUFF
  559.         FastLED.clear();                                 // 1A - clear all leds...
  560.         displayTime(now());                              // 2A - output rtcTime to the led array..
  561.       #endif
  562.       lastSecondDisplayed = rtcTime.Second();
  563.     #else
  564.       #ifdef LEDSTUFF
  565.         FastLED.clear();                                 // 1B - clear all leds...
  566.         displayTime(now());                              // 2B - output sysTime to the led array...
  567.       #endif
  568.       lastSecondDisplayed = second(sysTime);
  569.     #endif
  570.     #ifdef CUSTOMDISPLAY
  571.       displayMyStuff();                                  // 3AB - if customDisplay is defined this will clear the led array again to display custom values...
  572.     #endif
  573.     doUpdate = false;
  574.     #ifdef DEBUG
  575.       if ( second() % 20 == 0 ) {
  576.         printTime();
  577.       }
  578.     #endif
  579.     #ifdef USENTP                                                                          // if NTP is enabled, resync to ntp server at 3:01:00 am
  580.       time_t t = myTimeZone.toLocal(now());                                                // convert current system time to local time zone according to rules on top of the sketch
  581.       if ( hour(t) == 3 && minute(t) == 1 and second(t) == 0 ) {
  582.         syncHelper();
  583.       }
  584.     #endif
  585.   }
  586.   #ifdef LEDSTUFF
  587.     colorizeOutput(colorMode);                           // 1C, 2C, 3C...colorize the data inside the led array right now...
  588.     #ifdef AUTOBRIGHTNESS
  589.       if ( millis() - lastReadLDR >= intervalLDR ) {     // if LDR is enabled and sample interval has been reached...
  590.         readLDR();                                       // ...call readLDR();
  591.         if ( abs(avgLDR - lastAvgLDR) >= 5 ) {           // if avgLDR has changed for more than +/- 5 update lastAvgLDR
  592.           lastAvgLDR = avgLDR;
  593.           FastLED.setBrightness(avgLDR);
  594.         }
  595.         lastReadLDR = millis();
  596.       }
  597.     #endif
  598.     #ifdef FADING
  599.       pixelFader();
  600.     #endif
  601.     if ( millis() - lastRefresh >= refreshDelay ) {
  602.       FastLED.show();
  603.       lastRefresh = millis();
  604.     }
  605.   #endif
  606.   lastInput = inputButtons();
  607. }
  608. /* */
  609. #ifdef LEDSTUFF
  610. #ifdef CUSTOMDISPLAY
  611.   void displayMyStuff() {
  612.   /* One way to display custom sensor data/other things. displayMyStuff() is then called inside the doUpdate if statement inside
  613.      void loop() - after updating the leds but before calling colorizeOutput() and FastLED.show()                              */
  614.     if ( second() >= 30 && second() < 40 ) {                   // only do something if current second is 30-39
  615.       #ifdef RTC_DS3231                                        // if DS3231 is used we can read the temperature from that for demo purposes here
  616.         float rtcTemp = Rtc.GetTemperature().AsFloatDegC();    // get temperature in °C as float (25.75°C)....
  617.         uint8_t tmp = round(rtcTemp);                          // ...and round (26°C)
  618.       #else
  619.         uint8_t tmp = 99;                                      // get whatever value from whatever sensor into tmp   
  620.       #endif
  621.         FastLED.clear();
  622.         if ( LED_DIGITS == 4 ) {                                      // if 4 digits, display following content:
  623.           showChar(tmp / 10, digitPositions[0], digitYPosition + 6);  // tmp (26°C) / 10 = 2 on position 1 of HH
  624.           showChar(tmp % 10, digitPositions[1], digitYPosition + 6);  // tmp (26°C) % 10 = 6 on position 2 of HH
  625.           showChar(17, digitPositions[2], digitYPosition);            // ° symbol from array digits[][] on position 1 of MM
  626.           showChar(14, digitPositions[3], digitYPosition);            // C from array digits[][] on position 2 of MM
  627.         }
  628.         if ( LED_DIGITS == 6 ) {                                      // if 6 digits....
  629.           showChar(tmp / 10, digitPositions[2], digitYPosition + 6);  // ...do the above using MM:SS positions instead of HH:MM
  630.           showChar(tmp % 10, digitPositions[3], digitYPosition + 6);
  631.           showChar(17, digitPositions[4], digitYPosition);
  632.           showChar(14, digitPositions[5], digitYPosition);
  633.         }
  634.     }
  635.   }
  636. #endif
  637. #ifdef FADING
  638. void fadePixel(uint8_t x, uint8_t y, uint8_t amount, uint8_t fadeType) {
  639.   /* this will check if the first led of a given segment is lit and if it is, will fade by  
  640.      amount using fadeType. fadeType is important because when fading things in that where
  641.      off previously we must avoid setting them black at first - hence fadeLightBy instead
  642.      of fadeToBlack.  */
  643.   uint8_t pixel = calcPixel(x, y);
  644.   if ( leds[pixel] ) {
  645.     if ( fadeType == 0 ) {
  646.       leds[pixel].fadeToBlackBy(amount);
  647.     } else {
  648.       leds[pixel].fadeLightBy(amount);
  649.     }
  650.   }
  651. }
  652. void pixelFader() {
  653.   if ( fadePixels == 0 ) return;
  654.   static unsigned long firstRun = 0;                                                                // time when a change has been detected and fading starts
  655.   static unsigned long lastRun = 0;                                                                 // used to store time when this function was executed the last time
  656.   static boolean active = false;                                                                    // will be used as a flag when to do something / fade pixels
  657.   static uint8_t previousPixels[LED_COUNT] = { 0 };                                                 // all the pixels lit after the last run
  658.   static uint8_t currentPixels[LED_COUNT] = { 0 };                                                  // all the pixels lit right now
  659.   static uint8_t changedPixels[LED_COUNT] = { 0 };                                                  // used to store the differences -> 1 = led has been turned off, fade out, 2 = was off, fade in
  660.   static uint8_t fadeSteps = 15;                                                                    // steps used to fade in or out
  661.   lastRun = millis();
  662.   if ( !active ) {                                                                                  // this will check if....
  663.     firstRun = millis();
  664.     for ( uint8_t x = 0; x < RES_X; x++ ) {                                                         // ...any of the pixels are on....
  665.       for ( uint8_t y = 0; y < RES_Y; y++ ) {
  666.         if ( leds[calcPixel(x, y)] ) {
  667.           currentPixels[calcPixel(x, y)] = 1;
  668.         } else {
  669.           currentPixels[calcPixel(x, y)] = 0;
  670.         }
  671.         if ( currentPixels[calcPixel(x, y)] != previousPixels[calcPixel(x, y)] ) {                  // ...and compare them to the previous displayed pixels.
  672.           active = true;                                                                            // if a change has been detected, set active = true so fading gets executed
  673.           #ifdef DEBUG
  674.             Serial.print(F("pixel at: ")); Serial.print(x);
  675.             Serial.print(F(" / ")); Serial.print(y);
  676.             Serial.print(F(" was "));
  677.           #endif
  678.           if ( currentPixels[calcPixel(x, y)] == 0 ) {
  679.             changedPixels[calcPixel(x, y)] = 1;
  680.             #ifdef DEBUG
  681.               Serial.println(F("ON, is now OFF"));
  682.             #endif
  683.           } else {
  684.             changedPixels[calcPixel(x, y)] = 2;
  685.             #ifdef DEBUG
  686.               Serial.println(F("OFF, is now ON"));
  687.             #endif
  688.           }
  689.         }
  690.       }
  691.     }
  692.   }
  693.   if ( active ) {                                                                                   // this part is executed once a change has been detected....
  694.     static uint8_t counter = 1;
  695.     static unsigned long lastFadeStep = millis();
  696.     for ( uint8_t x = 0; x < RES_X; x++ ) {                                                         // redraw pixels that have turned off, so we can fade them out...
  697.       for ( uint8_t y = 0; y < RES_Y; y++ ) {
  698.         uint8_t pixel = calcPixel(x, y);
  699.         if ( changedPixels[pixel] == 1 ) {
  700.           setPixel(x, y);
  701.         }
  702.       }
  703.     }
  704.     colorizeOutput(colorMode);                                                                      // colorize again after redraw, so colors keep consistent
  705.     for ( uint8_t x = 0; x < RES_X; x++ ) {
  706.       for ( uint8_t y = 0; y < RES_Y; y++ ) {
  707.         uint8_t pixel = calcPixel(x, y);
  708.         if ( changedPixels[pixel] == 1 ) {                                                          // 1 - pixel has turned on, this one has to be faded in
  709.           fadePixel(x, y, counter * ( 255.0 / fadeSteps ), 0);                                      // fadeToBlackBy, segments supposed to be off/fading out
  710.         }
  711.         if ( changedPixels[pixel] == 2 ) {                                                          // 2 - pixel has turned off, this one has to be faded out
  712.           if ( fadePixels == 2 ) {
  713.             fadePixel(x, y, 255 - counter * ( 255.0 / fadeSteps ), 1 );                             // fadeLightBy, pixels supposed to be on/fading in
  714.           }
  715.         }
  716.       }
  717.     }
  718.     if ( millis() - lastFadeStep >= fadeDelay ) {
  719.       counter++;
  720.       lastFadeStep = millis();
  721.     }
  722.     if ( counter > fadeSteps ) {                                                                    // done with fading, reset variables...
  723.       counter = 1;
  724.       active = false;
  725.       for ( uint8_t x = 0; x < RES_X; x++ ) {                                                       // and save current pixels to previousPixels
  726.         for ( uint8_t y = 0; y < RES_Y; y++ ) {
  727.           uint8_t pixel = calcPixel(x, y);
  728.           if ( leds[pixel] ) {
  729.             previousPixels[pixel] = 1;
  730.           } else {
  731.             previousPixels[pixel] = 0;
  732.           }
  733.           changedPixels[pixel] = 0;
  734.         }
  735.       }
  736.       #ifdef DEBUG
  737.         Serial.print(F("pixel fading sequence took "));                                             // for debugging/checking duration - fading should never take longer than 1000ms!
  738.         Serial.print(millis() - firstRun);
  739.         Serial.println(F(" ms"));
  740.       #endif
  741.     }
  742.   }
  743. }
  744. #endif
  745. #ifdef AUTOBRIGHTNESS
  746. void readLDR() {                                                                                            // read LDR value 5 times and write average to avgLDR
  747.   static uint8_t runCounter = 1;
  748.   static uint16_t tmp = 0;
  749.   uint8_t readOut = map(analogRead(pinLDR), 0, 1023, 0, 250);
  750.   tmp += readOut;
  751.   if (runCounter == 5) {
  752.     avgLDR = ( tmp / 5 )  * factorLDR;
  753.     tmp = 0; runCounter = 0;
  754.     #ifdef DEBUG
  755.       if ( dbgLDR ) {
  756.         Serial.print(F("readLDR(): avgLDR value: "));
  757.         Serial.print(avgLDR);
  758.       }
  759.     #endif
  760.     if ( avgLDR < minBrightness ) avgLDR = minBrightness;
  761.     if ( avgLDR > brightness ) avgLDR = brightness;
  762.     if ( avgLDR >= upperLimitLDR && avgLDR < brightness ) avgLDR = brightness;                              // if avgLDR is above upperLimitLDR switch to max current brightness
  763.     if ( avgLDR <= lowerLimitLDR ) avgLDR = minBrightness;                                                  // if avgLDR is below lowerLimitLDR switch to minBrightness
  764.     #ifdef DEBUG
  765.       if ( dbgLDR ) {
  766.         Serial.print(F(" - adjusted to: "));
  767.         Serial.println(avgLDR);
  768.       }
  769.     #endif
  770.   }
  771.   runCounter++;
  772. }
  773. #endif
  774. void setupClock() {
  775. /* This sets time and date (if AUTODST is defined) on the clock/rtc */
  776.   clockStatus = 90;                                                                         // clockStatus 9x = setup, relevant for other functions/coloring
  777.   while ( digitalRead(buttonA) == LOW || digitalRead(buttonB) == LOW ) {                    // do nothing until both buttons are released to avoid accidental inputs right away
  778.     #ifdef NODEMCU
  779.       yield();
  780.     #endif
  781.   }
  782.   tmElements_t setupTime;                                                                   // Create a time element which will be used. Using the current time would
  783.   setupTime.Hour = 12;                                                                      // give some problems (like time still running while setting hours/minutes)
  784.   setupTime.Minute = 0;                                                                     // Setup starts at 12 (12 pm) (utc 12 if AUTODST is defined)
  785.   setupTime.Second = 0;                                                                     //
  786.   setupTime.Day = 1;                                                                        // date settings only used when AUTODST is defined, but will set them anyways
  787.   setupTime.Month = 2;                                                                      // see above
  788.   setupTime.Year = 23;                                                                      // current year - 2000 (2023 - 2000 = 23)
  789.   #ifdef USERTC
  790.     RtcDateTime writeTime;
  791.   #endif
  792.   #ifdef AUTODST
  793.     clockStatus = 91;                                                                       // 91 = y/m/d setup
  794.     uint8_t y, m, d;
  795.     y = getUserInput(12, 19, 23, 99);                                                       // show Y + blank, get value from 21 - 99 into y
  796.     setupTime.Year = y;
  797.     m = getUserInput(16, 19, 1, 12);                                                        // show M, get value from 1 - 12 into m
  798.     setupTime.Month = m;
  799.     if ( m == 2 ) {
  800.       if ( leapYear(y + 2000) ) {                                                           // check for leap year...
  801.         #ifdef DEBUG                                                                        // ...and get according day input ranges for each month
  802.           Serial.println(F("setupClock(): Leap year detected"));
  803.         #endif
  804.         d = getUserInput(13, 19, 1, 29);
  805.       } else {
  806.         d = getUserInput(13, 19, 1, 28);
  807.       }
  808.     }
  809.     if ( m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12 ) {
  810.       d = getUserInput(13, 19, 1, 31);
  811.     }
  812.     if ( m == 4 || m == 6 || m == 9 || m == 11 ) {
  813.       d = getUserInput(13, 19, 1, 30);      
  814.     }
  815.     setupTime.Day = d;
  816.     #ifdef USERTC
  817.       writeTime = { 2000 + y, setupTime.Month, setupTime.Day,
  818.                     setupTime.Hour, setupTime.Minute, setupTime.Second };
  819.       Rtc.SetDateTime(writeTime);
  820.       setTime(writeTime.Hour(), writeTime.Minute(), writeTime.Second(), writeTime.Day(), writeTime.Month(), writeTime.Year());
  821.     #else
  822.       setTime(setupTime.Hour, setupTime.Minute, setupTime.Second, setupTime.Day, setupTime.Month, setupTime.Year + 30);
  823.     #endif
  824.     #ifdef DEBUG
  825.       Serial.print(F("setupClock(): now is ")); Serial.println(now());
  826.     #endif
  827.   #endif
  828.   uint8_t lastInput = 0;
  829.   // hours
  830.   while ( lastInput != 2 ) {
  831.     clockStatus = 92;                                                                      // 92 = HH setup
  832.     if ( lastInput == 1 ) {
  833.       if ( setupTime.Hour < 23 ) {
  834.         setupTime.Hour++;
  835.       } else {
  836.         setupTime.Hour = 0;
  837.       }
  838.     }
  839.     displayTime(makeTime(setupTime));
  840.     lastInput = inputButtons();
  841.   }
  842.   lastInput = 0;
  843.   // minutes
  844.   while ( lastInput != 2 ) {
  845.     clockStatus = 93;                                                                      // 93 = MM setup
  846.     if ( lastInput == 1 ) {
  847.       if ( setupTime.Minute < 59 ) {
  848.         setupTime.Minute++;
  849.       } else {
  850.         setupTime.Minute = 0;
  851.       }
  852.     }
  853.     displayTime(makeTime(setupTime));
  854.     lastInput = inputButtons();
  855.   }
  856.   lastInput = 0;
  857.   // seconds
  858.   if ( LED_DIGITS == 6 ) {
  859.     while ( lastInput != 2 ) {
  860.       clockStatus = 94;                                                                    // 94 = SS setup
  861.       if ( lastInput == 1 ) {
  862.         if ( setupTime.Second < 59 ) {
  863.           setupTime.Second++;
  864.         } else {
  865.           setupTime.Second = 0;
  866.         }
  867.       }
  868.       displayTime(makeTime(setupTime));
  869.       lastInput = inputButtons();
  870.     }
  871.     lastInput = 0;
  872.   }
  873.   #ifdef DEBUG
  874.     #ifdef AUTODST
  875.       Serial.print(F("setupClock(): "));
  876.       Serial.print(F("Y/M/D -> "));
  877.       Serial.print(2000 + setupTime.Year); Serial.print(F("/"));
  878.       Serial.print(setupTime.Month); Serial.print(F("/"));
  879.       Serial.println(setupTime.Day);
  880.     #endif
  881.     Serial.print(F("setupClock(): "));
  882.     Serial.print(F("HH:MM:SS -> "));
  883.     #ifdef AUTODST
  884.       Serial.print(F("AUTODST enabled, setting LOCAL time -> "));
  885.     #endif
  886.     if ( setupTime.Hour < 10 ) Serial.print(F("0"));
  887.     Serial.print(setupTime.Hour); Serial.print(F(":"));
  888.     if ( setupTime.Minute < 10 ) Serial.print(F("0"));
  889.     Serial.print(setupTime.Minute); Serial.print(F(":"));
  890.     if ( setupTime.Second < 10 ) Serial.print(F("0"));
  891.     Serial.println(setupTime.Second);
  892.   #endif
  893.   #ifdef USERTC
  894.     writeTime = { 2000 + setupTime.Year, setupTime.Month, setupTime.Day,
  895.                   setupTime.Hour, setupTime.Minute, setupTime.Second };
  896.     #ifdef AUTODST
  897.       setupTime.Year += 30;
  898.       time_t t = myTimeZone.toUTC(makeTime(setupTime)); // get UTC time from entered time
  899.       writeTime = { year(t), month(t), day(t), hour(t), minute(t), second(t) };
  900.     #endif
  901.     Rtc.SetDateTime(writeTime);
  902.     setTime(writeTime.Hour(), writeTime.Minute(), writeTime.Second(), writeTime.Day(), writeTime.Month(), writeTime.Year());
  903.     #ifdef DEBUG
  904.       Serial.println(F("setupClock(): RTC time set"));
  905.       printTime();
  906.     #endif
  907.   #else
  908.     #ifdef AUTODST
  909.       setupTime.Year += 30;
  910.       time_t t = myTimeZone.toUTC(makeTime(setupTime)); // get UTC time from entered time
  911.       setTime(t);
  912.     #else
  913.       setupTime.Year += 30;
  914.       setTime(makeTime(setupTime));
  915.     #endif
  916.   #endif
  917.   clockStatus = 0;
  918.   #ifdef DEBUG
  919.     Serial.println(F("setupClock() done"));
  920.   #endif
  921. }
  922. uint16_t getUserInput(uint8_t sym1, uint8_t sym2, uint8_t startVal, uint8_t endVal) {
  923. /* This will show two symbols on HH and allow to enter a 2 digit value using the buttons
  924.    and display the value on MM.                                                                                        */
  925.   static uint8_t lastInput = 0;
  926.   static uint8_t currentVal = startVal;
  927.   static bool newInput = true;
  928.   if ( newInput ) {
  929.     currentVal = startVal;
  930.     newInput = false;
  931.   }
  932.   while ( lastInput != 2 ) {
  933.     if ( lastInput == 1 ) {
  934.       if ( currentVal < endVal ) {
  935.         currentVal++;
  936.       } else {
  937.         currentVal = startVal;
  938.       }
  939.     }
  940.     FastLED.clear();
  941.     showChar(sym1, digitPositions[0], digitYPosition + 6);
  942.     if ( sym1 == 16 ) showChar(sym1, digitPositions[0] + 2, digitYPosition + 6); // draw index 16 two times with an offset to get a "M"
  943.     showChar(sym2, digitPositions[1], digitYPosition + 6);
  944.     showChar(currentVal / 10, digitPositions[2], digitYPosition);
  945.     showChar(currentVal % 10, digitPositions[3], digitYPosition);
  946.     if ( millis() % 30 == 0 ) {
  947.       colorizeOutput(colorMode);
  948.       FastLED.show();
  949.       }
  950.     lastInput = inputButtons();
  951.   }
  952.   #ifdef DEBUG
  953.     Serial.print(F("getUserInput(): returned ")); Serial.println(currentVal);
  954.   #endif
  955.   lastInput = 0;
  956.   newInput = true;
  957.   return currentVal;
  958.   #ifdef DEBUG
  959.     Serial.print(F("getUserInput(): returned ")); Serial.println(currentVal);
  960.   #endif
  961. }
  962. void colorizeOutput(uint8_t mode) {
  963. /* So far showChar()/setPixel() only set some leds inside the array to values from "markerHSV" but we haven't updated
  964.    the leds yet using FastLED.show(). This function does the coloring of the right now single colored but "insivible"
  965.    output. This way color updates/cycles aren't tied to updating display contents                                      */
  966.   static unsigned long lastColorChange = 0;
  967.   static uint8_t startColor = 0;
  968.   static uint8_t colorOffset = 0;              // different offsets result in quite different results, depending on the amount of leds inside each segment...
  969.                                                // ...so it's set inside each color mode if required
  970.   /* mode 0 = simply assign different colors with an offset of "colorOffset" to each led based on its x position -> each digit gets its own color */
  971.   if ( mode == 0 ) {
  972.     colorOffset = 32;
  973.     for ( uint8_t pos = 0; pos < LED_DIGITS; pos++ ) {
  974.       for ( uint8_t x = digitPositions[pos]; x < digitPositions[pos] + CHAR_X; x++ ) {
  975.         if ( pos < 2 ) {
  976.           for ( uint8_t y = 6; y < RES_Y; y++ ) {
  977.             if ( leds[calcPixel(x, y)] ) leds[calcPixel(x, y)] = ColorFromPalette(currentPalette, startColor - pos * colorOffset, brightness, LINEARBLEND);
  978.           }
  979.         } else {
  980.           for ( uint8_t y = 0; y < 6; y++ ) {
  981.             if ( leds[calcPixel(x, y)] ) leds[calcPixel(x, y)] = ColorFromPalette(currentPalette, startColor - pos * colorOffset, brightness, LINEARBLEND);
  982.           }
  983.         }
  984.       }
  985.     }
  986.     // following will color the dots in the same way
  987.     for ( uint8_t x = 0; x < RES_X; x++ ) {
  988.       if ( leds[calcPixel(x, 5)] ) leds[calcPixel(x, 5)] = ColorFromPalette(currentPalette, startColor - 8 * colorOffset, brightness, LINEARBLEND);
  989.     }
  990.   }
  991.   /* mode 1 = simply assign different colors with an offset of "colorOffset" to each led based on its y coordinate -> each digit with a top/down gradient */
  992.   if ( mode == 1 ) {
  993.     colorOffset = 16;
  994.     for ( uint8_t pos = 0; pos < LED_DIGITS; pos++ ) {
  995.       for ( uint8_t x = digitPositions[pos]; x < digitPositions[pos] + CHAR_X; x++ ) {
  996.         for ( uint8_t y = 0; y < RES_Y; y++ ) {
  997.           if ( leds[calcPixel(x, y)] ) leds[calcPixel(x, y)] = ColorFromPalette(currentPalette, startColor - y * colorOffset, brightness, LINEARBLEND);
  998.         }
  999.       }
  1000.     }
  1001.     // following will color the dots in the same way
  1002.     for ( uint8_t x = 0; x < RES_X; x++ ) {
  1003.       if ( leds[calcPixel(x, 5)] ) leds[calcPixel(x, 5)] = ColorFromPalette(currentPalette, startColor - 12 * colorOffset, brightness, LINEARBLEND);
  1004.     }
  1005.   }
  1006.   /* clockStatus >= 90 is used for coloring output while in setup mode */
  1007.   if ( clockStatus >= 90 ) {
  1008.     static boolean blinkFlag = true;
  1009.     static unsigned long lastBlink = millis();
  1010.     static uint8_t b = brightnessLevels[0];
  1011.     if ( millis() - lastBlink > 333 ) {                    // blink switch frequency, 3 times a second
  1012.       if ( blinkFlag ) {
  1013.         blinkFlag = false;
  1014.         b = brightnessLevels[1];
  1015.       } else {
  1016.         blinkFlag = true;
  1017.         b = brightnessLevels[0];
  1018.       }
  1019.       lastBlink = millis();
  1020.     }                                                      // unset values = red, set value = green, current value = yellow and blinkinkg
  1021.     for ( uint8_t pos = 0; pos < LED_DIGITS; pos++ ) {
  1022.       if ( clockStatus == 91 ) {  // Y/M/D setup
  1023.         colorHelper(digitPositions[0], digitYPosition + 6, 0, 255, brightness);
  1024.         colorHelper(digitPositions[0] + 2, digitYPosition + 6, 0, 255, brightness); // offset for double drawn "M"
  1025.         colorHelper(digitPositions[1], digitYPosition, 0, 255, brightness);
  1026.         colorHelper(digitPositions[2], digitYPosition, 64, 255, b);
  1027.         colorHelper(digitPositions[3], digitYPosition, 64, 255, b);
  1028.       }
  1029.       if ( clockStatus == 92 ) { // hours
  1030.         colorHelper(digitPositions[0], digitYPosition + 6, 64, 255, b);
  1031.         colorHelper(digitPositions[1], digitYPosition + 6, 64, 255, b);
  1032.         colorHelper(digitPositions[2], digitYPosition, 0, 255, brightness);
  1033.         colorHelper(digitPositions[3], digitYPosition, 0, 255, brightness);
  1034.       }
  1035.       if ( clockStatus == 93 ) {  // minutes
  1036.         colorHelper(digitPositions[0], digitYPosition + 6, 96, 255, brightness);
  1037.         colorHelper(digitPositions[1], digitYPosition + 6, 96, 255, brightness);
  1038.         colorHelper(digitPositions[2], digitYPosition, 64, 255, b);
  1039.         colorHelper(digitPositions[3], digitYPosition, 64, 255, b);
  1040.       }
  1041.       if ( clockStatus == 94 ) {  // seconds
  1042.         colorHelper(digitPositions[0], digitYPosition + 6, 96, 255, brightness);
  1043.         colorHelper(digitPositions[1], digitYPosition + 6, 96, 255, brightness);
  1044.         colorHelper(digitPositions[2], digitYPosition, 96, 255, brightness);
  1045.         colorHelper(digitPositions[3], digitYPosition, 96, 255, brightness);
  1046.       }
  1047.     }
  1048.     for ( uint8_t x = 0; x < RES_X; x++ ) {
  1049.       if ( leds[calcPixel(x, 5)] ) leds[calcPixel(x, 5)] = ColorFromPalette(currentPalette, startColor - 5 * colorOffset, brightness, LINEARBLEND);
  1050.     }
  1051.   }
  1052.   #ifdef FASTFORWARD
  1053.     if ( millis() - lastColorChange > 15 ) {
  1054.   #else
  1055.     if ( millis() - lastColorChange > colorSpeed ) {
  1056.   #endif
  1057.     if ( reverseColorCycling ) {
  1058.       startColor--;
  1059.     } else {
  1060.       startColor++;
  1061.     }
  1062.     lastColorChange = millis();
  1063.   }
  1064.   #ifdef AUTOBRIGHTNESS
  1065.   if ( nightMode && clockStatus == 0 ) {                           // nightmode will overwrite everything that has happened so far...
  1066.     for ( uint16_t i = 0; i < LED_COUNT; i++ ) {
  1067.       if ( leds[i] ) {
  1068.         if ( avgLDR == minBrightness ) {
  1069.           leds[i].setHSV(nightColor[0], 255, nightColor[1] );      // and assign nightColor to all lit leds. Default is a very dark red.
  1070.           FastLED.setDither(0);
  1071.         } else {
  1072.           FastLED.setDither(1);
  1073.         }
  1074.       }
  1075.     }
  1076.   }
  1077.   #endif
  1078.   
  1079. /*  // example for time based coloring
  1080.   // for coloring based on current times the following will get local display time into
  1081.   // checkTime if autoDST is defined as the clock is running in utc time then
  1082.   #ifdef AUTODST
  1083.     time_t checkTime = myTimeZone.toLocal(now());
  1084.   #else
  1085.     time_t checkTime = now();
  1086.   #endif
  1087.   // below if-loop simply checks for a given time and colors everything in green/blue accordingly
  1088.   if ( hour(checkTime) > 6 && hour(checkTime) <= 22 ) {           // if hour > 6 AND hour <= 22 ---> 07:00 - 22:59
  1089.     for ( uint16_t i = 0; i < LED_COUNT; i++ ) {                  // for each position...
  1090.       if ( leds[i] ) {                                            // ...check led and if it's lit...
  1091.         leds[i].setHSV(96, 255, brightness);                      // ...redraw with HSV color 96 -> green
  1092.       }
  1093.     }
  1094.   } else {                                                        // ---> 23:00 - 06:59
  1095.     for ( uint16_t i = 0; i < LED_COUNT; i++ ) {                  // for each position...
  1096.       if ( leds[i] ) {                                            // ...check led and if it's lit...
  1097.         leds[i].setHSV(160, 255, brightness);                     // ...redraw with HSV color 160 -> blue
  1098.       }
  1099.     }
  1100.   }
  1101. */
  1102. }
  1103. void colorHelper(uint8_t xpos, uint8_t ypos, uint8_t hue, uint8_t sat, uint8_t bri) {
  1104.   for ( uint8_t x = xpos; x < xpos + CHAR_X; x++ ) {
  1105.     for ( uint8_t y = ypos; y < ypos + CHAR_Y; y++ ) {
  1106.       if ( leds[calcPixel(x, y)] ) leds[calcPixel(x, y)].setHSV(hue, sat, bri);
  1107.     }
  1108.   }
  1109. }
  1110. void displayTime(time_t t) {
  1111.   #ifdef AUTODST
  1112.     if ( clockStatus < 90 ) {                             // display adjusted times only while NOT in setup
  1113.       t = myTimeZone.toLocal(t);                          // convert display time to local time zone according to rules on top of the sketch
  1114.     }
  1115.   #endif
  1116.   if ( clockStatus >= 90 ) {
  1117.     FastLED.clear();
  1118.   }
  1119.   /* hours */
  1120.   if ( displayMode == 0 ) {
  1121.     if ( hour(t) < 10 ) {
  1122.       if ( leadingZero ) {
  1123.         showChar(0, digitPositions[0], digitYPosition + 6);
  1124.       }
  1125.     } else {
  1126.       showChar(hour(t) / 10, digitPositions[0], digitYPosition + 6);
  1127.     }
  1128.     showChar(hour(t) % 10, digitPositions[1], digitYPosition + 6);
  1129.   } else if ( displayMode == 1 ) {
  1130.     if ( hourFormat12(t) < 10 ) {
  1131.       if ( leadingZero ) {
  1132.         showChar(0, digitPositions[0], digitYPosition + 6);
  1133.       }
  1134.     } else {
  1135.       showChar(hourFormat12(t) / 10, digitPositions[0], digitYPosition + 6);
  1136.     }
  1137.     showChar(hourFormat12(t) % 10, digitPositions[1], digitYPosition + 6);
  1138.   }
  1139.   /* minutes */
  1140.   showChar(minute(t) / 10, digitPositions[2], digitYPosition);
  1141.   showChar(minute(t) % 10, digitPositions[3], digitYPosition);
  1142.   if ( LED_DIGITS == 6 ) {
  1143.     /* seconds */
  1144.     showChar(second(t) / 10, digitPositions[4], digitYPosition);
  1145.     showChar(second(t) % 10, digitPositions[5], digitYPosition);
  1146.   }
  1147.   if ( clockStatus >= 90 ) {                                      // in setup modes displayTime will also use colorizeOutput/FastLED.show!
  1148.     static unsigned long lastRefresh = millis();
  1149.     if ( isAM(t) && displayMode == 1 ) {                          // in 12h mode and if it's AM only light up the upper dots (while setting time)
  1150.       showDots(1);
  1151.     } else {
  1152.       showDots(2);
  1153.     }
  1154.     if ( millis() - lastRefresh >= 25 ) {
  1155.       colorizeOutput(colorMode);
  1156.       FastLED.show();
  1157.       lastRefresh = millis();
  1158.     }
  1159.     return;
  1160.   }
  1161.   /* dots */
  1162.   if ( dotsBlinking ) {
  1163.     if ( second(t) % 2 == 0 ) {
  1164.       showDots(2);
  1165.     }
  1166.   }
  1167. }
  1168. void setPixel(uint8_t x, uint8_t y) {
  1169.   uint8_t pixel = 0;
  1170.   if ( x < RES_X && y < RES_Y ) {
  1171.     if ( ( x % 2 ) == 0 ) pixel = x + ( x * RES_Y ) + y;
  1172.       else pixel = ( ( x + 1 ) * RES_Y ) - y + x;
  1173.     leds[pixel].setHSV(markerHSV[0], markerHSV[1], markerHSV[2]);
  1174.   }
  1175. }
  1176. uint16_t calcPixel(uint8_t x, uint8_t y) {                   // returns led # located at x/y
  1177.   uint8_t pixel = 0;
  1178.   if ( ( x % 2 ) == 0 ) pixel = x + ( x * RES_Y ) + y;
  1179.     else pixel = ( ( x + 1 ) * RES_Y ) - y + x;
  1180.   return pixel;
  1181. }
  1182. void showDots(uint8_t dots) {
  1183.   if ( dots == 1 ) {
  1184.     if ( displayMode == 1 && !isAM() ) setPixel(1, 5);
  1185.   } else {
  1186.     setPixel(3, 5);
  1187.   }
  1188. }
  1189. void showChar(uint8_t character, uint8_t x, uint8_t y) {        // show character with lower left corner at position x/y
  1190.   for ( uint8_t i = 0; i < ( CHAR_X * CHAR_Y ); i++ ) {
  1191.     if ( pgm_read_byte_near(&characters[character][i]) == 1 ) {
  1192.       setPixel(x + ( i - ( ( i / CHAR_X ) * CHAR_X ) ), ( y + CHAR_Y - 1 ) - ( i / CHAR_X ) );
  1193.     }
  1194.   }
  1195. }
  1196. void paletteSwitcher() {   
  1197. /* As the name suggests this takes care of switching palettes. When adding palettes, make sure paletteCount increases
  1198.   accordingly.  A few examples of gradients/solid colors by using RGB values or HTML Color Codes  below               */
  1199.   static uint8_t paletteCount = 6;                                          
  1200.   static uint8_t currentIndex = 0;
  1201.   if ( clockStatus == 1 ) {                                                 // Clock is starting up, so load selected palette from eeprom...
  1202.     uint8_t tmp = EEPROM.read(0);
  1203.     if ( tmp >= 0 && tmp < paletteCount ) {
  1204.       currentIndex = tmp;                                                   // 255 from eeprom would mean there's nothing been written yet, so checking range...
  1205.     } else {
  1206.       currentIndex = 0;                                                     // ...and default to 0 if returned value from eeprom is not 0 - 6
  1207.     }
  1208.     #ifdef DEBUG
  1209.       Serial.print(F("paletteSwitcher(): loaded EEPROM value "));
  1210.       Serial.println(tmp);
  1211.     #endif
  1212.   }
  1213.   switch ( currentIndex ) {
  1214.     case 0: currentPalette = CRGBPalette16( CRGB( 224,   0,  32 ),
  1215.                                             CRGB(   0,   0, 244 ),
  1216.                                             CRGB( 128,   0, 128 ),
  1217.                                             CRGB( 224,   0,  64 ) ); break;
  1218.     case 1: currentPalette = CRGBPalette16( CRGB( 224,  16,   0 ),
  1219.                                             CRGB( 192,  64,   0 ),
  1220.                                             CRGB( 192, 128,   0 ),
  1221.                                             CRGB( 240,  40,   0 ) ); break;
  1222.     case 2: currentPalette = CRGBPalette16( CRGB::Aquamarine,
  1223.                                             CRGB::Turquoise,
  1224.                                             CRGB::Blue,
  1225.                                             CRGB::DeepSkyBlue   ); break;
  1226.     case 3: currentPalette = RainbowColors_p; break;
  1227.     case 4: currentPalette = PartyColors_p; break;
  1228.     case 5: currentPalette = CRGBPalette16( CRGB::LawnGreen ); break;
  1229.   }
  1230.   #ifdef DEBUG
  1231.     Serial.print(F("paletteSwitcher(): selected palette "));
  1232.     Serial.println(currentIndex);
  1233.   #endif
  1234.   if ( clockStatus == 0 ) {                             // only save selected palette to eeprom if clock is in normal running mode, not while in startup/setup/whatever
  1235.     EEPROM.put(0, currentIndex);
  1236.     #ifdef NODEMCU
  1237.       EEPROM.commit();
  1238.     #endif
  1239.     #ifdef DEBUG
  1240.       Serial.print(F("paletteSwitcher(): saved index "));
  1241.       Serial.print(currentIndex);
  1242.       Serial.println(F(" to eeprom"));
  1243.     #endif
  1244.   }
  1245.   if ( currentIndex < paletteCount - 1 ) {
  1246.     currentIndex++;   
  1247.   } else {
  1248.     currentIndex = 0;
  1249.   }
  1250.   if ( colorPreview ) {
  1251.     previewMode();
  1252.   }
  1253.   #ifdef DEBUG
  1254.     Serial.println(F("paletteSwitcher() done"));
  1255.   #endif
  1256. }
  1257. void brightnessSwitcher() {
  1258.   static uint8_t currentIndex = 0;
  1259.   if ( clockStatus == 1 ) {                                                 // Clock is starting up, so load selected palette from eeprom...
  1260.     uint8_t tmp = EEPROM.read(1);
  1261.     if ( tmp >= 0 && tmp < 3 ) {
  1262.       currentIndex = tmp;                                                   // 255 from eeprom would mean there's nothing been written yet, so checking range...
  1263.     } else {
  1264.       currentIndex = 0;                                                     // ...and default to 0 if returned value from eeprom is not 0 - 2
  1265.     }
  1266.     #ifdef DEBUG
  1267.       Serial.print(F("brightnessSwitcher(): loaded EEPROM value "));
  1268.       Serial.println(tmp);
  1269.     #endif
  1270.   }
  1271.   switch ( currentIndex ) {
  1272.     case 0: brightness = brightnessLevels[currentIndex]; break;
  1273.     case 1: brightness = brightnessLevels[currentIndex]; break;
  1274.     case 2: brightness = brightnessLevels[currentIndex]; break;
  1275.   }
  1276.   #ifdef DEBUG
  1277.     Serial.print(F("brightnessSwitcher(): selected brightness index "));
  1278.     Serial.println(currentIndex);
  1279.   #endif
  1280.   if ( clockStatus == 0 ) {                             // only save selected brightness to eeprom if clock is in normal running mode, not while in startup/setup/whatever
  1281.     EEPROM.put(1, currentIndex);
  1282.     #ifdef NODEMCU
  1283.       EEPROM.commit();
  1284.     #endif
  1285.     #ifdef DEBUG
  1286.       Serial.print(F("brightnessSwitcher(): saved index "));
  1287.       Serial.print(currentIndex);
  1288.       Serial.println(F(" to eeprom"));
  1289.     #endif
  1290.   }
  1291.   if ( currentIndex < 2 ) {
  1292.     currentIndex++;   
  1293.   } else {
  1294.     currentIndex = 0;
  1295.   }
  1296.   #ifdef DEBUG
  1297.     Serial.println(F("brightnessSwitcher() done"));
  1298.   #endif
  1299. }
  1300. void colorModeSwitcher() {
  1301.   static uint8_t currentIndex = 0;
  1302.   if ( clockStatus == 1 ) {                                                 // Clock is starting up, so load selected palette from eeprom...
  1303.     if ( colorMode != 0 ) return;                                           // 0 is default, if it's different on startup the config is set differently, so exit here
  1304.     uint8_t tmp = EEPROM.read(3);
  1305.     if ( tmp >= 0 && tmp < 2 ) {                                            // make sure tmp < 2 is increased if color modes are added in colorizeOutput()!
  1306.       currentIndex = tmp;                                                   // 255 from eeprom would mean there's nothing been written yet, so checking range...
  1307.     } else {
  1308.       currentIndex = 0;                                                     // ...and default to 0 if returned value from eeprom is not 0 - 2
  1309.     }
  1310.     #ifdef DEBUG
  1311.       Serial.print(F("colorModeSwitcher(): loaded EEPROM value "));
  1312.       Serial.println(tmp);
  1313.     #endif
  1314.   }
  1315.   colorMode = currentIndex;
  1316.   #ifdef DEBUG
  1317.     Serial.print(F("colorModeSwitcher(): selected colorMode "));
  1318.     Serial.println(currentIndex);
  1319.   #endif
  1320.   if ( clockStatus == 0 ) {                             // only save selected colorMode to eeprom if clock is in normal running mode, not while in startup/setup/whatever
  1321.     EEPROM.put(3, currentIndex);
  1322.     #ifdef NODEMCU
  1323.       EEPROM.commit();
  1324.     #endif
  1325.     #ifdef DEBUG
  1326.       Serial.print(F("colorModeSwitcher(): saved index "));
  1327.       Serial.print(currentIndex);
  1328.       Serial.println(F(" to eeprom"));
  1329.     #endif
  1330.   }
  1331.   if ( currentIndex < 1 ) {
  1332.     currentIndex++;   
  1333.   } else {
  1334.     currentIndex = 0;
  1335.   }
  1336.   if ( colorPreview ) {
  1337.     previewMode();
  1338.   }
  1339.   #ifdef DEBUG
  1340.     Serial.println(F("colorModeSwitcher() done"));
  1341.   #endif
  1342. }
  1343. void displayModeSwitcher() {
  1344.   static uint8_t currentIndex = 0;
  1345.   if ( clockStatus == 1 ) {                                                 // Clock is starting up, so load selected palette from eeprom...
  1346.     if ( displayMode != 0 ) return;                                         // 0 is default, if it's different on startup the config is set differently, so exit here
  1347.     uint8_t tmp = EEPROM.read(2);
  1348.     if ( tmp >= 0 && tmp < 2 ) {                                            // make sure tmp < 2 is increased if display modes are added
  1349.       currentIndex = tmp;                                                   // 255 from eeprom would mean there's nothing been written yet, so checking range...
  1350.     } else {
  1351.       currentIndex = 0;                                                     // ...and default to 0 if returned value from eeprom is not 0 - 1 (24h/12h mode)
  1352.     }
  1353.     #ifdef DEBUG
  1354.       Serial.print(F("displayModeSwitcher(): loaded EEPROM value "));
  1355.       Serial.println(tmp);
  1356.     #endif
  1357.   }
  1358.   displayMode = currentIndex;
  1359.   #ifdef DEBUG
  1360.     Serial.print(F("displayModeSwitcher(): selected displayMode "));
  1361.     Serial.println(currentIndex);
  1362.   #endif
  1363.   if ( clockStatus == 0 ) {                             // only save selected colorMode to eeprom if clock is in normal running mode, not while in startup/setup/whatever
  1364.     EEPROM.put(2, currentIndex);
  1365.     #ifdef NODEMCU
  1366.       EEPROM.commit();
  1367.     #endif
  1368.     #ifdef DEBUG
  1369.       Serial.print(F("displayModeSwitcher(): saved index "));
  1370.       Serial.print(currentIndex);
  1371.       Serial.println(F(" to eeprom"));
  1372.     #endif
  1373.   }
  1374.   if ( clockStatus == 0 ) {                             // show 12h/24h for 2 seconds after selected in normal run mode, don't show this on startup (status 1)
  1375.     FastLED.clear();
  1376.     unsigned long timer = millis();
  1377.     while ( millis() - timer <= 2000 ) {
  1378.       if ( currentIndex == 0 ) {
  1379.         showChar(2, digitPositions[0], digitYPosition + 6);
  1380.         showChar(4, digitPositions[1], digitYPosition + 6);
  1381.         showChar(18, digitPositions[3], digitYPosition);
  1382.       }
  1383.       if ( currentIndex == 1 ) {
  1384.         showChar(1, digitPositions[0], digitYPosition + 6);
  1385.         showChar(2, digitPositions[1], digitYPosition + 6);
  1386.         showChar(18, digitPositions[3], digitYPosition);
  1387.       }
  1388.       colorizeOutput(colorMode);
  1389.       if ( millis() % 50 == 0 ) {
  1390.         FastLED.show();
  1391.       }
  1392.       #ifdef NODEMCU
  1393.         yield();
  1394.       #endif
  1395.     }
  1396.   }
  1397.   if ( currentIndex < 1 ) {
  1398.     currentIndex++;   
  1399.   } else {
  1400.     currentIndex = 0;
  1401.   }
  1402.   #ifdef DEBUG
  1403.     Serial.println(F("displayModeSwitcher() done"));
  1404.   #endif
  1405. }
  1406. void previewMode() {
  1407. /*  This will simply display "8" on all positions, speed up the color cyling and preview the
  1408.     selected palette or colorMode                                                            */
  1409.   if ( clockStatus == 1 ) return;                           // don't preview when starting up
  1410.   unsigned long previewStart = millis();
  1411.   uint16_t colorSpeedBak = colorSpeed;
  1412.   colorSpeed = 5;
  1413.   while ( millis() - previewStart <= uint16_t ( colorPreviewDuration * 1000L ) ) {
  1414.     for ( uint8_t i = 0; i < LED_DIGITS; i++ ) {
  1415.       if ( i < 2 ) {
  1416.         showChar(8, digitPositions[i], digitYPosition + 6);
  1417.       } else {
  1418.         showChar(8, digitPositions[i], digitYPosition);
  1419.       }
  1420.     }
  1421.     colorizeOutput(colorMode);
  1422.     FastLED.show();
  1423.     #ifdef NODEMCU
  1424.       yield();
  1425.     #endif
  1426.   }
  1427.   colorSpeed = colorSpeedBak;
  1428.   FastLED.clear();
  1429. }
  1430. #endif /* LEDSTUFF */
  1431. bool leapYear(uint16_t y) {
  1432.   boolean isLeapYear = false;
  1433.   if (y % 4 == 0) isLeapYear = true;
  1434.   if (y % 100 == 0 && y % 400 != 0) isLeapYear = false;
  1435.   if (y % 400 == 0) isLeapYear = true;
  1436.   if ( isLeapYear ) return true; else return false;
  1437. }
  1438. uint8_t inputButtons() {
  1439.   /* This scans for button presses and keeps track of delay/repeat for user inputs
  1440.      Short keypresses will only be returned when buttons are released before repeatDelay
  1441.      is reached. This is to avoid constantly sending 1 or 2 when executing a long button
  1442.      press and/or multiple buttons.
  1443.      Note: Buttons are using pinMode INPUT_PULLUP, so HIGH = not pressed, LOW = pressed! */
  1444.   static uint8_t scanInterval = 30;                                 // only check buttons every 30ms
  1445.   static uint16_t repeatDelay = 1000;                               // delay in milliseconds before repeating detected keypresses
  1446.   static uint8_t repeatRate = 1000 / 10;                            // 10 chars per 1000 milliseconds
  1447.   static uint8_t minTime = scanInterval * 2;                        // minimum time to register a button as pressed
  1448.   static unsigned long lastReadout = millis();                      // keeps track of when the last readout happened
  1449.   static unsigned long lastReturn = millis();                       // keeps track of when the last readout value was returned
  1450.   static uint8_t lastState = 0;                                     // button state from previous scan
  1451.   uint8_t currentState = 0;                                         // button state from current scan
  1452.   uint8_t retVal = 0;                                               // return value, will be 0 if no button is pressed
  1453.   static unsigned long eventStart = millis();                       // keep track of when button states are changing
  1454.   if ( millis() - lastReadout < scanInterval ) return 0;            // only scan for button presses every <scanInterval> ms
  1455.   if ( digitalRead(buttonA) == LOW ) currentState += 1;
  1456.   if ( digitalRead(buttonB) == LOW ) currentState += 2;
  1457.   if ( currentState == 0 && currentState == lastState ) {
  1458.     btnRepeatCounter = 0;
  1459.   }
  1460.   if ( currentState != 0 && currentState != lastState ) {           // if any button is pressed and different from the previous scan...
  1461.     eventStart = millis();                                          // ...reset eventStart to current time
  1462.     btnRepeatCounter = 0;                                           // ...and reset global variable btnRepeatCounter
  1463.   }
  1464.   if ( currentState != 0 && currentState == lastState ) {           // if same input has been detected at least twice (2x scanInterval)...
  1465.     if ( millis() - eventStart >= repeatDelay ) {                   // ...and longer than repeatDelay...
  1466.       if ( millis() - lastReturn >= repeatRate ) {                  // ...check for repeatRate...
  1467.         retVal = currentState;                                      // ...and set retVal to currentState
  1468.         btnRepeatCounter++;
  1469.         lastReturn = millis();
  1470.       } else retVal = 0;                                            // return 0 if repeatDelay hasn't been reached yet
  1471.     }
  1472.   }
  1473.   if ( currentState == 0 && currentState != lastState && millis() - eventStart >= minTime && btnRepeatCounter == 0 ) {
  1474.     retVal = lastState;                                             // return lastState if all buttons are released after having been pressed for <minTime> ms
  1475.     btnRepeatCounter = 0;
  1476.   }
  1477.   lastState = currentState;
  1478.   lastReadout = millis();
  1479.   #ifdef DEBUG                                                      // output some information and read serial input, if available
  1480.     uint8_t serialInput = dbgInput();
  1481.     if ( serialInput != 0 ) {
  1482.       Serial.print(F("inputButtons(): Serial input detected: ")); Serial.println(serialInput);
  1483.       retVal = serialInput;
  1484.     }
  1485.     if ( retVal != 0 ){
  1486.       Serial.print(F("inputButtons(): Return value is: ")) ; Serial.print(retVal); Serial.print(F(" - btnRepeatCounter is: ")); Serial.println(btnRepeatCounter);
  1487.     }
  1488.   #endif
  1489.   return retVal;
  1490. }
  1491. // following will only be included if USENTP is defined
  1492. #ifdef USENTP
  1493. /* This syncs system time to the RTC at startup and will periodically do other sync related
  1494.    things, like syncing rtc to ntp time */
  1495.   void syncHelper() {
  1496.     static unsigned long lastSync = millis();    // keeps track of the last time a sync attempt has been made
  1497.     if ( millis() - lastSync < 60000 && clockStatus != 1 ) return;   // only allow one ntp request per minute
  1498.     if ( WiFi.status() != WL_CONNECTED ) {
  1499.       #ifdef DEBUG
  1500.         Serial.println(F("syncHelper(): No WiFi connection"));
  1501.         return;
  1502.       #endif
  1503.     }
  1504.     #ifndef USERTC
  1505.       #ifndef USENTP
  1506.         #ifdef DEBUG
  1507.           Serial.println(F("syncHelper(): No RTC and no NTP configured, nothing to do..."));
  1508.           return;
  1509.         #endif
  1510.       #endif
  1511.     #endif
  1512.     time_t ntpTime = 0;
  1513.     #ifdef USERTC
  1514.       RtcDateTime ntpTimeConverted;
  1515.     #endif
  1516.     if ( clockStatus == 1 ) {                                                                    // looks like the sketch has just started running...
  1517.       #ifdef DEBUG
  1518.         Serial.println(F("syncHelper(): Initial sync on power up..."));
  1519.       #endif
  1520.       ntpTime = getTimeNTP();
  1521.       #ifdef DEBUG
  1522.         Serial.print(F("syncHelper(): NTP result is "));
  1523.         Serial.println(ntpTime);
  1524.       #endif
  1525.       lastSync = millis();
  1526.     } else {
  1527.       #ifdef DEBUG
  1528.         Serial.println(F("syncHelper(): Resyncing to NTP..."));
  1529.       #endif
  1530.       ntpTime = getTimeNTP();
  1531.       #ifdef DEBUG
  1532.         Serial.print(F("syncHelper(): NTP result is "));
  1533.         Serial.println(ntpTime);
  1534.       #endif
  1535.       lastSync = millis();
  1536.     }
  1537.     #ifdef USERTC
  1538.       ntpTimeConverted = { year(ntpTime), month(ntpTime), day(ntpTime),
  1539.                            hour(ntpTime), minute(ntpTime), second(ntpTime) };
  1540.       RtcDateTime rtcTime = Rtc.GetDateTime();                                                 // get current time from the rtc....
  1541.       if ( ntpTime > 100 ) {
  1542.         Rtc.SetDateTime(ntpTimeConverted);
  1543.       }
  1544.     #else
  1545.       time_t sysTime = now();                                                                  // ...or from system
  1546.       #ifdef DEBUG
  1547.         Serial.println(F("syncHelper(): No RTC configured, using system time"));
  1548.         Serial.print(F("syncHelper(): sysTime was "));
  1549.         Serial.println(now());
  1550.       #endif
  1551.       if ( ntpTime > 100 ) {
  1552.         setTime(ntpTime);
  1553.       }
  1554.     #endif
  1555.     #ifdef DEBUG
  1556.       Serial.println(F("syncHelper() done"));
  1557.     #endif
  1558.   }
  1559.   
  1560.   time_t getTimeNTP() {
  1561.     unsigned long startTime = millis();
  1562.     time_t timeNTP;
  1563.     if ( WiFi.status() != WL_CONNECTED ) {
  1564.       #ifdef DEBUG
  1565.         Serial.print(F("getTimeNTP(): Not connected, WiFi.status is "));
  1566.         Serial.println(WiFi.status());
  1567.       #endif
  1568.     }                                                                                         // Sometimes the connection doesn't work right away although status is WL_CONNECTED...
  1569.     while ( millis() - startTime < 2000 ) {                                                   // ...so we'll wait a moment before causing network traffic
  1570.       #ifdef NODEMCU
  1571.         yield();
  1572.       #endif
  1573.     }
  1574.     if ( timeClient.update() == true ) {
  1575.       timeNTP = timeClient.getEpochTime();
  1576.     } else {
  1577.       timeNTP = 1;
  1578.       #ifdef DEBUG
  1579.         Serial.print(F("getTimeNTP(): No valid ntp response: ")); Serial.println(timeNTP);
  1580.       #endif
  1581.     }
  1582.     #ifdef DEBUG
  1583.       Serial.println(F("getTimeNTP() done"));
  1584.     #endif
  1585.     return timeNTP;
  1586.   }
  1587. #endif
  1588. // ---
  1589. // functions below will only be included if DEBUG is defined on top of the sketch
  1590. #ifdef DEBUG
  1591.   void printTime() {
  1592.     /* outputs current system and RTC time to the serial monitor, adds autoDST if defined */
  1593.     time_t tmp = now();
  1594.     #ifdef USERTC
  1595.       RtcDateTime tmp2 = Rtc.GetDateTime();
  1596.       setTime(tmp2.Hour(), tmp2.Minute(), tmp2.Second(),
  1597.               tmp2.Day(), tmp2.Month(), tmp2.Year() );
  1598.       tmp = now();
  1599.     #endif
  1600.     Serial.println(F("-----------------------------------"));
  1601.     Serial.print(F("System time is : "));
  1602.     if ( hour(tmp) < 10 ) Serial.print(F("0"));
  1603.     Serial.print(hour(tmp)); Serial.print(F(":"));
  1604.     if ( minute(tmp) < 10 ) Serial.print(F("0"));
  1605.     Serial.print(minute(tmp)); Serial.print(F(":"));
  1606.     if ( second(tmp) < 10 ) Serial.print(F("0"));
  1607.     Serial.println(second(tmp));
  1608.     Serial.print(F("System date is : "));
  1609.     Serial.print(year(tmp)); Serial.print("-");
  1610.     Serial.print(month(tmp)); Serial.print("-");
  1611.     Serial.print(day(tmp)); Serial.println(F(" (Y/M/D)"));
  1612.     #ifdef USERTC
  1613.       Serial.print(F("RTC time is    : "));
  1614.       if ( tmp2.Hour() < 10 ) Serial.print(F("0"));
  1615.       Serial.print(tmp2.Hour()); Serial.print(F(":"));
  1616.       if ( tmp2.Minute() < 10 ) Serial.print(F("0"));
  1617.       Serial.print(tmp2.Minute()); Serial.print(F(":"));
  1618.       if ( tmp2.Second() < 10 ) Serial.print(F("0"));
  1619.       Serial.println(tmp2.Second());
  1620.       Serial.print(F("RTC date is    : "));
  1621.       Serial.print( tmp2.Year()); Serial.print("-");
  1622.       Serial.print( tmp2.Month()); Serial.print("-");
  1623.       Serial.print( tmp2.Day()); Serial.println(F(" (Y/M/D)"));
  1624.     #endif
  1625.     #ifdef AUTODST
  1626.       tmp = myTimeZone.toLocal(tmp);
  1627.       Serial.print(F("autoDST time is: "));
  1628.       if ( hour(tmp) < 10 ) Serial.print(F("0"));
  1629.       Serial.print(hour(tmp)); Serial.print(F(":"));
  1630.       if ( minute(tmp) < 10 ) Serial.print(F("0"));
  1631.       Serial.print(minute(tmp)); Serial.print(F(":"));
  1632.       if ( second(tmp) < 10 ) Serial.print(F("0"));
  1633.       Serial.println(second(tmp));
  1634.       Serial.print(F("autoDST date is: "));
  1635.       Serial.print(year(tmp)); Serial.print("-");
  1636.       Serial.print(month(tmp)); Serial.print("-");
  1637.       Serial.print(day(tmp)); Serial.println(F(" (Y/M/D)"));
  1638.     #endif
  1639.     Serial.println(F("-----------------------------------"));
  1640.   }
  1641.   
  1642.   uint8_t dbgInput() {
  1643.     /* this catches input from the serial console and hands it over to inputButtons() if DEBUG is defined
  1644.        Serial input "7" matches buttonA, "8" matches buttonB, "9" matches buttonA + buttonB */
  1645.     if ( Serial.available() > 0 ) {
  1646.       uint8_t incomingByte = 0;
  1647.       incomingByte = Serial.read();
  1648.       if ( incomingByte == 52 ) {          // 4 - long press buttonA
  1649.         btnRepeatCounter = 10;
  1650.         return 1;
  1651.       }
  1652.       if ( incomingByte == 53 ) {          // 5 - long press buttonB
  1653.         btnRepeatCounter = 10;
  1654.         return 2;
  1655.       }
  1656.       if ( incomingByte == 54 ) {          // 6 - long press buttonA + buttonB
  1657.         btnRepeatCounter = 10;
  1658.         return 3;
  1659.       }
  1660.       if ( incomingByte == 55 ) return 1;  // 7 - buttonA
  1661.       if ( incomingByte == 56 ) return 2;  // 8 - buttonB
  1662.       if ( incomingByte == 57 ) return 3;  // 9 - buttonA + buttonB
  1663.     }
  1664.     return 0;
  1665.   }
  1666. #endif
  1667. // ---
  1668. #ifdef USEWIFI
  1669.   void connectWPS() {                                                                            // join network using wps. Will try for 3 times before exiting...
  1670.     #ifdef DEBUG
  1671.       Serial.println(F("connectWPS(): Initializing WPS setup..."));
  1672.     #endif
  1673.     uint8_t counter = 1;
  1674.     static unsigned long startTimer = millis();
  1675.     #ifdef LEDSTUFF
  1676.       FastLED.clear();
  1677.       showChar(10, digitPositions[0], digitYPosition + 6);
  1678.       showChar(11, digitPositions[1], digitYPosition + 6);
  1679.       showChar(12, digitPositions[2], digitYPosition);
  1680.       showChar(counter, digitPositions[3], digitYPosition);
  1681.       colorizeOutput(colorMode);
  1682.       FastLED.show();
  1683.     #endif
  1684.     while ( counter < 4 ) {
  1685.       #ifdef LEDSTUFF
  1686.         if ( millis() % 50 == 0 ) {
  1687.           FastLED.clear();
  1688.           showChar(10, digitPositions[0], digitYPosition + 6);
  1689.           showChar(11, digitPositions[1], digitYPosition + 6);
  1690.           showChar(12, digitPositions[2], digitYPosition);
  1691.           showChar(counter, digitPositions[3], digitYPosition);
  1692.           colorizeOutput(colorMode);
  1693.           FastLED.show();
  1694.         }
  1695.       #endif
  1696.       if ( millis() - startTimer > 300 ) {
  1697.         #ifdef DEBUG
  1698.           Serial.print(F("connectWPS(): Waiting for WiFi/WPS, try "));
  1699.           Serial.println(counter);
  1700.         #endif
  1701.         WiFi.beginWPSConfig();
  1702.         if ( WiFi.SSID().length() <= 0 ) counter++; else counter = 4;
  1703.         startTimer = millis();
  1704.       }
  1705.       #ifdef NODEMCU
  1706.         yield();
  1707.       #endif
  1708.     }
  1709.     FastLED.clear();
  1710.     startTimer = millis();
  1711.     if ( WiFi.SSID().length() > 0 ) {
  1712.       #ifdef LEDSTUFF
  1713.         FastLED.clear();
  1714.         showChar(5, digitPositions[0], digitYPosition + 6);
  1715.         showChar(5, digitPositions[1], digitYPosition + 6);
  1716.         showChar(1, digitPositions[2], digitYPosition);
  1717.         showChar(13, digitPositions[3], digitYPosition);
  1718.         colorizeOutput(colorMode);
  1719.         FastLED.show();
  1720.       #endif
  1721.       #ifdef DEBUG
  1722.         Serial.print(F("connectWPS(): Connected to SSID: ")); Serial.println(WiFi.SSID());
  1723.       #endif
  1724.       while ( millis() - startTimer < 2000 ) {
  1725.         #ifdef NODEMCU
  1726.           yield();
  1727.         #endif
  1728.       }
  1729.       #ifdef USENTP
  1730.         clockStatus = 1;
  1731.         syncHelper();
  1732.         clockStatus = 0;
  1733.       #endif USENTP
  1734.     } else {
  1735.       #ifdef DEBUG
  1736.         Serial.println(F("connectWPS(): Failed, no WPS connection established"));
  1737.       #endif      
  1738.     }
  1739.     #ifdef DEBUG
  1740.       Serial.println(F("connectWPS() done"));
  1741.     #endif
  1742.   }
  1743. #endif
  1744. /* Wooohaa... this one took a bit longer than expected... ^^ /daniel cikic - 07/2021 */
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 3 天前

【Arduino 动手做】 LGCv2:使用单个 LED 灯条的 7x11 LED 矩...

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail