// // FILE: ClockServer.cpp // PURPOSE: UDP broadcast sender for fastclock (FREMO clock) // // #include "ClockServer.h" #include #include #include #ifdef ESP8266 extern "C" { #include "user_interface.h" } #endif const char * const PROGMEM clockServerConfig[] = { "clockServerName:string:DefaultClock", "waitBeforeStartingClockServer_ms:int:5000", "timeToCollectOtherClocks_ms:int:20000", "autoActivateAfter_ms:int:0", "sendClockUpdateEvery_ms:int:2000", "ipMulticast:string:239.50.50.20", "clientListenPort:int:2000", }; static WiFiUDP udp; #define CLOCK_PACKET_SIZE 1024 static byte packetBuffer[CLOCK_PACKET_SIZE+1]; //buffer to hold outgoing packets String ClockServer::name{""}; IPAddress ClockServer::ipMulticast; //.fromString("239.50.50.20"); int ClockServer::clientListenPort{2000}; String ClockServer::text{""}; boolean ClockServer::active{false}; float ClockServer::clockSpeed{1.0}; String ClockServer::clockString{""}; String ClockServer::weekdayName{""}; int ClockServer::weekday{0}; int ClockServer::clockHours{0}; int ClockServer::clockMinutes{0}; int ClockServer::clockSeconds{0}; ClockServerStatus ClockServer::serverStatus = ClockServerStatus::ServerNotStarted; uint32_t ClockServer::nextServerStatusTime; int ClockServer::sendClockUpdateEvery_ms; int ClockServer::autoActivateAfter_ms; void ClockServer::begin() { config.loadFile("clockserver.cfg", clockServerConfig, sizeof(clockServerConfig)/sizeof(clockServerConfig[0])); name = config.getString("clockServerName"); ipMulticast.fromString(config.getString("ipMulticast")); clientListenPort = config.getInt("clientListenPort"); sendClockUpdateEvery_ms = config.getInt("sendClockUpdateEvery_ms"); autoActivateAfter_ms = config.getInt("autoActivateAfter_ms"); nextServerStatusTime = millis() + config.getInt("waitBeforeStartingClockServer_ms"); logHeap(); } void ClockServer::clockTick() { if (ClockServer::active == false) return; //Debug::outln(F("> Tick")); ClockServer::clockSeconds++; if (ClockServer::clockSeconds >= 60) { ClockServer::clockSeconds = 0; ClockServer::clockMinutes++; if (ClockServer::clockMinutes >= 60) { ClockServer::clockMinutes = 0; ClockServer::clockHours++; if (ClockServer::clockHours >= 24) { ClockServer::clockHours = 0; weekday++; if (weekday >= 7) { weekday = 0; } } } } } static void clockTick() { ClockServer::clockTick(); } void ClockServer::activateClock() { active = true; if (clockSpeed < 0.1) { clockSpeed = 0.1; debug.outln(F("ERROR: clockSpeed was too small, set to 0.1 which still is very small")); } clockTrigger.attach_ms(1000.0 / clockSpeed, clockTick); logHeap(); } void ClockServer::deactivateClock() { active = false; clockTrigger.detach(); logHeap(); } void ClockServer::setSpeed(float newSpeed) { clockSpeed = newSpeed; if (active) { deactivateClock(); activateClock(); } logHeap(); } void ClockServer::sendFastclockMessage() { IPAddress ip = WiFi.localIP(); static String nl = String("\r\n"); String msg = String("fastclock") + nl + "version=2" + nl; msg += "name=" + name + nl; msg += "ip-address=" + ip.toString() + nl; msg += "ip-port=" + String(clientListenPort) + nl; msg += "text=" + text + nl; msg += "clocktype=fastclock" + nl; msg += "active=" + (active?String("yes"):String("no")) + nl; msg += "speed=" + String(clockSpeed) + nl; msg += "clock=" + String(clockHours) + ":" + String(clockMinutes) + ":" + String(clockSeconds) + nl; msg += "weekday=" + String(weekday) + nl; uint8_t result = udp.beginPacketMulticast(ipMulticast, clientListenPort, ip); debug.outln(result?"udp.begin":"ERROR on udp.begin"); size_t sentBytes = udp.write(msg.c_str(),msg.length()); debug.outln(String(sentBytes) + " bytes sent"); //,sizeof(msg)); result = udp.endPacket(); debug.outln(result?"send was OK":"send FAILED"); debug.out(F("*** ")); debug.outln(msg); delay(1); } void ClockServer::loop() { static String *knownClocks; switch (serverStatus) { case ClockServerStatus::ServerNotStarted: if (millis() > nextServerStatusTime) { serverStatus = ClockServerStatus::CollectingExistingClocks; nextServerStatusTime = millis() + config.getInt("timeToCollectOtherClocks_ms"); if (!udp.beginMulticast(WiFi.localIP(), ipMulticast, clientListenPort)) { debug.outln(F("ERROR: failed to begin UDP")); debug.out(F("local IP=")); debug.out(WiFi.localIP().toString()); debug.out(F(", multicast IP=")); debug.out(ipMulticast.toString()); debug.out(F(", Port=")); debug.outln(clientListenPort); } logHeap(); } break; case ClockServerStatus::CollectingExistingClocks: // do nothing, we wait for the client to collect the clocks for us if (millis() > nextServerStatusTime) { serverStatus = ClockServerStatus::StartingServer; nextServerStatusTime = millis()-1; // be sure, this will go through the next section on next loop() call logHeap(); } else { int length = udp.parsePacket(); if (length > 0) { // debug.out(F("ClockServer received: ")); debug.out(length); debug.outln(F(" bytes.")); udp.read(packetBuffer, CLOCK_PACKET_SIZE); packetBuffer[length] = '\0'; // interpret Clock Message // debug.out(F("> ")); debug.outln( (char *) packetBuffer); String msg = String((char *) packetBuffer); if (!msg.startsWith("fastclock\r\n")) { debug.out(F("ERROR: This is not a fastclock message! Got message=")); debug.outln(msg.substring(0,30)); } msg = msg.substring(11); if (!msg.startsWith("version=2\r\n")) { debug.out(F("WARNING: Version of fastclock not supported! Got ")); debug.outln(msg.substring(0,10)); } msg = msg.substring(msg.indexOf('\n')+1); String checkName{""}; if (msg.startsWith("name=")) { checkName = msg.substring(msg.indexOf('=')+1, msg.indexOf('\r')); fastclockScanner.addClock(checkName); } else { debug.outln(F("ERROR: Clock Message Format invalid! Expected name field.")); return; } msg = String(""); // release string } } break; case ClockServerStatus::StartingServer: // debug: output the clocks seen during collectingExistingClocks time knownClocks = fastclockScanner.getKnownClocks(); debug.outln(F("--- Following Clocks have been seen during bootup: ---")); for (int i=0; i 0) { nextServerStatusTime = millis() + autoActivateAfter_ms; serverStatus = ClockServerStatus::WaitForAutoActivate; } else { serverStatus = ClockServerStatus::ServerStarted; nextServerStatusTime = millis() + sendClockUpdateEvery_ms; } break; case ClockServerStatus::WaitForAutoActivate: if (millis() > nextServerStatusTime) { serverStatus = ClockServerStatus::ServerStarted; activateClock(); logHeap(); } break; case ClockServerStatus::ServerStarted: if (millis() > nextServerStatusTime) { if (active) sendFastclockMessage(); nextServerStatusTime = millis() + sendClockUpdateEvery_ms; logHeap(); } break; default: // this is an error case debug.outln(F("ERROR: Unknown server state in ClockServer.cpp. Resetting to ServerNotStarted.")); serverStatus = ClockServerStatus::ServerNotStarted; break; } }