#include #include #include #include #include // ---------------- MQTT Settings ------------------ const char *mqtt_server = "mqtt.chaospott.de"; // <<< anpassen! const int mqtt_port = 1883; const char *mqtt_client = "fooclock"; // Eindeutige ID für die Uhr in Home Assistant const char *device_id = "fooclock_01"; // Topics für Home Assistant const char *mqtt_command_topic = "fooclock/display/set"; // Topic zum Senden von Befehlen (ON/OFF) const char *mqtt_state_topic = "fooclock/display/state"; // Topic zum Melden des Status (ON/OFF) const char *mqtt_discovery_topic = "homeassistant/switch/fooclock/display/config"; // HA Auto-Discovery Topic EthernetClient ethClient; PubSubClient client(ethClient); bool displayEnabled = true; // Steuerflag für Anzeige // Forward declaration der neuen Funktionen void publishState(); void publishDiscoveryMessage(); // ------------------------------------------------- #define _A 0 #define _B 1 #define _C 2 #define _D 3 #define _E 4 #define _F 5 #define _G 6 #define _H 7 #define _I 8 #define _J 9 #define _K 10 #define _L 11 #define _M 12 #define _N 13 #define _O 14 #define _P 15 #define _Q 16 #define _R 17 #define _S 18 #define _T 19 #define _U 20 #define _V 21 #define _W 22 #define _X 23 #define _Y 24 #define _Z 25 #define UP 1 #define DOWN 2 #define light_lvl_standard 60 #define light_lvl_risen 255 // ---------------------------------- Definition of Global Variables -------------------------- // byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // NTP Servers: IPAddress timeServer(132, 163, 96, 1); // time-a.timefreq.bldrdoc.gov // IPAddress timeServer(132, 163, 96, 2); // time-b.timefreq.bldrdoc.gov // IPAddress timeServer(132, 163, 96, 3); // time-c.timefreq.bldrdoc.gov //const int timeZone = 1; // Central European Time const int timeZone = 2; // Central European Time (summertime) //const int timeZone = -5; // Eastern Standard Time (USA) //const int timeZone = -4; // Eastern Daylight Time (USA) //const int timeZone = -8; // Pacific Standard Time (USA) //const int timeZone = -7; // Pacific Daylight Time (USA) time_t cur_time = 0; time_t alarm_goal = 0; bool update_done = false; bool second_changed = false; bool init_done = false; bool transition_active = false; bool swipe_active = false; bool shrink_active = false; int cur_update = 0; int shift_state = 0; int update_counter = 0; int spinner_pos = 0; int animation = 0; EthernetUDP Udp; EthernetUDP AlarmClock; unsigned int alarmClockPort = 123; // local port to listen for custom AlarmClock packets unsigned int localPort = 8888; // local port to listen for UDP packets int OutputEnable = 3; //Pin connected to ST_CP of 74HC595 int latchPin = 4; //Pin connected to SH_CP of 74HC595 int clockPin = 2; //Pin connected to DS of 74HC595 int dataPin = 5; // 4 // /======\ // || || // 8 || || 2 // || 16 || // >======< // || || // 128 || || 32 // || 64 || // \======/ O 1 int duration = 10; int digits[10] = { 238, // 0 0xEE 34, // 1 0x22 214, // 2 0xD6 118, // 3 0x76 58, // 4 0x3A 124, // 5 0x7C 252, // 6 0xFC 38, // 7 0x26 254, // 8 0xFE 126 // 9 0x7E }; int letter[26] = { 191, // A 248, // b 204, // C 242, // d 220, // E 156, // F 126, // g 184, // h 32, // i 230, // J 190, // k 200, // L 176, // m 176, // n 240, // o 158, // P 238, // Q 144, // r 124, // S 216, // t 224, // u 224, // v 26, // w 186, // X 58, // y 214, // Z }; int frame[6] = { 0, letter[_T], letter[_R], letter[_A], letter[_T], letter[_S] // Display "Start" (write from right to left into the array) }; int today[7] = { 184, 220, 200, 200, 240, 0, 0 // Display "Hello" }; // ---------------- MQTT Callback ------------------ void mqttCallback(char *topic, byte *payload, unsigned int length) { payload[length] = '\0'; // String terminieren String message = String((char *) payload); Serial.print("Nachricht empfangen ["); Serial.print(topic); Serial.print("] "); Serial.println(message); if (String(topic) == mqtt_command_topic) { if (message == "ON") { displayEnabled = true; Serial.println("MQTT: Display eingeschaltet"); } else if (message == "OFF") { displayEnabled = false; Serial.println("MQTT: Display ausgeschaltet"); } // Sende den neuen Status sofort zurück publishState(); } } // ---------------- MQTT Publish State ------------------ void publishState() { if (displayEnabled) { client.publish(mqtt_state_topic, "ON", true); // true = retain message } else { client.publish(mqtt_state_topic, "OFF", true); // true = retain message } } // ---------------- MQTT Publish Home Assistant Discovery Message ------------------ void publishDiscoveryMessage() { // Use a fixed-size buffer to create the JSON payload. This is much more // memory-efficient than using the Arduino String class for concatenation. char discoveryPayload[400]; // Create the JSON payload using snprintf to prevent buffer overflows snprintf(discoveryPayload, sizeof(discoveryPayload), "{" "\"name\":\"FooClock Display\"," "\"unique_id\":\"%s_display\"," "\"cmd_t\":\"%s\"," "\"stat_t\":\"%s\"," "\"pl_on\":\"ON\"," "\"pl_off\":\"OFF\"," "\"icon\":\"mdi:clock-digital\"," "\"device\":{" "\"identifiers\":[\"%s\"]," "\"name\":\"FooClock\"," "\"manufacturer\":\"DIY\"" "}" "}", device_id, mqtt_command_topic, mqtt_state_topic, device_id ); // Nachricht mit Retain-Flag publishen, damit HA sie auch nach einem Neustart findet client.publish(mqtt_discovery_topic, discoveryPayload, true); } // ---------------- MQTT Reconnect ------------------ void reconnect() { while (!client.connected()) { Serial.print("MQTT Verbindung herstellen..."); if (client.connect(mqtt_client)) { Serial.println("verbunden."); // Sobald verbunden, Befehls-Topic abonnieren client.subscribe(mqtt_command_topic); // Discovery-Nachricht und initialen Status senden publishDiscoveryMessage(); publishState(); } else { Serial.print("Fehler, rc="); Serial.print(client.state()); Serial.println(" -> neuer Versuch in 5s"); delay(5000); } } } // ----------------------------------- Setup Code ---------------------------------------------- // void setup() { pinMode(OutputEnable, OUTPUT); pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); analogWrite(OutputEnable,light_lvl_standard); Timer1.initialize(1000); // initialize timer1, and set a 1 millisecond period Timer1.attachInterrupt(updateDisplay); // attaches callback() as a timer overflow interrupt Serial.begin(9600); while (Ethernet.begin(mac) == 0) { delay(5000); // Wait for a valid IP-Address } Udp.begin(localPort); setSyncProvider(getNtpTime); update_counter = 0; init_done = true; // <<< MQTT init >>> client.setServer(mqtt_server, mqtt_port); client.setCallback(mqttCallback); } // -------------------------------------- Main Loop ------------------------------------------------- // void loop() { // <<< MQTT >>> if (!client.connected()) { reconnect(); } client.loop(); time_t timestamp = now(); // Sync blinking with second change if (cur_time != timestamp && !second_changed) { update_counter = 0; cur_time = timestamp; second_changed = true; } int intervalpos = timestamp % 70; if (intervalpos == 0) { update_done = false; } if (intervalpos < 10) { switch (animation) { case 0: transition(today); break; case 1: combine(today); break; case 2: swipe(UP, today); break; case 3: swipe(DOWN, today); break; case 4: shrink(UP, today); break; case 5: shrink(DOWN, today); break; default: displayDate(timestamp); break; } // shrink(DOWN,today); // transition(today); // combine(today); // swipe(UP,today); // displayDate(timestamp); } else if (intervalpos > 20 && intervalpos < 30) { displayBinaryTime(timestamp); } else { displayTime(timestamp); } if (intervalpos == 50 && !update_done) { update_done = true; transition_active = false; swipe_active = false; shrink_active = false; shift_state = 0; updateDate(timestamp); animation = (int) (rand() % 6); } // Blinkenfoo - comment to remove pulsating light if (update_counter < (light_lvl_risen - light_lvl_standard) && timestamp % 600 == 0) { dim(UP,light_lvl_standard,light_lvl_risen); } if (update_counter > (1000 - (light_lvl_risen - light_lvl_standard)) && timestamp % 600 == 0) { dim(DOWN,light_lvl_standard,light_lvl_risen); } if (update_counter == 900) { second_changed = true; } } // ---------------------- Interrupt Handler (Timer1) ------------------- // void updateDisplay() { if (!displayEnabled) { // Anzeige aus: alles dunkel digitalWrite(latchPin, LOW); for (int digitCount = 5; digitCount >= 0; digitCount--) { shiftOut(dataPin, clockPin, MSBFIRST, 0); } digitalWrite(latchPin, HIGH); return; } // take the latchPin low so // the LEDs don't flicker while you're sending in bits: digitalWrite(latchPin, LOW); for (int digitCount = 5; digitCount >= 0; digitCount--) { shiftOut(dataPin, clockPin, MSBFIRST, frame[digitCount]); } //take the latch pin high so the LEDs will light up again: digitalWrite(latchPin, HIGH); update_counter++; if (update_counter == 1000) { update_counter = 0; } if (!init_done) { if (update_counter % 100 == 0) { spin(0); } } } // -------------------------------- Date Functions ---------------------- // void updateDate(time_t t) { today[0] = digits[(day(t) / 10)]; today[1] = digits[(day(t) % 10)] + 1; today[2] = digits[(month(t) / 10)]; today[3] = digits[(month(t) % 10)] + 1; today[4] = digits[((year(t) - 2000) / 10)]; today[5] = digits[((year(t) - 2000) % 10)]; } void displayDate(time_t t) { frame[5] = digits[(day(t) / 10)]; frame[4] = digits[(day(t) % 10)] + 1; frame[3] = digits[(month(t) / 10)]; frame[2] = digits[(month(t) % 10)] + 1; frame[1] = digits[((year(t) - 2000) / 10)]; frame[0] = digits[((year(t) - 2000) % 10)]; } // ------------------------------- Time Functions -------------------- // void displayTime(time_t t) { frame[5] = digits[(hour(t) / 10)]; frame[4] = digits[(hour(t) % 10)]; frame[3] = digits[(minute(t) / 10)]; frame[2] = digits[(minute(t) % 10)]; frame[1] = digits[(second(t) / 10)]; frame[0] = digits[(second(t) % 10)]; } void displayBinaryTime(time_t t) { int digit; for (int digitCount = 5; digitCount >= 0; digitCount--) { // digit zusammenbauen und dann invertieren digit = 0; digit |= ((hour(t) & (1 << digitCount)) >> digitCount) << 2; digit |= ((minute(t) & (1 << digitCount)) >> digitCount) << 4; digit |= ((second(t) & (1 << digitCount)) >> digitCount) << 6; // bitWrite(digit,2,(bitRead(hour(t), digitCount)) ); // bitWrite(digit,4,(bitRead(minute(t), digitCount)) ); // bitWrite(digit,6,(bitRead(second(t), digitCount)) ); frame[digitCount] = digit; } } // ----------------------------------- System Functions ----------------------------------- // void dim(int direction, int lower_limit, int upper_limit) { int range = upper_limit - lower_limit; int shift_start = 1000 - range; int write_out = 0; int local_counter = 0; if (update_counter > range) { local_counter = update_counter - shift_start; } else { local_counter = update_counter; } if (direction == DOWN && local_counter % range <= range) { write_out = 255 - (upper_limit - (local_counter) % range); analogWrite(OutputEnable, write_out); Serial.println("Down ->"); Serial.println(write_out); } else { write_out = 255 - (lower_limit + (local_counter % range)); analogWrite(OutputEnable, write_out); Serial.println("UP ->"); Serial.println(write_out); } } void spin(int digit) { if (spinner_pos >= 6)spinner_pos = 0; switch (spinner_pos) { case 0: frame[digit] = 4; break; case 1: frame[digit] = 2; break; case 2: frame[digit] = 32; break; case 3: frame[digit] = 64; break; case 4: frame[digit] = 128; break; case 5: frame[digit] = 8; break; default: frame[digit] = 0; break; } spinner_pos++; }; void shrink(int direction, int *new_data) { static int j = 0; static int height = 0; int bitmask = 0; if (shrink_active == false) { shrink_active = true; j = 0; if (direction == UP) { height = 2; } else { height = 0; } } switch (height) { case 0: bitmask = 0x44; break; case 1: bitmask = 0xAA; break; case 2: bitmask = 0x10; break; default: break; } if (update_counter % 100 == 0 && j < 7) { if (j < 3) { for (int digit = 0; digit < 6; digit++) { frame[digit] &= ~bitmask; } if (direction == DOWN) { height++; } else { height--; } Serial.print("Shrink.J = "); Serial.println(j); } else { for (int digit = 0; digit < 6; digit++) { frame[5 - digit] |= new_data[digit] & bitmask; } if (direction == DOWN) { height--; } else { height++; } Serial.print("Shrink.J = "); Serial.println(j); } j++; } } void swipe(int direction, int *new_data) { static int j = 0; static int height = 0; int bitmask = 0; if (swipe_active == false) { swipe_active = true; j = 0; if (direction == UP) { height = 4; } else { height = 0; } } switch (height) { case 0: bitmask = 0x04; break; case 1: bitmask = 0x0A; break; case 2: bitmask = 0x10; break; case 3: bitmask = 0xA0; break; case 4: bitmask = 0x40; break; default: break; } if (update_counter % 60 == 0 && j < 11) { if (j < 5) { for (int digit = 0; digit < 6; digit++) { frame[digit] &= ~bitmask; } if (direction == DOWN) { height++; } else { height--; } Serial.print("Swipe. J = "); Serial.println(j); } else { for (int digit = 0; digit < 6; digit++) { frame[5 - digit] |= new_data[digit] & bitmask; } if (direction == DOWN) { height--; } else { height++; } Serial.print("Swipe. J = "); Serial.println(j); } j++; } }; void combine(int *a) { static int i = 0; static int j = 0; int current[6]; if (transition_active == false) { transition_active = true; memcpy(current, frame, 6 * sizeof(int)); j = 0; } if (update_counter % 33 == 0 && j < 18) { Serial.print("i : "); Serial.print(i); Serial.print(" j : "); Serial.println(j); if (j % 3 == 0) { frame[5 - i] |= a[i]; } else if (j % 3 == 1) { frame[5 - i] &= ~current[i]; } else { frame[5 - i] = a[i]; i++; } if (i == 6) { i = 0; } j++; } } void shift_right(int neu) { for (int i = 0; i < 5; i++) { frame[i] = frame[i + 1]; } frame[5] = neu; } void transition(int *a) { if (update_counter % 150 == 0 && cur_update != update_counter && shift_state < sizeof(today) / 2) { //assuming the Array contains only ints, //which have a sizeof 2 on this Arduino Serial.println(sizeof(a)); cur_update = update_counter; shift_right(a[6 - shift_state]); shift_state++; } } /*-------- Alarm Clock code ---------*/ const int AlarmClockPacketSize = 256; byte AlarmBuffer[AlarmClockPacketSize]; int listenForAlarm() { while (AlarmClock.parsePacket() > 0); Serial.println("Any Alarm imminent?"); uint32_t packageBegin = millis(); while (millis() - packageBegin < 1500) { int size = AlarmClock.parsePacket(); if (size >= AlarmClockPacketSize) { Serial.println("Alarm seems to be imminent."); AlarmClock.read(AlarmBuffer, AlarmClockPacketSize); alarm_goal = (unsigned long) AlarmBuffer; return 1; } } Serial.println("No Alarm present!"); return 0; } /*-------- NTP code ----------*/ const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { while (Udp.parsePacket() > 0); // discard any previously received packets //Serial.println("Transmit NTP Request"); sendNTPpacket(timeServer); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long) packetBuffer[40] << 24; secsSince1900 |= (unsigned long) packetBuffer[41] << 16; secsSince1900 |= (unsigned long) packetBuffer[42] << 8; secsSince1900 |= (unsigned long) packetBuffer[43]; return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } //Serial.println("No NTP Response :-("); return 0; // return 0 if unable to get the time } // send an NTP request to the time server at the given address void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }