fastclockClient/src/ClockClient.cpp

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() {
}