371 lines
10 KiB
C++
371 lines
10 KiB
C++
//
|
|
// FILE: ClockClient.cpp
|
|
// PURPOSE: UDP broadcast listener for fastclock (FREMO clock)
|
|
//
|
|
//
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <lwip/igmp.h>
|
|
|
|
#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<numClockChangeCallbacks; ++i) {
|
|
clockChangeCallback[i](clockHours, clockMinutes, clockSeconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClockClient::loop() {
|
|
}
|