// // FILE: ClockClient.cpp // PURPOSE: UDP broadcast listener for fastclock (FREMO clock) // // #include #include #include #include "common/platform.h" #include "mgos.h" #include "mgos_app.h" #include "mongoose/mongoose.h" #include "mgos_wifi.h" #include "mgos_net.h" #include "mgos_mongoose.h" #include "mgos_sys_config.h" #include "ClockClient.h" #define CLOCK_PACKET_SIZE 1024 static char packetBuffer[CLOCK_PACKET_SIZE+1]; //buffer to hold incoming and outgoing packets const char * ClockClient::getLastMessage() { return (const char *) packetBuffer; } int ClockClient::protocolVersion{0}; char * ClockClient::listenToName{nullptr}; char * ClockClient::name{nullptr}; char * ClockClient::text{nullptr}; char * ClockClient::clocktype{nullptr}; bool ClockClient::active{false}; bool ClockClient::isFastclock{false}; double ClockClient::speed{1.0}; char * ClockClient::clock{nullptr}; unsigned int ClockClient::weekday{0}; unsigned int ClockClient::clockHours{0}; unsigned int ClockClient::clockMinutes{0}; unsigned int ClockClient::clockSeconds{0}; int ClockClient::numClockChangeCallbacks{0}; char * ClockClient::fastclockIP{nullptr}; unsigned int ClockClient::fastclockPort{0}; unsigned int ClockClient::listenPort{0}; ClockChangeCallback ClockClient::clockChangeCallback[]; static void strFree(char **target) { if (*target != nullptr) { free(*target); *target = nullptr; } } static void strReplace(char **target, const char *newValue) { strFree(target); *target = strdup(newValue); } void ClockClient::addClockChangeCallback(ClockChangeCallback _clockChangeCallback) { if (numClockChangeCallbacks >= MAX_CLOCK_CHANGE_CALLBACKS) { LOG(LL_ERROR, ("ERROR: Too many clock change callbacks registered!")); return; } clockChangeCallback[numClockChangeCallbacks++] = _clockChangeCallback; } ClockClient::ClockClient() { fastclockScanner = FastclockScanner(); } static void multicastMessageHandler(struct mg_connection *nc, int ev, void *p) { struct mbuf *io = &nc->recv_mbuf; (void) p; switch (ev) { case MG_EV_RECV: LOG(LL_INFO, ("Received (%d bytes): '%.*s'", (int) io->len, (int) io->len, io->buf)); ClockClient::interpretClockMessage(io->len, io->buf); mbuf_remove(io, io->len); nc->flags |= MG_F_SEND_AND_CLOSE; break; } } static void on_wifi_change(enum mgos_wifi_status event, void *ud) { (void) ud; switch (event) { case MGOS_WIFI_IP_ACQUIRED: { ip_addr_t host_addr; ip_addr_t group_addr; struct mgos_net_ip_info ip_info; char sta_ip[16]; if (mgos_net_get_ip_info(MGOS_NET_IF_TYPE_WIFI, MGOS_NET_IF_WIFI_STA, &ip_info)) { mgos_net_ip_to_str(&ip_info.ip, sta_ip); } host_addr.addr = inet_addr(sta_ip); group_addr.addr = inet_addr(ClockClient::getMulticastIP()); LOG(LL_INFO, ("Joining multicast group %s", ClockClient::getMulticastIP())); if (igmp_joingroup(&host_addr, &group_addr) != ERR_OK) { LOG(LL_INFO, ("udp_join_multigroup failed!")); }; } default: ; } } static int init_multicastListener(struct mg_mgr *mgr, char *multicast, int port) { struct mg_bind_opts bopts; char listener_spec[128]; (void) multicast; snprintf(listener_spec, sizeof(listener_spec), "udp://:%d", port); LOG(LL_INFO, ("Listening on %s", listener_spec)); memset(&bopts, 0, sizeof(bopts)); struct mg_connection *lc = mg_bind_opt(mgr, listener_spec, multicastMessageHandler, bopts); if (lc == NULL) { LOG(LL_ERROR, ("Failed to create listener")); return 0; } return 1; } void ClockClient::begin() { strReplace(&name, mgos_sys_config_get_clock_listenToName()); // WiFi.mode(WIFI_STA); strReplace(&multicast, mgos_sys_config_get_clock_multicast()); listenPort = mgos_sys_config_get_clock_port(); logHeap(); mgos_wifi_add_on_change_cb(on_wifi_change, NULL); if (!init_multicastListener(mgos_get_mgr(), multicast, listenPort)) { LOG(LL_ERROR, ("Starting listener for Fastclock Multicast failed")); } } void ClockClient::setDefaults() { // do not touch listenToName, multicast, listenPort // strFree(&listenToName); // listenPort = 0; // strFree(&multicast); protocolVersion = 0; active = false; isFastclock = false; speed = 0; strReplace(&name, "DefaultClock"); strReplace(&text, ""); weekday = 0; clockHours = 0; clockMinutes = 0; clockSeconds = 0; strFree(&fastclockIP); fastclockPort = 2000; } static const char *weekdayName[] = { "---", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; const char* ClockClient::getClockWeekdayName() { if (weekday > sizeof(weekdayName)) { LOG(LL_ERROR, ("Invalid weekday %d, have no name for this", weekday)); return weekdayName[0]; } return weekdayName[weekday]; } static int movePointerForwardUntilEndOfLine(char **source) { int moved = 0; while (**source != '\r' && **source != '\n' && **source != '\0') { (*source)++; ++moved; } return moved; } #define MAX_LINE_LENGTH 512 static int copyUntilEndOfLine(const char* parameter, const char* source, char** target) { char buffer[MAX_LINE_LENGTH+1]; char* buffer_ptr; int copied=0; buffer_ptr = &buffer[0]; while (*source != '\r' && *source != '\n' && *source != '\0' && copied < MAX_LINE_LENGTH) { *buffer_ptr++ = *source++; copied++; } *buffer_ptr = '\0'; if (copied >= MAX_LINE_LENGTH) { if (*source != '\r' && *source != '\n' && *source != '\0') { LOG(LL_ERROR, ("String to copy is too long for parameter %s (rest: %s) -- ignored!", parameter, source)); while (*source != '\r' && *source != '\n' && *source != '\0') ++source; // skip } } strReplace(target, buffer_ptr); return copied; } static int copyBoolean(const char* parameter, const char* source, bool* target) { if (strncmp(source, "yes", 3) == 0) { *target = true; return 3; } else if (strncmp(source, "no", 2) == 0) { *target = false; return 2; } LOG(LL_ERROR, ("Invalid boolean value found for parameter %s (%s) -- ignored (using default=false)!", parameter, source)); *target = false; return 0; } static int copyInteger(const char *source, unsigned int *result) { char *movedSource = (char *) source; *result = (unsigned int) strtoul(movedSource, &movedSource, 10); movePointerForwardUntilEndOfLine(&movedSource); return movedSource - source; } static int copyDouble(const char *source, double *result) { char *movedSource = (char *) source; *result = (double) strtod(movedSource, &movedSource); movePointerForwardUntilEndOfLine(&movedSource); return movedSource - source; } static int copyClock(const char *source, unsigned int *hours, unsigned int *minutes, unsigned int *seconds) { char *movedSource = (char *) source; *hours = 0; *minutes = 0; *seconds = 0; *hours = strtoul(movedSource, &movedSource, 10); if (*movedSource != ':') { LOG(LL_ERROR, ("Invalid clock value found (%s) -- missing colon after hours!", source)); } else { ++movedSource; *minutes = strtoul(movedSource, &movedSource, 10); if (*movedSource != ':') { LOG(LL_ERROR, ("Invalid clock value found (%s) -- missing colon after minutes!", source)); } else { ++movedSource; *seconds = strtoul(movedSource, &movedSource, 10); } } movePointerForwardUntilEndOfLine(&movedSource); return movedSource - source; } void ClockClient::interpretClockMessage(int len, char* msg) { int copied = 0; setDefaults(); // ignore line 1, as this is for 1st version of clock clients only len -= movePointerForwardUntilEndOfLine(&msg); while (len > 0 && *msg != '\0') { if (msg[0] == '\r' || msg[0] == '\n') { msg++; len--; } else if (strncmp(msg, "version=", 8) == 0) { if (msg[8] == '1') { protocolVersion = 1; } else if (msg[8] == '2') { protocolVersion = 2; } else { LOG(LL_ERROR, ("Invalid version in fastclock message (%c) -- ignored!", msg[8])); } msg += 9; len -= 9; } else if (strncmp(msg, "name=", 5) == 0) { msg += 5; len -= 5; copied = copyUntilEndOfLine("name", msg, &name); fastclockScanner.addClock(name); msg += copied; len -= copied; } else if (strncmp(msg, "text=", 5) == 0) { msg += 5; len -= 5; copied = copyUntilEndOfLine("text", msg, &text); msg += copied; len -= copied; } else if (strncmp(msg, "clock=", 6) == 0) { msg += 6; len -= 6; copied = copyClock(msg, &clockHours, &clockMinutes, &clockSeconds); msg += copied; len -= copied; } else if (strncmp(msg, "active=", 7) == 0) { msg += 7; len -= 7; copied = copyBoolean("active", msg, &active); msg += copied; len -= copied; } else if (strncmp(msg, "speed=", 6) == 0) { msg += 6; len -= 6; copied = copyDouble(msg, &speed); msg += copied; len -= copied; } else if (strncmp(msg, "weekday=", 8) == 0) { msg += 8; len -= 8; copied = copyInteger(msg, &weekday); msg += copied; len -= copied; } else if (strncmp(msg, "ip-port=", 8) == 0) { msg += 8; len -= 8; copied = copyInteger(msg, &fastclockPort); msg += copied; len -= copied; } else if (strncmp(msg, "ip-address=", 11) == 0) { msg += 11; len -= 11; copied = copyUntilEndOfLine("ip-address", msg, &fastclockIP); msg += copied; len -= copied; } else if (strncmp(msg, "clocktype=", 10) == 0) { char * buffer = nullptr; msg += 10; len -= 10; copied = copyUntilEndOfLine("clocktype", msg, &buffer); msg += copied; len -= copied; if (strcmp(buffer, "fastclock") == 0) { isFastclock = true; } else if (strcmp(buffer, "realclock") == 0) { isFastclock = false; } else { LOG(LL_ERROR, ("Invalid value for parameter clocktype in fastclock message (%s) -- using default (fastclock)!", buffer)); } strFree(&buffer); } else { LOG(LL_ERROR, ("Invalid parameter in fastclock message (%s) -- ignored!", msg)); len -= movePointerForwardUntilEndOfLine(&msg); } } if (strcmp(name, listenToName) == 0) { // yes, we follow this clock, so do not ignore // propagate new message for (int i=0; i