diff --git a/README.md b/README.md index 407eecc..0a2114e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ English description further below. Dies ist ein Projekt für eine mehrsprachige Wortuhr auf Grundlage eines ESP8266-Mikrocontrollers und einer programmierbaren LED-Leiste (WS2812 oder SK6812). Eine Wortuhr ist ein wunderschönes DIY-Projekt für Anfänger, das Technologie und Design kombiniert, um eine funktionale und ästhetisch ansprechende Uhr zu schaffen. Egal, ob Sie ein Anfänger oder ein erfahrener Bastler sind, dieses Projekt ist eine großartige Möglichkeit, Ihre Fähigkeiten unter Beweis zu stellen und etwas wirklich Besonderes zu schaffen. Die Software hat viele Funktionen: -- Mehrsprachig (🇬🇧, 🇩🇪, 🇪🇸, 🇮🇹, 🇳🇱, 🇭🇺) +- Mehrsprachig (🇬🇧, 🇩🇪, 🇪🇸, 🇮🇹, 🇳🇱, 🇭🇺, 🇷🇴, 🇨🇭, 🇷🇺, 🇸🇪, 🇫🇷) - Unterstützung für mehrere Layouts und LED-Abstände - Farbwechsel der Displayfarbe möglich (RGB oder RGBW) - Digitale Uhranzeige @@ -105,7 +105,7 @@ Die einzige Bedingung ist, dass der Copyright-Hinweis des Originalprogramms nich This is a project for a multilingual word clock based on an ESP8266 microcontroller and a programmable LED strip (WS2812 or SK6812). A word clock is a beautiful DIY project for beginners that combines technology and design to create a functional and aesthetically pleasing clock. Whether you're a beginner or an experienced hobbyist, this project is a great way to show off your skills and create something truly special. The software has many features: -- Multilingual (🇬🇧, 🇩🇪, 🇪🇸, 🇮🇹, 🇳🇱, 🇭🇺) +- Multilingual (🇬🇧, 🇩🇪, 🇪🇸, 🇮🇹, 🇳🇱, 🇭🇺, 🇷🇴, 🇨🇭, 🇷🇺, 🇸🇪, 🇫🇷) - Support for multiple layouts and LED spacing - Colour change of the display colour possible (RGB or RGBW) - Digital clock display diff --git a/eslintrc.json b/eslintrc.json old mode 100644 new mode 100755 index 503ca9a..869b7e6 --- a/eslintrc.json +++ b/eslintrc.json @@ -268,6 +268,7 @@ "TRANSLATION_IT": "readonly", "TRANSLATION_ES": "readonly", "TRANSLATION_HU": "readonly", + "TRANSLATION_RU": "readonly", "i18next": "readonly", "isLocalEnvironment": "readonly" } diff --git a/include/EEPROMAnything.h b/include/EEPROMAnything.h index f35dc94..68e5732 100644 --- a/include/EEPROMAnything.h +++ b/include/EEPROMAnything.h @@ -45,6 +45,9 @@ void read() { Serial.printf("BgCol.H : %f\n", G.color[Background].H); Serial.printf("BgCol.S : %f\n", G.color[Background].S); Serial.printf("BgCol.V : %f\n", G.color[Background].B); + Serial.printf("FrCol.H : %f\n", G.color[Frame].H); + Serial.printf("FrCol.S : %f\n", G.color[Frame].S); + Serial.printf("FrCol.V : %f\n", G.color[Frame].B); Serial.printf("Zeitserver: %s\n", G.timeserver); Serial.printf("Lauftext : %s\n", G.scrollingText); Serial.printf("H6 : %u\n", G.h6); diff --git a/include/Transitiontypes/Transition.hpp b/include/Transitiontypes/Transition.hpp index 3362d7e..910d308 100644 --- a/include/Transitiontypes/Transition.hpp +++ b/include/Transitiontypes/Transition.hpp @@ -953,15 +953,15 @@ bool Transition::isOverwrittenByTransition(WordclockChanges changesInWordMatrix, //------------------------------------------------------------------------------ void Transition::loop(struct tm &tm) { - if (matrixChanged) { - matrixChanged = false; - saveMatrix(); - copyMatrix(work, act); - } - if (G.prog == COMMAND_IDLE || G.prog == COMMAND_MODE_WORD_CLOCK) { if (!isSilvester(transitionType, tm, hasMinuteChanged())) { - transitionType = getTransitionType(hasMinuteChanged()); + transitionType = getTransitionType(matrixChanged); + } + + if (matrixChanged) { + matrixChanged = false; + saveMatrix(); + copyMatrix(work, act); } if (transitionType == NO_TRANSITION) { diff --git a/include/Uhr.h b/include/Uhr.h index f0cdb01..dcb77a0 100644 --- a/include/Uhr.h +++ b/include/Uhr.h @@ -164,6 +164,7 @@ bool externalRTC = false; enum ColorPosition { Foreground = 0, Background = 1, + Frame = 2, }; enum LedColorVariants { @@ -270,6 +271,8 @@ enum ClockType { Ch10x11 = 18, Ro10x11 = 19, Fr10x11 = 21, + Se10x11 = 22, + Ru10x11 = 23, }; enum Icons { @@ -286,4 +289,8 @@ enum Icons { SMILEY = 10, NOTE = 11, SNOW = 12, + MAIL = 13, + BELL = 14, + STOP = 15, + STBY = 16, }; diff --git a/include/Uhrtypes/HU10x10.hpp b/include/Uhrtypes/HU10x10.hpp index 3273185..2335d37 100644 --- a/include/Uhrtypes/HU10x10.hpp +++ b/include/Uhrtypes/HU10x10.hpp @@ -5,8 +5,8 @@ /* * Layout Front * COL - * X 9 8 7 6 5 4 3 2 1 0 - * ROW + - - - - - - - - - - - + * 9 8 7 6 5 4 3 2 1 0 + * ROW + - - - - - - - - - - * 0 | Ö T B T Í Z J A S U * 1 | P E R C C E L E W O * 2 | M Ú L T A M Ú L V A diff --git a/include/Uhrtypes/RU10x11.hpp b/include/Uhrtypes/RU10x11.hpp new file mode 100755 index 0000000..868486f --- /dev/null +++ b/include/Uhrtypes/RU10x11.hpp @@ -0,0 +1,227 @@ +#pragma once + +#include "Uhrtype.hpp" + +/* + * Layout Front by Ragman + * COL + * X 9 8 7 6 5 4 3 2 1 0 + * ROW + - - - - - - - - - - - + * 0 | О Д И Н П Я Т Ь Д В А + * 1 | Д Е Ш Е С Т Ь В Я Т Ь + * 2 | В О Ч Е C E M Ь Т Р И + * 3 | Т Ы Д В Е Р Е С Я Т Ь + * 4 | Н А Д Ц А Т Ь Ч А С А + * 5 | Ч А С О В Д С О Р О К + * 6 | Т Р И Д В А Д П Я Т Ь + * 7 | П Я Т Н А Д Е Ц А Т Ь + * 8 | A M Д Е С Я Т С Я Т Ь + * 9 | П Я Т Ь Я Р М И Н У Т + */ + +class Ru10x11_t : public iUhrType { +public: + virtual LanguageAbbreviation usedLang() override { + return LanguageAbbreviation::RU; + }; + + //------------------------------------------------------------------------------ + + virtual const bool hasZwanzig() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasTwentyfive() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasThirtyfive() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasForty() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasDreiviertel() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasFifty() override { return true; } + + //------------------------------------------------------------------------------ + + virtual const bool hasFiftyFive() override { return true; } + + //------------------------------------------------------------------------------ + + void show(FrontWord word) override { + switch (word) { + + case FrontWord::es_ist: + // Ч А С О В (5 ... 12) + setFrontMatrixWord(5, 6, 10); + break; + + case FrontWord::es_ist___plural___: + // Ч А С A (2 ...4) + setFrontMatrixWord(4, 0, 3); + break; + + case FrontWord::es_ist__singular__: + // Ч А С (1) + setFrontMatrixWord(4, 1, 3); + break; + + case FrontWord::hour_1: + // О Д И Н + setFrontMatrixWord(0, 7, 10); + break; + + case FrontWord::hour_2: + // Д В А + setFrontMatrixWord(0, 0, 2); + break; + + case FrontWord::hour_3: + // Т Р И + setFrontMatrixWord(2, 0, 2); + break; + + case FrontWord::hour_4: + // Ч Е Т Ы Р Е + setFrontMatrixWord(2, 7, 8); + setFrontMatrixWord(3, 9, 10); + setFrontMatrixWord(3, 4, 5); + break; + + case FrontWord::hour_5: + // П Я Т Ь + setFrontMatrixWord(0, 3, 6); + break; + + case FrontWord::hour_6: + // Ш Е С Т Ь + setFrontMatrixWord(1, 4, 8); + break; + + case FrontWord::hour_7: + // C E M Ь + setFrontMatrixWord(2, 3, 6); + break; + + case FrontWord::hour_8: + // B O C E M Ь + setFrontMatrixWord(2, 9, 10); + setFrontMatrixWord(2, 3, 6); + break; + + case FrontWord::hour_9: + // Д Е В Я Т Ь + setFrontMatrixWord(1, 9, 10); + setFrontMatrixWord(1, 0, 3); + break; + + case FrontWord::hour_10: + // Д Е С Я Т Ь + setFrontMatrixWord(1, 9, 10); + setFrontMatrixWord(3, 0, 3); + break; + + case FrontWord::hour_11: + // О Д И Н Н А Д Ц А Т Ь + setFrontMatrixWord(0, 7, 10); + setFrontMatrixWord(4, 4, 10); + break; + + case FrontWord::hour_12: + // Д В Е Н А Д Ц А Т Ь + setFrontMatrixWord(3, 6, 8); + setFrontMatrixWord(4, 4, 10); + break; + + case FrontWord::min_5: + // П Я Т Ь М И Н У Т + setFrontMatrixWord(9, 7, 10); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_10: + // Д Е С Я Т Ь М И Н У Т + setFrontMatrixWord(8, 7, 8); + setFrontMatrixWord(8, 0, 3); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::viertel: + // П Я Т Н А Д Ц А Т Ь М И Н У Т + setFrontMatrixWord(7, 5, 10); + setFrontMatrixWord(7, 0, 3); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_20: + // Д В А Д Ц А Т Ь М И Н У Т + setFrontMatrixWord(6, 4, 7); + setFrontMatrixWord(7, 0, 3); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_25: + // Д В А Д Ц А Т Ь П Я Т Ь М И Н У Т + setFrontMatrixWord(6, 4, 7); + setFrontMatrixWord(7, 0, 3); + setFrontMatrixWord(9, 7, 10); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::halb: + // Т Р И Д Ц А Т Ь М И Н У Т + setFrontMatrixWord(6, 7, 10); + setFrontMatrixWord(7, 0, 3); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_35: + // Т Р И Д Ц А Т Ь П Я Т Ь М И Н У Т + setFrontMatrixWord(6, 7, 10); + setFrontMatrixWord(7, 0, 3); + setFrontMatrixWord(9, 7, 10); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_40: + // C O P O K М И Н У Т + setFrontMatrixWord(5, 0, 4); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::dreiviertel: + // C O P O K П Я Т Ь М И Н У Т + setFrontMatrixWord(5, 0, 4); + setFrontMatrixWord(9, 7, 10); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_50: + // П Я Т Ь Д Е С Я Т М И Н У Т + setFrontMatrixWord(6, 0, 3); + setFrontMatrixWord(8, 4, 8); + setFrontMatrixWord(9, 0, 4); + break; + + case FrontWord::min_55: + // П Я Т Ь Д Е С Я Т П Я Т Ь М И Н У Т + setFrontMatrixWord(6, 0, 3); + setFrontMatrixWord(8, 4, 8); + setFrontMatrixWord(9, 7, 10); + setFrontMatrixWord(9, 0, 4); + break; + + default: + break; + }; + }; +}; + +Ru10x11_t _ru10x11; \ No newline at end of file diff --git a/include/Uhrtypes/SE10x11.hpp b/include/Uhrtypes/SE10x11.hpp new file mode 100644 index 0000000..c292747 --- /dev/null +++ b/include/Uhrtypes/SE10x11.hpp @@ -0,0 +1,146 @@ +#pragma once + +#include "Uhrtype.hpp" + +/* + * Layout Front + * COL + * X 9 8 7 6 5 4 3 2 1 0 + * ROW + - - - - - - - - - - - + * 0 | K L O C K A N T Ä R K + * 1 | F E M Y I S T I O N I + * 2 | K V A R T Q I E N Z O + * 3 | T J U G O L I V I P M + * 4 | Ö V E R K A M H A L V + * 5 | E T T U S V L X T V Å + * 6 | T R E M Y K Y F Y R A + * 7 | F E M S F L O R S E X + * 8 | S J U Å T T A I N I O + * 9 | T I O E L V A T O L V + */ + +class Se10x11_t : public iUhrType { +public: + virtual LanguageAbbreviation usedLang() override { + return LanguageAbbreviation::SE; + }; + + //------------------------------------------------------------------------------ + + virtual const bool hasZwanzig() override { return true; } + + //------------------------------------------------------------------------------ + + void show(FrontWord word) override { + switch (word) { + + case FrontWord::es_ist: + // Klockan är + setFrontMatrixWord(0, 4, 10); + setFrontMatrixWord(0, 1, 2); + break; + + case FrontWord::hour_1: + // Ett + setFrontMatrixWord(5, 8, 10); + break; + + case FrontWord::hour_2: + // Två + setFrontMatrixWord(5, 0, 2); + break; + + case FrontWord::hour_3: + // Tre + setFrontMatrixWord(6, 8, 10); + break; + + case FrontWord::hour_4: + // Fyra + setFrontMatrixWord(6, 0, 3); + break; + + case FrontWord::hour_5: + // Fem + setFrontMatrixWord(7, 8, 10); + break; + + case FrontWord::hour_6: + // Sex + setFrontMatrixWord(7, 0, 2); + break; + + case FrontWord::hour_7: + // Sju + setFrontMatrixWord(8, 8, 10); + break; + + case FrontWord::hour_8: + // Åtta + setFrontMatrixWord(8, 4, 7); + break; + + case FrontWord::hour_9: + // Nio + setFrontMatrixWord(8, 0, 2); + break; + + case FrontWord::hour_10: + // Tio + setFrontMatrixWord(9, 8, 10); + break; + + case FrontWord::hour_11: + // Elva + setFrontMatrixWord(9, 4, 7); + break; + + case FrontWord::hour_12: + // Tolv + setFrontMatrixWord(9, 0, 3); + break; + + case FrontWord::min_5: + // Fem + setFrontMatrixWord(1, 8, 10); + break; + + case FrontWord::min_10: + // Tio + setFrontMatrixWord(1, 2, 4); + break; + + case FrontWord::viertel: + // Kvart + setFrontMatrixWord(2, 6, 10); + break; + + case FrontWord::min_20: + // Tjugo + setFrontMatrixWord(3, 6, 10); + break; + + case FrontWord::halb: + // Halv + setFrontMatrixWord(4, 0, 3); + break; + + case FrontWord::nach: + case FrontWord::v_nach: + // Över + setFrontMatrixWord(4, 7, 10); + break; + + case FrontWord::vor: + case FrontWord::v_vor: + // I + setFrontMatrixWord(3, 2, 2); + break; + + default: + break; + }; + }; +}; + +Se10x11_t _se10x11; \ No newline at end of file diff --git a/include/Uhrtypes/Uhrtype.hpp b/include/Uhrtypes/Uhrtype.hpp index f97d7b0..e0fa96f 100644 --- a/include/Uhrtypes/Uhrtype.hpp +++ b/include/Uhrtypes/Uhrtype.hpp @@ -1,6 +1,6 @@ #pragma once -enum class LanguageAbbreviation { DE, EN, ES, IT, NL, HU, RO, FR }; +enum class LanguageAbbreviation { DE, EN, ES, IT, NL, HU, RO, FR, SE, RU }; enum class FrontWord { error, @@ -69,6 +69,7 @@ enum class FrontWord { m_num4, es_ist, + es_ist__singular__, es_ist___plural___, nach, vor, @@ -181,6 +182,12 @@ class iUhrType { virtual const bool hasThirtyfive() { return false; } + virtual const bool hasForty() { return false; } + + virtual const bool hasFifty() { return false; } + + virtual const bool hasFiftyFive() { return false; } + virtual const bool hasMitternacht() { return false; } virtual const bool has24HourLayout() { return false; } diff --git a/include/clockWork.h b/include/clockWork.h index 356e620..4505dca 100644 --- a/include/clockWork.h +++ b/include/clockWork.h @@ -40,6 +40,8 @@ class ClockWork { void showMinute(uint8_t min); void resetMinVariantIfNotAvailable(); FrontWord getFrontWordForNum(uint8_t min); + bool hasTwentyAndCheckForUsage(); + bool hasDreiviertelAndCheckForUsage(); void setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour); //------------------------------------------------------------------------------ @@ -55,6 +57,7 @@ class ClockWork { WordclockChanges changesInClockface(); void calcClockface(); void setClock(); + void DetermineWhichItIsToShow(uint8_t offsetHour); void clearClockByProgInit(); public: diff --git a/include/clockWork.hpp b/include/clockWork.hpp index 10d61ca..4b5503b 100644 --- a/include/clockWork.hpp +++ b/include/clockWork.hpp @@ -123,6 +123,10 @@ iUhrType *ClockWork::getPointer(uint8_t type) { return &_de10x11schwaebisch; case Fr10x11: return &_fr10x11; + case Se10x11: + return &_se10x11; + case Ru10x11: + return &_ru10x11; default: return nullptr; } @@ -494,6 +498,25 @@ FrontWord ClockWork::getFrontWordForNum(uint8_t min) { //------------------------------------------------------------------------------ +bool ClockWork::hasTwentyAndCheckForUsage() { + return usedUhrType->hasZwanzig() || G.languageVariant[ItIs40]; +} + +//------------------------------------------------------------------------------ + +bool ClockWork::hasDreiviertelAndCheckForUsage() { + if (usedUhrType->hasDreiviertel()) { + if (usedUhrType->usedLang() != LanguageAbbreviation::DE) { + return true; + } else if (G.languageVariant[ItIs45]) { + return true; + } + } + return false; +} + +//------------------------------------------------------------------------------ + void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { if (usedUhrType->has24HourLayout()) { usedUhrType->show(FrontWord::uhr); @@ -544,9 +567,6 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { case 13: case 14: usedUhrType->show(getFrontWordForNum(min)); - if (usedUhrType->has24HourLayout()) { - usedUhrType->show(FrontWord::minuten); - } usedUhrType->show(FrontWord::nach); break; case 15: // quarter past @@ -569,15 +589,15 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { usedUhrType->show(getFrontWordForNum(min)); usedUhrType->show(FrontWord::nach); break; - case 20: // 20 past - if (!usedUhrType->hasZwanzig() || G.languageVariant[ItIs20]) { + case 20: + if (hasTwentyAndCheckForUsage()) { + usedUhrType->show(FrontWord::min_20); + usedUhrType->show(FrontWord::nach); + } else { usedUhrType->show(FrontWord::min_10); usedUhrType->show(FrontWord::vor); usedUhrType->show(FrontWord::halb); offsetHour = 1; - } else { - usedUhrType->show(FrontWord::min_20); - usedUhrType->show(FrontWord::nach); } break; case 21: @@ -616,7 +636,7 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { usedUhrType->show(FrontWord::halb); usedUhrType->show(FrontWord::nach); } else { - if (G.UhrtypeDef == Fr10x11) { + if (G.UhrtypeDef == Fr10x11 || G.UhrtypeDef == Ru10x11) { usedUhrType->show(FrontWord::halb); } else { usedUhrType->show(FrontWord::halb); @@ -641,13 +661,12 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { } else if (usedUhrType->hasTwentyfive()) { usedUhrType->show(FrontWord::min_25); usedUhrType->show(FrontWord::vor); - offsetHour = 1; } else { usedUhrType->show(FrontWord::min_5); usedUhrType->show(FrontWord::nach); usedUhrType->show(FrontWord::halb); - offsetHour = 1; } + offsetHour = 1; break; case 36: case 37: @@ -659,13 +678,15 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { offsetHour = 1; break; case 40: - if (!usedUhrType->hasZwanzig() || G.languageVariant[ItIs40]) { + if (usedUhrType->hasForty()) { + usedUhrType->show(FrontWord::min_40); + } else if (hasTwentyAndCheckForUsage()) { + usedUhrType->show(FrontWord::min_20); + usedUhrType->show(FrontWord::vor); + } else { usedUhrType->show(FrontWord::min_10); usedUhrType->show(FrontWord::nach); usedUhrType->show(FrontWord::halb); - } else { - usedUhrType->show(FrontWord::min_20); - usedUhrType->show(FrontWord::vor); } offsetHour = 1; break; @@ -678,7 +699,7 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { offsetHour = 1; break; case 45: // quarter to - if (usedUhrType->hasDreiviertel() && G.languageVariant[ItIs45]) { + if (hasDreiviertelAndCheckForUsage()) { usedUhrType->show(FrontWord::dreiviertel); } else { // A Quarter to @@ -695,13 +716,25 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { case 48: case 49: case 50: + if (usedUhrType->hasFifty()) { + usedUhrType->show(FrontWord::min_50); + } else { + usedUhrType->show(getFrontWordForNum(60 - min)); + usedUhrType->show(FrontWord::vor); + } + offsetHour = 1; + break; case 51: case 52: case 53: case 54: case 55: - usedUhrType->show(getFrontWordForNum(60 - min)); - usedUhrType->show(FrontWord::vor); + if (usedUhrType->hasFiftyFive()) { + usedUhrType->show(FrontWord::min_55); + } else { + usedUhrType->show(getFrontWordForNum(60 - min)); + usedUhrType->show(FrontWord::vor); + } offsetHour = 1; break; case 56: @@ -721,6 +754,10 @@ void ClockWork::setMinute(uint8_t min, uint8_t &offsetHour, bool &fullHour) { default: break; } + + if (G.UhrtypeDef == Ru10x11) { + offsetHour = 0; + } } } @@ -860,11 +897,32 @@ void ClockWork::setClock() { setHour(_hour + offsetHour, fullHour); if (!G.languageVariant[NotShowItIs]) { - if (G.UhrtypeDef == Es10x11 && (_hour + offsetHour) == 1) { + DetermineWhichItIsToShow(_hour + offsetHour); + } +} + +//------------------------------------------------------------------------------ + +void ClockWork::DetermineWhichItIsToShow(uint8_t hour) { + if (G.UhrtypeDef == Ru10x11) { + hour %= 12; + switch (hour) { + case 1: + usedUhrType->show(FrontWord::es_ist__singular__); + break; + case 2: + case 3: + case 4: usedUhrType->show(FrontWord::es_ist___plural___); - } else { + break; + default: usedUhrType->show(FrontWord::es_ist); + break; } + } else if (G.UhrtypeDef == Es10x11 && hour == 1) { + usedUhrType->show(FrontWord::es_ist___plural___); + } else { + usedUhrType->show(FrontWord::es_ist); } } @@ -1159,10 +1217,6 @@ void ClockWork::loop(struct tm &tm) { } delay(100); - if (G.mqtt.state && !mqtt.getConnected()) { - mqtt.reInit(); - } - eeprom::write(); break; } @@ -1299,8 +1353,12 @@ void ClockWork::loop(struct tm &tm) { for (uint8_t row = 0; row < usedUhrType->rowsWordMatrix(); row++) { frontMatrix[row] = num32BitWithOnesAccordingToColumns(); } + } + + if (parametersChanged) { led.setbyFrontMatrix(Foreground, false); led.show(); + parametersChanged = false; } break; } @@ -1320,6 +1378,11 @@ void ClockWork::loop(struct tm &tm) { calcClockface(); switch (changesInClockface()) { + case WordclockChanges::Minute: + lastMinuteArray = minuteArray; + memcpy(&lastFrontMatrix, &frontMatrix, sizeof lastFrontMatrix); + led.set(WordclockChanges::Minute); + break; case WordclockChanges::Words: lastMinuteArray = minuteArray; memcpy(&lastFrontMatrix, &frontMatrix, sizeof lastFrontMatrix); diff --git a/include/config.h b/include/config.h index 4b61d61..fb18b53 100644 --- a/include/config.h +++ b/include/config.h @@ -124,6 +124,22 @@ // 10 rows, 11 LED's per row + 4 LED's for minutes, with modified // layout for the French language +/**********************/ +/* Russian */ +/**********************/ +// +// #define DEFAULT_LAYOUT Ru10x11 +// 10 rows, 11 LED's per row + 4 LED's for minutes, with modified +// layout for the Russian language + +/**********************/ +/* Swedish */ +/**********************/ +// +// #define DEFAULT_LAYOUT Se10x11 +// 10 rows, 11 LED's per row + 4 LED's for minutes, with modified +// layout for the Swedish language + //-------------------------------------------------------------------------- // Define LED Type //-------------------------------------------------------------------------- diff --git a/include/icons.h b/include/icons.h index 8729160..8584e21 100644 --- a/include/icons.h +++ b/include/icons.h @@ -146,4 +146,48 @@ const uint16_t grafik_11x10[][11] PROGMEM = { 0b00110101100, // 7 87: . . 0 0 . 0 . 0 0 . . : 77 0b00110101100, // 8 88: . . 0 0 . 0 . 0 0 . . : 98 0b00000100000}, // 9 109: . . . . . 0 . . . . . : 99 + + {0b00000000000, // 0 MAIL 0: . . . . . . . . . . . : 10 + 0b11111111111, // 1 21: 0 0 0 0 0 0 0 0 0 0 0 : 11 + 0b11000000011, // 2 22: 0 0 . . . . . . . 0 0 : 32 + 0b10100000101, // 3 43: 0 . 0 . . . . . 0 . 0 : 33 + 0b10011111001, // 4 44: 0 . . 0 0 0 0 0 . . 0 : 54 + 0b10000100001, // 5 65: 0 . . . . 0 . . . . 0 : 55 + 0b10000000001, // 6 66: 0 . . . . . . . . . 0 : 76 + 0b10000000001, // 7 87: 0 . . . . . . . . . 0 : 77 + 0b11111111111, // 8 88: 0 0 0 0 0 0 0 0 0 0 0 : 98 + 0b00000000000}, // 9 109: . . . . . . . . . . . : 99 + + {0b00000100000, // 0 BELL 0: . . . . . 0 . . . . . : 10 + 0b00001110000, // 1 21: . . . . 0 0 0 . . . . : 11 + 0b00010001000, // 2 22: . . . 0 . . . 0 . . . : 32 + 0b00100000100, // 3 43: . . 0 . . . . . 0 . . : 33 + 0b00100000100, // 4 44: . . 0 . . . . . 0 . . : 54 + 0b01100000110, // 5 65: . 0 0 . . . . . 0 0 . : 55 + 0b01100000011, // 6 66: 0 0 . . . . . . . 0 0 : 76 + 0b01111111110, // 7 87: . 0 0 0 0 0 0 0 0 0 . : 77 + 0b00001110000, // 8 88: . . . . 0 0 0 . . . . : 98 + 0b00000100000}, // 9 109: . . . . . 0 . . . . . : 99 + + {0b00001110000, // 0 STOP 0: . . . . 0 0 0 . . . . : 10 + 0b00111111100, // 1 21: . . 0 0 0 0 0 0 0 . . : 11 + 0b01110001110, // 2 22: . 0 0 0 . . . 0 0 0 . : 32 + 0b11001010011, // 3 43: 0 0 . . 0 . 0 . . 0 0 : 33 + 0b11000100011, // 4 44: 0 0 . . . 0 . . . 0 0 : 54 + 0b11001010011, // 5 65: 0 0 . . 0 . 0 . . 0 0 : 55 + 0b11010001011, // 6 66: 0 0 . 0 . . . 0 . 0 0 : 76 + 0b01100000110, // 7 87: . 0 0 . . . . . 0 0 . : 77 + 0b00111111100, // 8 88: . . 0 0 0 0 0 0 0 . . : 98 + 0b00001110000}, // 9 109: . . . . 0 0 0 . . . . : 99 + + {0b00001110000, // 0 STBY 0: . . . . 0 0 0 . . . . : 10 + 0b00001110000, // 1 21: . . . . 0 0 0 . . . . : 11 + 0b00111111100, // 2 22: . . 0 0 0 0 0 0 0 . . : 32 + 0b01101110110, // 3 43: . 0 0 . 0 0 0 . 0 0 . : 33 + 0b11001110011, // 4 44: 0 0 . . 0 0 0 . . 0 0 : 54 + 0b11001110011, // 5 65: 0 0 . . 0 0 0 . . 0 0 : 55 + 0b11000000011, // 6 66: 0 0 . . . . . . . 0 0 : 76 + 0b11000000011, // 7 87: 0 0 . . . . . . . 0 0 : 77 + 0b01100000110, // 8 88: . 0 0 . . . . . 0 0 . : 98 + 0b00111111100}, // 9 109: . . 0 0 0 0 0 0 0 . . : 99 }; diff --git a/include/led.h b/include/led.h index 5f4ae94..d284a5a 100644 --- a/include/led.h +++ b/include/led.h @@ -44,7 +44,7 @@ class Led { float setBrightnessAuto(float val); void getCurrentManualBrightnessSetting(uint8_t ¤tBrightness); void getColorbyPositionWithAppliedBrightness(HsbColor &color, - uint8_t position); + ColorPosition position); void shiftColumnToRight(); //------------------------------------------------------------------------------ @@ -52,10 +52,10 @@ class Led { //------------------------------------------------------------------------------ void setPixel(uint16_t ledIndex, HsbColor color); void setPixel(uint8_t row, uint8_t col, HsbColor color); - void setbyFrontMatrix(uint8_t ColorPosition = Foreground, + void setbyFrontMatrix(ColorPosition position = Foreground, bool applyMirrorAndReverse = true); - void setbyMinuteArray(uint8_t ColorPosition = Foreground); - void setbySecondArray(uint8_t ColorPosition = Foreground); + void setbyMinuteArray(ColorPosition position = Foreground); + void setbySecondArray(ColorPosition position = Foreground); void setIcon(uint8_t iconNum); void setSingle(uint8_t wait); void setPixelForChar(uint8_t col, uint8_t row, uint8_t offsetCol, diff --git a/include/led.hpp b/include/led.hpp index 9befa5c..164e49a 100644 --- a/include/led.hpp +++ b/include/led.hpp @@ -151,8 +151,8 @@ void Led::getCurrentManualBrightnessSetting(uint8_t ¤tBrightness) { //------------------------------------------------------------------------------ void Led::getColorbyPositionWithAppliedBrightness(HsbColor &color, - uint8_t colorPosition) { - color = G.color[colorPosition]; + ColorPosition position) { + color = G.color[position]; uint8_t manBrightnessSetting = 100; getCurrentManualBrightnessSetting(manBrightnessSetting); @@ -220,8 +220,8 @@ void Led::setPixel(uint8_t row, uint8_t col, HsbColor color) { //------------------------------------------------------------------------------ -void Led::setbyFrontMatrix(uint8_t colorPosition, bool applyMirrorAndReverse) { - +void Led::setbyFrontMatrix(ColorPosition colorPosition, + bool applyMirrorAndReverse) { if (applyMirrorAndReverse) { applyMirroringAndReverseIfDefined(); } @@ -246,7 +246,7 @@ void Led::setbyFrontMatrix(uint8_t colorPosition, bool applyMirrorAndReverse) { //------------------------------------------------------------------------------ -void Led::setbyMinuteArray(uint8_t colorPosition) { +void Led::setbyMinuteArray(ColorPosition colorPosition) { HsbColor displayedColor; getColorbyPositionWithAppliedBrightness(displayedColor, colorPosition); @@ -261,7 +261,7 @@ void Led::setbyMinuteArray(uint8_t colorPosition) { //------------------------------------------------------------------------------ -void Led::setbySecondArray(uint8_t colorPosition) { +void Led::setbySecondArray(ColorPosition colorPosition) { HsbColor displayedColor; getColorbyPositionWithAppliedBrightness(displayedColor, colorPosition); diff --git a/include/mqtt.h b/include/mqtt.h index 8cd22c3..de49ec3 100644 --- a/include/mqtt.h +++ b/include/mqtt.h @@ -4,7 +4,7 @@ class Mqtt { private: - void reconnect(); + void reInit(); static void callback(char *topic, byte *payload, unsigned int length); public: @@ -12,10 +12,9 @@ class Mqtt { ~Mqtt() = default; void init(); - void reInit(); void loop(); void sendState(); void sendDiscovery(); - bool getConnected(); + bool isConnected(); }; diff --git a/include/mqtt.hpp b/include/mqtt.hpp index d5ac0b2..65fe2e8 100644 --- a/include/mqtt.hpp +++ b/include/mqtt.hpp @@ -7,55 +7,191 @@ #define HOMEASSISTANT_DISCOVERY_TOPIC "homeassistant" +#define RETRY_INTERVALL_WITHIN_5_MINUTES 15000 // 15 Seconds +#define MAX_RETRIES_WITHIN_5_MINUTES 20 + +#define RETRY_INTERVALL 3600000 // 1 Hour + extern WiFiClient client; PubSubClient mqttClient(client); //------------------------------------------------------------------------------ +/* Description: + +This function checks if a character array representing an MQTT user is empty. An +MQTT user is considered empty if it contains only null characters ('\0') up to +the specified length. + +Input: + +None +Output: + +true if the MQTT user array is empty. +false if the MQTT user array is not empty. +*/ + +bool checkIfMqttUserIsEmpty() { + for (uint8_t i = 0; i < PAYLOAD_LENGTH; i++) { + if (G.mqtt.user[i] != '\0' && !isSpace(G.mqtt.user[i])) { + return false; // Array is not empty + } + } + return true; // Array is empty +} + +//------------------------------------------------------------------------------ + +/* Description: + +This function initializes an MQTT client connection on an ESP8266 device using +the PubSubClient library. It sets up the MQTT client with the provided MQTT +server address, port, client ID, and other optional parameters such as user +credentials and topic subscription. + +Input: + +None +Output: + +None +*/ + void Mqtt::init() { mqttClient.setServer(G.mqtt.serverAdress, G.mqtt.port); mqttClient.setCallback(callback); - mqttClient.connect(G.mqtt.clientId, G.mqtt.user, G.mqtt.password); + if (checkIfMqttUserIsEmpty()) { + mqttClient.connect(G.mqtt.clientId); + } else { + mqttClient.connect(G.mqtt.clientId, G.mqtt.user, G.mqtt.password); + } + delay(50); mqttClient.subscribe((std::string(G.mqtt.topic) + "/cmd").c_str()); + delay(50); + if (isConnected()) { + Serial.println("MQTT Connected"); + } } //------------------------------------------------------------------------------ +/* Description: + +This function attempts to reconnect the MQTT client to the broker after a loss +of connection. It includes a retry mechanism with a maximum number of retries +and a timeout duration. + +Input: + +None +Output: + +None +*/ + void Mqtt::reInit() { - mqttClient.connect(G.mqtt.clientId, G.mqtt.user, G.mqtt.password); - reconnect(); + static uint8_t retryCount = 0; + static uint32_t lastRetryTime = 0; + static uint32_t retryIntervall = RETRY_INTERVALL_WITHIN_5_MINUTES; + + if (millis() - lastRetryTime >= retryIntervall) { + retryCount++; + Serial.print("Reconnecting to MQTT Server. Try "); + Serial.println(retryCount); + lastRetryTime = millis(); + + init(); + + // Check if maximum retries reached + if (retryCount >= MAX_RETRIES_WITHIN_5_MINUTES) { + Serial.println("Switched to hourly MQTT connect retry"); + retryIntervall = RETRY_INTERVALL; + retryCount = 0; + } + + if (isConnected()) { + retryCount = 0; + retryIntervall = RETRY_INTERVALL_WITHIN_5_MINUTES; + } + } } //------------------------------------------------------------------------------ -bool Mqtt::getConnected() { return mqttClient.connected(); } +/* Description: + +This function checks whether the MQTT client is currently connected to the MQTT +broker. It returns a boolean value indicating the connection status. + +Input: + +None +Output: + +Boolean value: +true if the MQTT client is connected to the broker. +false if the MQTT client is not connected to the broker. + */ + +bool Mqtt::isConnected() { return mqttClient.connected(); } //------------------------------------------------------------------------------ +/* Description: + +This function is responsible for managing the MQTT client's main loop. It checks +the MQTT connection status and reinitializes the connection if necessary. +Additionally, it invokes the loop() function of the underlying MQTT client +library to handle incoming MQTT messages and maintain the connection. + +Input: + +None +Output: + +None +*/ + void Mqtt::loop() { - if (!mqttClient.connected()) { - reconnect(); + if (!isConnected()) { + reInit(); } mqttClient.loop(); } //------------------------------------------------------------------------------ +/* Description: + +This function handles incoming MQTT messages received from the broker. It +parses the payload as JSON data and updates the device's state and +parameters accordingly. + +Input: + +char *topic: A pointer to a character array containing the topic of the +received message. byte *payload: A pointer to an array of bytes containing +the payload of the received message. unsigned int length: The length of the +payload in bytes. Output: + +None +*/ + void Mqtt::callback(char *topic, byte *payload, unsigned int length) { StaticJsonDocument<512> doc; Serial.print("Received message ["); Serial.print(topic); Serial.print("] "); + char msg[length + 1]; - for (uint32_t i = 0; i < length; i++) { - Serial.print((char)payload[i]); - msg[i] = (char)payload[i]; - } - Serial.println(); + // Convert payload to a null-terminated string + memcpy(msg, payload, length); msg[length] = '\0'; + // Deserialize JSON DeserializationError error = deserializeJson(doc, msg); if (error) { @@ -64,10 +200,12 @@ void Mqtt::callback(char *topic, byte *payload, unsigned int length) { return; } + // Process received JSON data if (doc.containsKey("state")) { - if (!strcmp(doc["state"], "ON")) { + const char *state = doc["state"]; + if (!strcmp(state, "ON")) { G.state = true; - } else if (!strcmp(doc["state"], "OFF")) { + } else if (!strcmp(state, "OFF")) { led.clear(); led.show(); G.state = false; @@ -75,37 +213,42 @@ void Mqtt::callback(char *topic, byte *payload, unsigned int length) { parametersChanged = true; } + const char *effect = doc["effect"]; if (doc.containsKey("effect")) { - if (!strcmp("Wordclock", doc["effect"])) { + if (!strcmp("Wordclock", effect)) { G.prog = COMMAND_MODE_WORD_CLOCK; - } else if (!strcmp("Seconds", doc["effect"])) { + } else if (!strcmp("Seconds", effect)) { G.prog = COMMAND_MODE_SECONDS; - } else if (!strcmp("Digitalclock", doc["effect"])) { + } else if (!strcmp("Digitalclock", effect)) { G.prog = COMMAND_MODE_DIGITAL_CLOCK; - } else if (!strcmp("Scrollingtext", doc["effect"])) { + } else if (!strcmp("Scrollingtext", effect)) { G.prog = COMMAND_MODE_SCROLLINGTEXT; - } else if (!strcmp("Rainbowcycle", doc["effect"])) { + } else if (!strcmp("Rainbowcycle", effect)) { G.prog = COMMAND_MODE_RAINBOWCYCLE; - } else if (!strcmp("Rainbow", doc["effect"])) { + } else if (!strcmp("Rainbow", effect)) { G.prog = COMMAND_MODE_RAINBOW; - } else if (!strcmp("Color", doc["effect"])) { + } else if (!strcmp("Color", effect)) { G.prog = COMMAND_MODE_COLOR; - } else if (!strcmp("Symbol", doc["effect"])) { + } else if (!strcmp("Symbol", effect)) { G.prog = COMMAND_MODE_SYMBOL; } } + // Copy marquee_text if present if (doc.containsKey("marquee_text")) { strcpy(G.scrollingText, doc["marquee_text"]); } - if (doc.containsKey("color")) { + // Update color if present + JsonObject color = doc["color"]; + if (!color.isNull()) { G.color[Foreground] = - HsbColor(float(doc["color"]["h"]) / 360.f, - float(doc["color"]["s"]) / 100.f, G.color[Foreground].B); + HsbColor(float(color["h"]) / 360.f, float(color["s"]) / 100.f, + G.color[Foreground].B); parametersChanged = true; } + // Update brightness if present if (doc.containsKey("brightness")) { G.color[Foreground] = HsbColor(G.color[Foreground].H, G.color[Foreground].S, @@ -116,6 +259,22 @@ void Mqtt::callback(char *topic, byte *payload, unsigned int length) { //------------------------------------------------------------------------------ +/* Description: + +This function is responsible for publishing the current state of the device +to an MQTT topic. It constructs a JSON message containing information about +the device state, such as the power state (ON or OFF), color settings, and +brightness. The constructed JSON message is then published to the MQTT +broker on a specified topic. + +Input: + +None +Output: + +None +*/ + void Mqtt::sendState() { StaticJsonDocument<200> doc; @@ -137,39 +296,55 @@ void Mqtt::sendState() { void Mqtt::sendDiscovery() { - // MQTT discovery for Home Assistant - // { - // "brightness": true, - // "color_mode": true, - // "supported_color_modes": [ - // "hs" - // ], - // "schema": "json", - // "name": "ESP", - // "device": { - // "identifiers": [ - // "ESPBuro" - // ], - // "name": "ESP", - // "sw_version": "3.3", - // "configuration_url": "http://" - // }, - // "state_topic": "ESPBuro/status", - // "command_topic": "ESPBuro/cmd", - // "unique_id": "", - // "plattform": "mqtt", - // "effect": true, - // "effect_list": [ - // "Wordclock", - // "Seconds", - // "Digitalclock", - // "Scrollingtext", - // "Rainbowcycle", - // "Rainbow", - // "Color", - // "Symbol" - // ] - // } + /* Description: + + This function publishes MQTT discovery messages for Home Assistant, + providing configuration details for a light entity. It constructs a JSON + payload according to Home Assistant's MQTT discovery format and + publishes it to the appropriate topic. + + Input: + + None + Output: + + None + */ + + /* Example MQTT Message + { + "brightness": true, + "color_mode": true, + "supported_color_modes": [ + "hs" + ], + "schema": "json", + "name": "ESP", + "device": { + "identifiers": [ + "ESPBuro" + ], + "name": "ESP", + "sw_version": "3.3", + "configuration_url": "http://" + }, + "state_topic": "ESPBuro/status", + "command_topic": "ESPBuro/cmd", + "unique_id": "", + "plattform": "mqtt", + "effect": true, + "effect_list": [ + "Wordclock", + "Seconds", + "Digitalclock", + "Scrollingtext", + "Rainbowcycle", + "Rainbow", + "Color", + "Symbol" + ] + } + */ StaticJsonDocument<700> root; mqttClient.setBufferSize(700); @@ -216,10 +391,3 @@ void Mqtt::sendDiscovery() { .c_str(), buffer, true); } - -//------------------------------------------------------------------------------ - -void Mqtt::reconnect() { - mqttClient.subscribe((std::string(G.mqtt.topic) + "/cmd").c_str()); - Serial.println("MQTT Connected..."); -} diff --git a/include/webPageAdapter.h b/include/webPageAdapter.h index 55c38e7..32d579b 100644 --- a/include/webPageAdapter.h +++ b/include/webPageAdapter.h @@ -111,13 +111,11 @@ WebPageAdapter webSocket = WebPageAdapter(80); //------------------------------------------------------------------------------ uint16_t split(uint8_t *payload, uint8_t start, uint8_t length = 3) { - char buffer[length]; - uint8_t m = 0; - for (uint16_t k = start; k < (start + length); k++) { - buffer[m] = payload[k]; - m++; + String value; + for (uint16_t k = start; k < start + length; k++) { + value += char(payload[k]); } - return atoi(buffer); + return value.toInt(); } //------------------------------------------------------------------------------ @@ -150,7 +148,7 @@ bool compareEffBriAndSpeedToOld(uint8_t *payload) { //------------------------------------------------------------------------------ -void parseColor(uint8_t *payload, uint8_t position = Foreground) { +void parseColor(uint8_t *payload, ColorPosition position = Foreground) { if (position == Background) { G.color[position] = {HsbColor(split(payload, 12) / 360.f, split(payload, 15) / 100.f, @@ -272,6 +270,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, G.progInit = true; } + parametersChanged = true; parseColor(payload); break; } @@ -392,10 +391,13 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, //------------------------------------------------------------------------------ case COMMAND_SET_MQTT: { - if (!G.mqtt.state) { + uint8_t newState = split(payload, 3); + + if (newState && !G.mqtt.state) { G.progInit = true; } - G.mqtt.state = split(payload, 3); + + G.mqtt.state = newState; G.mqtt.port = split(payload, 6, 5); uint8_t index_start = 11; payloadTextHandling(payload, G.mqtt.serverAdress, index_start); diff --git a/package-lock.json b/package-lock.json index cc6dc04..724c639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,8 @@ "license": "BSD-3-Clause", "dependencies": { "@jaames/iro": "^5.5.2", - "i18next": "^23.8.1", - "i18next-browser-languagedetector": "^7.2.0", + "i18next": "^23.11.3", + "i18next-browser-languagedetector": "^7.2.1", "minified": "^1.0.1", "purecss": "^3.0.0" }, @@ -28,7 +28,7 @@ "grunt-htmllint": "^0.3.0", "grunt-terser": "^2.0.0", "grunt-version": "^3.0.1", - "terser": "^5.27.0" + "terser": "^5.31.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2367,9 +2367,9 @@ } }, "node_modules/i18next": { - "version": "23.8.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.1.tgz", - "integrity": "sha512-Yhe6oiJhigSh64ev7nVVywu7vHjuUG41MRmFKNwphbkadqTL1ozZFBQISflY7/ju+gL6I/SPfI1GgWQh1yYArA==", + "version": "23.11.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.3.tgz", + "integrity": "sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==", "funding": [ { "type": "individual", @@ -2389,9 +2389,9 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz", - "integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz", + "integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==", "dependencies": { "@babel/runtime": "^7.23.2" } @@ -3658,9 +3658,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -5769,17 +5769,17 @@ } }, "i18next": { - "version": "23.8.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.8.1.tgz", - "integrity": "sha512-Yhe6oiJhigSh64ev7nVVywu7vHjuUG41MRmFKNwphbkadqTL1ozZFBQISflY7/ju+gL6I/SPfI1GgWQh1yYArA==", + "version": "23.11.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.3.tgz", + "integrity": "sha512-Pq/aSKowir7JM0rj+Wa23Kb6KKDUGno/HjG+wRQu0PxoTbpQ4N89MAT0rFGvXmLkRLNMb1BbBOKGozl01dabzg==", "requires": { "@babel/runtime": "^7.23.2" } }, "i18next-browser-languagedetector": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.0.tgz", - "integrity": "sha512-U00DbDtFIYD3wkWsr2aVGfXGAj2TgnELzOX9qv8bT0aJtvPV9CRO77h+vgmHFBMe7LAxdwvT/7VkCWGya6L3tA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.1.tgz", + "integrity": "sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==", "requires": { "@babel/runtime": "^7.23.2" } @@ -6736,9 +6736,9 @@ "dev": true }, "terser": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", - "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", diff --git a/package.json b/package.json index 0a3cbdc..162fbaf 100644 --- a/package.json +++ b/package.json @@ -85,12 +85,12 @@ "grunt-htmllint": "^0.3.0", "grunt-terser": "^2.0.0", "grunt-version": "^3.0.1", - "terser": "^5.27.0" + "terser": "^5.31.0" }, "dependencies": { "@jaames/iro": "^5.5.2", - "i18next": "^23.8.1", - "i18next-browser-languagedetector": "^7.2.0", + "i18next": "^23.11.3", + "i18next-browser-languagedetector": "^7.2.1", "minified": "^1.0.1", "purecss": "^3.0.0" } diff --git a/platformio.ini b/platformio.ini index 67fb0b7..4606549 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,13 +19,13 @@ framework = arduino upload_speed = 115200 monitor_speed = 460800 monitor_filters = esp8266_exception_decoder -lib_deps = - makuna/NeoPixelBus@^2.7.6 - bblanchon/ArduinoJson@^6.17.2 - links2004/WebSockets@^2.2.1 - adafruit/RTClib@^1.11.2 - knolleary/PubSubClient@^2.8.0 - https://github.com/tzapu/WiFiManager.git#88c378f - bbx10/DNSServer@^1.1.0 - claws/BH1750@^1.3.0 +lib_deps = + makuna/NeoPixelBus@^2.7.6 + bblanchon/ArduinoJson@^6.17.2 + links2004/WebSockets@^2.2.1 + adafruit/RTClib@^1.11.2 + knolleary/PubSubClient@^2.8.0 + https://github.com/tzapu/WiFiManager#v2.0.17 + bbx10/DNSServer@^1.1.0 + claws/BH1750@^1.3.0 extra_scripts = pre:extra_scripts.py diff --git a/src/Wortuhr.cpp b/src/Wortuhr.cpp index 3887cd3..5208e1d 100644 --- a/src/Wortuhr.cpp +++ b/src/Wortuhr.cpp @@ -65,6 +65,10 @@ Network network; _Static_assert(sizeof(G) <= EEPROM_SIZE, "Datenstruktur G zu gross für reservierten EEPROM Bereich"); +uint16_t powerCycleCountAddr = + EEPROM_SIZE - 1; // Address in EEPROM to store power cycle count +uint16_t powerCycleCount = 0; // Variable to store power cycle count + //------------------------------------------------------------------------------ uint32_t sntp_startup_delay_MS_rfc_not_less_than_60000() { @@ -112,6 +116,17 @@ void time_is_set() { parametersChanged = true; } +//------------------------------------------------------------------------------ + +void incrementPowerCycleCount() { + if (powerCycleCount > 5) { + powerCycleCount = 0; + } + powerCycleCount++; + EEPROM.write(powerCycleCountAddr, powerCycleCount); + EEPROM.commit(); +} + //------------------------------------------------------------------------------ // Start setup() //------------------------------------------------------------------------------ @@ -130,10 +145,24 @@ void setup() { //------------------------------------- // Read / initialize EEPROM //------------------------------------- - EEPROM.begin(EEPROM_SIZE); + EEPROM.begin(EEPROM_SIZE); eeprom::read(); + //------------------------------------- + + // Read the power cycle count from EEPROM + powerCycleCount = EEPROM.read(powerCycleCountAddr); + incrementPowerCycleCount(); + Serial.print("Power cycle count: "); + Serial.println(powerCycleCount); + if (powerCycleCount == 5) { + G.sernr++; + Serial.println("Reset to initial values"); + } + + //------------------------------------- + if (G.sernr != SERNR) { for (uint16_t i = 0; i < EEPROM_SIZE; i++) { EEPROM.write(i, i); @@ -210,7 +239,7 @@ void setup() { G.transitionType = 0; // Transition::NO_TRANSITION; G.transitionDuration = 2; G.transitionSpeed = 30; - G.transitionColorize = 1; + G.transitionColorize = 0; G.transitionDemo = false; eeprom::write(); @@ -363,6 +392,14 @@ void setup() { Serial.println("--------------------------------------"); Serial.println(""); + //------------------------------------- + // Reset Powercycle Counter + //------------------------------------- + delay(500); + powerCycleCount = 0; + EEPROM.write(powerCycleCountAddr, powerCycleCount); + EEPROM.commit(); + //------------------------------------- // Setup Done //------------------------------------- diff --git a/webpage/i18n.js b/webpage/i18n.js index 9f8efb0..553155c 100644 --- a/webpage/i18n.js +++ b/webpage/i18n.js @@ -27,6 +27,9 @@ i18next.init({ }, hu: { translation: TRANSLATION_HU + }, + ru: { + translation: TRANSLATION_RU } } }, (error) => { diff --git a/webpage/index.html b/webpage/index.html index 493b5e0..3d0eb7d 100644 --- a/webpage/index.html +++ b/webpage/index.html @@ -65,6 +65,7 @@ + @@ -244,6 +245,8 @@

+ +
@@ -747,6 +750,7 @@

+ diff --git a/webpage/language/de.js b/webpage/language/de.js index dbd4362..89042b7 100644 --- a/webpage/language/de.js +++ b/webpage/language/de.js @@ -11,7 +11,8 @@ let TRANSLATION_DE_DE = { "nl": "🇳🇱 Niederländisch", "es": "🇪🇸 Spanisch", "it": "🇮🇹 Italienisch", - "hu": "🇭🇺 Ungarisch" + "hu": "🇭🇺 Ungarisch", + "ru": "🇷🇺 Russisch" } }, @@ -112,6 +113,8 @@ let TRANSLATION_DE_DE = { "ch-10-11": "🇨🇭 10 x 11", "ro-10-11": "🇷🇴 10 x 11", "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Minutenzählrichtung invertieren", "mirror-vertically": "Wortuhr vertikal spiegeln", "mirror-horizontally": "Wortuhr horizontal spiegeln", diff --git a/webpage/language/en.js b/webpage/language/en.js index 23d2101..288b331 100644 --- a/webpage/language/en.js +++ b/webpage/language/en.js @@ -11,7 +11,8 @@ let TRANSLATION_EN_US = { "nl": "🇳🇱 Dutch", "es": "🇪🇸 Spanish", "it": "🇮🇹 Italian", - "hu": "🇭🇺 Hungarian" + "hu": "🇭🇺 Hungarian", + "ru": "🇷🇺 Russian" } }, @@ -112,6 +113,8 @@ let TRANSLATION_EN_US = { "ch-10-11": "🇨🇭 10 x 11", "ro-10-11": "🇷🇴 10 x 11", "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Invert Minute Counting Direction", "mirror-vertically": "Mirror Word Clock Vertically", "mirror-horizontally": "Mirror Word Clock Horizontally", diff --git a/webpage/language/es.js b/webpage/language/es.js index 1e00c37..13944cd 100644 --- a/webpage/language/es.js +++ b/webpage/language/es.js @@ -11,7 +11,8 @@ let TRANSLATION_ES = { "nl": "🇳🇱 Holandés", "es": "🇪🇸 Español", "it": "🇮🇹 Italiano", - "hu": "🇭🇺 Húngaro" + "hu": "🇭🇺 Húngaro", + "ru": "🇷🇺 Ruso" } }, @@ -112,6 +113,8 @@ let TRANSLATION_ES = { "ch-10-11": "🇨🇭 10 x 11", "ro-10-11": "🇷🇴 10 x 11", "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Invertir la dirección de conteo de minutos", "mirror-vertically": "Voltear el reloj verticalmente", "mirror-horizontally": "Voltear el reloj horizontalmente", diff --git a/webpage/language/hu.js b/webpage/language/hu.js index f3fcfba..e294ab1 100644 --- a/webpage/language/hu.js +++ b/webpage/language/hu.js @@ -11,7 +11,8 @@ let TRANSLATION_HU = { "nl": "🇳🇱 Holland", "es": "🇪🇸 Spanyol", "it": "🇮🇹 Olasz", - "hu": "🇭🇺 Magyar" + "hu": "🇭🇺 Magyar", + "ru": "🇷🇺 Orosz" } }, @@ -109,6 +110,8 @@ let TRANSLATION_HU = { "es-10-11": "🇪🇸 10 × 11", "it-10-11": "🇮🇹 10 × 11", "hu-10-10": "🇭🇺 10 x 10", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Percszámláló-irány megfordítása", "mirror-vertically": "Szövegóra függőleges tükrözése", "mirror-horizontally": "Szövegóra vízszintes tükrözése", diff --git a/webpage/language/it.js b/webpage/language/it.js index d8781e4..f4af65e 100644 --- a/webpage/language/it.js +++ b/webpage/language/it.js @@ -11,7 +11,8 @@ let TRANSLATION_IT = { "nl": "🇳🇱 Olandese", "es": "🇪🇸 Spagnolo", "it": "🇮🇹 Italiano", - "hu": "🇭🇺 Ungherese" + "hu": "🇭🇺 Ungherese", + "ru": "🇷🇺 Russo" } }, @@ -112,6 +113,8 @@ let TRANSLATION_IT = { "ch-10-11": "🇨🇭 10 x 11", "ro-10-11": "🇷🇴 10 x 11", "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Invertire la direzione del conteggio dei minuti", "mirror-vertically": "Capovolgi l'orologio verticalmente", "mirror-horizontally": "Capovolgi l'orologio orizzontalmente", diff --git a/webpage/language/nl.js b/webpage/language/nl.js index fbc892f..75e4831 100644 --- a/webpage/language/nl.js +++ b/webpage/language/nl.js @@ -11,7 +11,8 @@ let TRANSLATION_NL = { "nl": "🇳🇱 Nederlands", "es": "🇪🇸 Spaans", "it": "🇮🇹 Italiaans", - "hu": "🇭🇺 Hongaars" + "hu": "🇭🇺 Hongaars", + "ru": "🇷🇺 Russisch" } }, @@ -112,6 +113,8 @@ let TRANSLATION_NL = { "ch-10-11": "🇨🇭 10 x 11", "ro-10-11": "🇷🇴 10 x 11", "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", "invert-min-dir": "Draai minuutrichting om", "mirror-vertically": "Spiegel Woordklok verticaal", "mirror-horizontally": "Spiegel Woordklok horizontaal", diff --git a/webpage/language/ru.js b/webpage/language/ru.js new file mode 100755 index 0000000..d8320cd --- /dev/null +++ b/webpage/language/ru.js @@ -0,0 +1,274 @@ +let TRANSLATION_RU = { + "title": "Словесные часы by Ragman", + + "menu": { + "aria-show-menu": "Покажи мне меню", + "aria-hide-menu": "Скрыть меню", + "language": { + "label": "Язык", + "de": "🇩🇪 Немецкий", + "en": "🇬🇧 Английский", + "nl": "🇳🇱 Голландский", + "es": "🇪🇸 Испанский", + "it": "🇮🇹 Итальянский", + "hu": "🇭🇺 Венгерский", + "ru": "🇷🇺 Pусский" + } + }, + + "connection-lost": { + "button": "Соединять", + "text": "Потерялась связь с часами." + }, + + "colors": { + "h1": "Цвета", + "with-background": "С цветом фона" + }, + + "functions": { + "h1": "Характеристики", + "mode": { + "h2": "Режим", + "wordclock": "Словесные часы", + "color": "Цвет", + "seconds": "Секунды", + "digital-clock": "Цифровые часы", + "scrollingtext": "Бегущий текст", + "rainbow": "Радуга", + "color-change": "Изменение цвета", + "symbol": "Символ" + }, + "animation": { + "h2": "Анимация", + "type": { + "label": "Тип" + }, + "duration": { + "label": "Временные рамки", + "short": "короткий", + "medium": "половина", + "long": "длинный" + }, + "color": { + "label": "Анимация в цветах", + "off": "Bыключен", + "words": "Красочные слова", + "letters": "Красочные буквы" + }, + "speed": { + "label": "Скорость" + }, + "demo": { + "label": "Демо" + } + }, + "settings": { + "h2": "Настройки", + "brightness": "Светиться ", + "scrollingtext": "Бегущий текст", + "save": "сохранять", + "speed": "Скорость ", + "leds": "Количество светодиодов ", + "position": "Позиция " + } + }, + + "view": { + "h1": "Параметры экрана", + "front": { + "h2": "Front", + "text": "Изменения размера массива применяются только при сбросе часов.", + "variant": "Выбор варианта часов", + "de-10-11": "🇩🇪 10 × 11", + "de-10-11-alt": "🇩🇪 10 × 11 Альтернатива", + "de-10-11-alt-frame": "🇩🇪 10 × 11 Альтернативная рама", + "de-10-11-vertical": "🇩🇪 10 × 11 Вертикальный", + "de-10-11-clock": "🇩🇪 10 × 11 Часы", + "de-10-11-nero": "🇩🇪 10 × 11 Nero", + "de-10-11-schwaebisch": "🇩🇪 10 × 11 Швабия", + "de-11-11": "🇩🇪 11 × 11", + "de-11-11-v2": "🇩🇪 11 × 11 V2", + "de-11-11-v3": "🇩🇪 11 × 11 (panbachi)", + "de-22-11-weather": "🇩🇪 10 × 11 Погода", + "de-16-8": "🇩🇪 16 × 8", + "de-16-18": "🇩🇪 16 × 18", + "en-10-11": "🇬🇧 10 × 11", + "nl-10-11": "🇳🇱 10 × 11", + "es-10-11": "🇪🇸 10 × 11", + "it-10-11": "🇮🇹 10 × 11", + "hu-10-10": "🇭🇺 10 x 10", + "ch-10-11": "🇨🇭 10 x 11", + "ro-10-11": "🇷🇴 10 x 11", + "fr-10-11": "🇫🇷 10 x 11", + "se-10-11": "🇸🇪 10 × 11", + "ru-10-11": "🇷🇺 10 × 11", + "invert-min-dir": "Обратное направление отсчета минут", + "mirror-vertically": "Переверните часы вертикально", + "mirror-horizontally": "Переверните часы горизонтально", + "buildtype": "Тип конструкции", + "normal": "Каждый светодиод соответствует букве", + "doubleResM1": "Каждый второй светодиод соответствует букве", + "doubleRes": "Два светодиода на букву" + }, + "language": { + "h2": "Язык", + "hide-it-is": "„ЧАС, ЧАСA, ЧАСОВ“ скрывать", + "at-nine-fifteen": "ЧАСОВ 9:15 ...", + "quarter-past-nine": "четверть десятого", + "quarter-nine": "четверть десяти", + "at-nine-twenty": "ЧАСОВ 9:20 ...", + "twenty-past-nine": "девять и двадцать", + "at-nine-fourty": "ЧАСОВ 9:40 ...", + "twenty-before-ten": "от двадцати до десяти", + "at-nine-fourtyfive-german": "ЧАСОВ 9:45 ...", + "quarter-to-ten": "восемь сорок пять", + "three-quarter-ten": "Три четверти десять", + "quarter-to-ten-english": "без четверти десять", + "a-quarter-to-ten-english": "без четверти десять" + }, + "minutes": { + "h2": "минуты", + "mode": "Режим визуализации по минутам", + "off": "Bыключен", + "normal-4-leds": "Нормальный (4 LEDs)", + "normal-7-leds": "Нормальный (7 LEDs)", + "edges": "Углы", + "in-words": "В словах" + }, + "seconds": { + "h2": "Секунды", + "in-border": "Секунды отображаются в рамке.", + "off": "Bыключен", + "point": "Место", + "sector": "Сектор", + "revolving-sector": "Оборотный сектор" + }, + "boot": { + "h2": "Начинать", + "help": "Что должно произойти при перезагрузке?", + "blink-leds": "Пусть светодиоды кратковременно мигнут", + "iterate-leds": "Пусть все светодиоды пройдут один раз", + "wifi-symbol": "Показывать значок Wi-Fi во время поиска сети", + "ip-address": "Показать IP-адрес" + } + }, + + "settings": { + "h1": "Настройки", + "status": { + "h2": "Состояние", + "label": "Статус подключения", + "connect": "Соединять" + }, + "led-type": { + "h2": "Тип светодиода", + "help": "Здесь можно настроить тип используемой светодиодной ленты (WS2812 или SK6812).\n" + + "В зависимости от производителя используемых полосок SK2812 существует другая версия, в которой цвета отображаются в разном порядке.\n" + + "Соответственно, здесь можно задать правильную цветовую схему полосы.\n" + + "G-зеленый/B-синий/R-красный/W-белый", + "label": "Изменение цветотипа", + "ws2812-brg": "WS2812 BRG", + "ws2812-grb": "WS2812 GRB", + "ws2812-rgb": "WS2812 RGB", + "ws2812-rbg": "WS2812 RBG", + "sk6812-brgw": "SK6812 RGBW", + "save": "Сохранить настройки" + }, + "whitetype": { + "h2": "Тип белого светодиода", + "in-border": "Настройка типа белого светодиода полосы RGBW", + "ww": "теплый белый", + "nw": "Нейтральный белый", + "cw": "Холодный белый" + }, + "time-server": { + "h2": "сервер времени", + "label": "сервер времени", + "save": "Сохранить настройки" + }, + "manual-time": { + "h2": "Установите время вручную", + "time": "Время", + "save": "Сохранить настройки" + }, + "brightness": { + "h2": "Светиться", + "mode": "Режим", + "automatic": "Автоматически", + "manual": "Вручную", + "p100": "100 %", + "p80": "80 %", + "p60": "60 %", + "p40": "40 %", + "p20": "20 %", + "off": "Aus", + "zero-to-six": "0:00 – 5:59", + "six-to-eight": "6:00 – 7:59", + "eight-to-twelve": "8:00 – 11:59", + "twelve-to-sixteen": "12:00 – 15:59", + "sixteen-to-eighteen": "16:00 – 17:59", + "eighteen-to-twenty": "18:00 – 19:59", + "twenty-to-twenty-two": "20:00 – 21:59", + "twenty-two-to-zero": "22:00 – 23:59", + "ldr-value": "Текущая яркость фоторезистора (LDR)", + "value-bright": "Значение «Яркое» (0 – 255)", + "value-dark": "«Темное» значение (0 – 255)" + }, + "hostname": { + "h2": "Имя хоста", + "label": "Имя хоста", + "save": "Guardar ajustes" + }, + "weather": { + "h2": "Климат", + "api-key": "OpenWeatherMap API-Ключ", + "city-id": "OpenWeatherMap Город-ID", + "save": "Сохранить настройки" + }, + "wifi": { + "h2": "WiFi / WLAN", + "help": "Реконфигурация создает вашу собственную WLAN.\n" + + "Подключитесь к этому, и тогда можно будет выбрать сеть назначения.", + "ssid": "Имя WLAN (SSID)", + "other-wifi": "Другой Wi-Fi", + "configure": "Настраивать", + "until-restart": "До перезагрузки", + "deactivate": "Выключить" + }, + "restart": { + "h2": "Перезапуск", + "reset": "Начать заново", + "restart": "Сброс к настройкам по умолчанию" + } + }, + + "smart-home": { + "h1": "Умный дом", + "mqtt": { + "h2": "MQTT", + "text": "Программное обеспечение Word Clock предлагает возможность управления через интерфейс MQTT. Вы можете интегрировать часы в существующую систему домашнего помощника, чтобы иметь возможность управлять элементарными функциями. Эта функция все еще является экспериментальной и постоянно расширяется.", + "activate": "Включить MQTT", + "server": "Адрес сервера", + "port": "Порт", + "username": "Имя пользователя", + "password": "Пароль", + "client-id": "ID клиента", + "topic": "Tema", + "save": "Сохранить настройки", + "discovery": "Отправить HA Discovery" + } + }, + + "about": { + "h1": "О", + "text-general": "Программное обеспечение этих часов основано на часах из Ульрих Радиг (Стенд 2019) и я теперь от сообщества продолжим разработку GitHub.", + "text-update": "Можно выполнить обновление ПО, сделайте это. Перейти на страницу обновления.", + "text-license": "Этот проект имеет открытый исходный код и распространяется по 3-пунктной лицензии BSD..", + "software": "Библиотеки и программное обеспечение", + "debug": "Отладочный вывод" + }, + "footer": { + "version": "Версия" + } +}; diff --git a/webpage/script.js b/webpage/script.js index 43ba5ea..e0a9b6c 100644 --- a/webpage/script.js +++ b/webpage/script.js @@ -448,7 +448,11 @@ function initWebsocket() { function changeColor(color) { hsb[color.index][0] = color.hue; hsb[color.index][1] = color.saturation; - hsb[color.index][2] = color.value; + if (color.value !== 100) { + hsb[color.index][2] = color.value; + } + + setColors(); sendColorData(command, nstr(1)); }