Initial commit

This commit is contained in:
2018-01-24 17:51:07 +01:00
commit 1d74a399b5
341 changed files with 28906 additions and 0 deletions

228
doc/ClockServer_cpp.txt Normal file
View File

@@ -0,0 +1,228 @@
//
// FILE: ClockServer.cpp
// PURPOSE: UDP broadcast sender for fastclock (FREMO clock)
//
//
#include "ClockServer.h"
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <DjFastclockScanner.h>
#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<fastclockScanner.getNumberOfKnownClocks(); ++i) {
debug.outln(knownClocks[i]);
}
debug.outln(F("--- end of list of known clocks"));
delay(10);
if (autoActivateAfter_ms > 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;
}
}

79
doc/ClockServer_h.txt Normal file
View File

@@ -0,0 +1,79 @@
//
// FILE: ClockServer.h
// VERSION: 0.1
// PURPOSE: FREMO Clock Server
//
//
#ifndef _ClockServerLoaded
#define _ClockServerLoaded
#include <Arduino.h>
#include <DjDebug.h>
#include <DjConfig.h>
#include <DjFastclockScanner.h>
#include <Ticker.h>
#include <WiFiUDP.h>
#define MAX_CLOCK_CHANGE_CALLBACKS 5
typedef void (*ClockChangeCallback)(int h, int m, int s);
enum class ClockServerStatus {
ServerNotStarted,
CollectingExistingClocks,
StartingServer,
WaitForAutoActivate,
ServerStarted
};
class ClockServer
{
public:
ClockServer(Debug& _debug, Config& _config):debug(_debug), config(_config), fastclockScanner(_debug) {};
void activateClock();
void deactivateClock();
void setSpeed(float newSpeed);
void begin();
void loop();
static void clockTick();
static void setText(String newText) { text = String(newText); }
void sendFastclockMessage();
static String const getClockString() {
String output = String(clockHours) + ":" + String(clockMinutes) + ":" + String(clockSeconds);
return output;
}
static int const getHours() { return clockHours; }
static int const getMinutes() { return clockMinutes; }
static int const getSeconds() { return clockSeconds; }
static int const getWeekday() { return weekday; }
static String const getName() { return name; }
static float const getSpeed() { return clockSpeed; }
static String const getText() { return text; }
static boolean const isActive() { return active; }
private:
Debug& debug;
Config& config;
FastclockScanner fastclockScanner;
Ticker clockTrigger;
static float clockSpeed;
static String name;
static String text;
static boolean active;
static String clockString;
static String weekdayName;
static int clockHours;
static int clockMinutes;
static int clockSeconds;
static int weekday;
static IPAddress ipMulticast;
static int clientListenPort;
static ClockServerStatus serverStatus;
static uint32_t nextServerStatusTime;
static int sendClockUpdateEvery_ms;
static int autoActivateAfter_ms;
};
#endif

117
doc/WebServer_cpp.txt Normal file
View File

@@ -0,0 +1,117 @@
//
// FILE: WebServer.cpp
// PURPOSE: Web Server offering online configuration changes and editing
//
//
#include "WebServer.h"
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#include <DjConfig.h>
#include <DjBattery.h>
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif
const char * const PROGMEM webConfig[] = {"showConfig:boolean:true", "xxx:int:4", "yyy:int:64"};
static Battery battery;
static size_t lastHeap = 0;
void WebServer::handleBattery() {
int value = battery.getPercentage();
String output = String(F("{\"battery\":")) + value + F("}");
server.send(200, contentType_textJson, output);
logHeap();
}
void WebServer::handleGetClockStatus() {
String output = String(F("{\"result\":\"ok\","));
output += "\"clockname\":\"" + clockServer.getName() + "\", \"time\":\"" + clockServer.getClockString() + "\"";
output += "}";
server.send(200, contentType_textJson, output);
logHeap();
}
void WebServer::handleClockActivate() {
clockServer.activateClock();
String output = String(F("{\"result\":\"ok\"}"));
server.send(200, contentType_textJson, output);
logHeap();
}
void WebServer::handleClockDeactivate() {
clockServer.deactivateClock();
String output = String(F("{\"result\":\"ok\"}"));
server.send(200, contentType_textJson, output);
logHeap();
}
void WebServer::handleClockStatus() {
String json = "{";
json += "\"heap\":" + String(ESP.getFreeHeap());
json += ",\"battery\":" + String(battery.getPercentage());
json += ",\"clockserver_name\":\"" + clockServer.getName() + "\"";
json += ",\"clockserver_clock\":\"" + clockServer.getClockString() + "\"";
json += ",\"clockserver_hours\":\"" + String(clockServer.getHours()) + "\"";
json += ",\"clockserver_minutes\":\"" + String(clockServer.getMinutes()) + "\"";
json += ",\"clockserver_seconds\":\"" + String(clockServer.getSeconds()) + "\"";
json += ",\"clockserver_speed\":\"" + String(clockServer.getSpeed(),1) + "\"";
json += ",\"clockserver_text\":\"" + clockServer.getText() + "\"";
json += ",\"clockserver_active\":\"" + (clockServer.isActive()? String("true"):String("false")) + "\"";
json += "}";
server.send(200, contentType_textJson, json);
json = String();
}
void WebServer::begin() {
GenericWebServer::begin();
config.begin("web.cfg", webConfig, sizeof(webConfig)/sizeof(webConfig[0]));
logHeap();
server.on("/all", HTTP_GET, [this](){
String json = "{";
json += "\"heap\":" + String(ESP.getFreeHeap());
json += ",\"battery\":" + String(battery.getPercentage());
json += ",\"clockserver_name\":\"" + clockServer.getName() + "\"";
json += ",\"clockserver_clock\":\"" + clockServer.getClockString() + "\"";
json += ",\"clockserver_hours\":\"" + String(clockServer.getHours()) + "\"";
json += ",\"clockserver_minutes\":\"" + String(clockServer.getMinutes()) + "\"";
json += ",\"clockserver_seconds\":\"" + String(clockServer.getSeconds()) + "\"";
json += ",\"clockserver_speed\":\"" + String(clockServer.getSpeed(),1) + "\"";
json += ",\"clockserver_text\":\"" + clockServer.getText() + "\"";
json += ",\"clockserver_active\":\"" + (clockServer.isActive()? String("true"):String("false")) + "\"";
json += "}";
debug.outln(json);
server.send(200, contentType_textJson, json);
json = String();
});
server.on("/clock/start", HTTP_GET, [this](){ handleClockActivate(); });
server.on("/clock/stop", HTTP_GET, [this](){ handleClockDeactivate(); });
server.on("/clock/status", HTTP_GET, [this](){ handleClockStatus(); });
server.on("/battery", HTTP_GET, [this](){ handleBattery();});
server.on("/inline", HTTP_GET, []() {
server.send(200, contentType_textPlain, F("this works as well"));
} );
server.begin();
debug.outln(F("HTTP server started"), DEBUG_MIN_INFO);
logHeap();
}
void WebServer::loop() {
GenericWebServer::loop();
if (lastHeap > 0)
{
debug.out(lastHeap); debug.outln(F("< heap on recent call"));
lastHeap = 0;
}
}

34
doc/WebServer_h.txt Normal file
View File

@@ -0,0 +1,34 @@
//
// FILE: WebServer.h
// VERSION: 0.1
// PURPOSE: Web Server offering online configuration changes and editing
//
//
#ifndef webServerLoaded
#define webServerLoaded true
#include <Arduino.h>
#include <DjDebug.h>
#include <DjGenericWebServer.h>
#include "ClockServer.h"
class WebServer: public GenericWebServer
{
public:
WebServer(Debug& _debug, Config& _config, ClockServer& _clockServer) :
GenericWebServer(_debug, _config), clockServer(_clockServer)
{ logHeap(); };
void loop();
void begin();
private:
ClockServer& clockServer;
void handleBattery();
void handleGetClockStatus();
void handleClockActivate();
void handleClockDeactivate();
void handleClockStatus();
};
#endif

85
doc/clockserver.ino Normal file
View File

@@ -0,0 +1,85 @@
#include <DjConfig.h>
#include <DjDebug.h>
#include <DjSimpleFS.h>
#include <DjWiFiConnection.h>
#include <DjWebUpdate.h>
#include <DjBattery.h>
#include "WebServer.h"
#include "ClockServer.h"
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif
static const char * const PROGMEM appConfig[] = {
"enable_battery:boolean:false",
"enable_webupdate:boolean:false",
"enable_fastclock_server:boolean:true",
};
static Debug debug(115200);
static Config config(debug);
static SimpleFS simpleFS(debug);
static Battery battery;
static WiFiConnection wifiConnection(debug, config);
static ClockServer clockServer(debug, config);
static WebServer webServer(debug, config, clockServer);
static WebUpdate webUpdate(debug, config, simpleFS, "clockserver");
// ***** Configuration Switches *****
static boolean enableBattery = false;
static boolean enableFastclockServer = false;
static boolean enableWebupdate = false;
void setup() {
debug.setOutputUptoLevel(DEBUG_MAX_INFO);
debug.outln(F("### Setup"));
// ********** Initialize file system **********
simpleFS.begin();
delay(100);
debug.outln(F("Getting app config"));
config.begin("app.cfg", appConfig, sizeof(appConfig)/sizeof(appConfig[0]));
enableFastclockServer = config.getBoolean("enable_fastclock_server");
enableBattery = config.getBoolean("enable_battery");
enableWebupdate = true; //config.getBoolean("enable_webupdate");
// ********** Setting up networking **********
wifiConnection.setup();
wifiConnection.begin();
// *********** Starting web server **********
webServer.begin();
delay(1000);
// *********** Check for Updates **********
if (enableWebupdate) {
webUpdate.begin();
}
// *********** Start Clock Server **********
if (enableFastclockServer) {
clockServer.begin();
}
}
int lastBatteryReadOn = 0;
void loop() {
webServer.loop();
if (enableBattery && (millis() - lastBatteryReadOn > 30000)) {
lastBatteryReadOn = millis();
debug.out("Battery="); debug.out(battery.getPercentage()); debug.outln(" %");
logHeap();
}
if (enableFastclockServer) clockServer.loop();
if (enableWebupdate) webUpdate.loop();
simpleFS.loop();
}

121
doc/display_c.txt Normal file
View File

@@ -0,0 +1,121 @@
// display.c
/*
* Copyright (c) 2014-2017 Cesanta Software Limited
* All rights reserved
*/
#include <Arduino.h>
#include <Adafruit_SSD1306.h>
#include "common/cs_dbg.h"
#include "fw/src/mgos_app.h"
#include "fw/src/mgos_timers.h"
Adafruit_SSD1306 *d1 = nullptr, *d2 = nullptr;
static void timer_cb(void *arg);
void setup(void) {
// I2C
d1 = new Adafruit_SSD1306(4 /* RST GPIO */, Adafruit_SSD1306::RES_128_64);
if (d1 != nullptr) {
d1->begin(SSD1306_SWITCHCAPVCC, 0x3D, true /* reset */);
d1->display();
}
// SPI.
// On ESP8266 SPI and I2C cannot be enabled at the same time by default
// because their pins overlap.
// You will need to disable I2C and enable SPI:
// mos config-set i2c.enable=false spi.enable=true
d2 = new Adafruit_SSD1306(21 /* DC */, 5 /* RST */, 17 /* CS */,
Adafruit_SSD1306::RES_128_32);
if (d2 != nullptr) {
d2->begin(SSD1306_SWITCHCAPVCC, 0 /* unused */, true /* reset */);
d2->display();
}
mgos_set_timer(1000 /* ms */, true /* repeat */, timer_cb, NULL);
}
static void show_num(Adafruit_SSD1306 *d, const char *s, int i) {
d->clearDisplay();
d->setTextSize(2);
d->setTextColor(WHITE);
d->setCursor(d->width() / 4, d->height() / 4);
d->printf("%s%d", s, i);
d->display();
}
static void timer_cb(void *arg) {
static int i = 0, j = 0;
if (d1 != nullptr) show_num(d1, "i = ", i);
if (d2 != nullptr) show_num(d2, "j = ", j);
LOG(LL_INFO, ("i = %d, j = %d", i, j));
i++;
j++;
(void) arg;
}
#if 0
void loop(void) {
/* For now, do not use delay() inside loop, use timers instead. */
}
#endif
#if 0
#include <stdio.h>
#include "common/cs_dbg.h"
#include "fw/src/mgos_app.h"
#include "fw/src/mgos_i2c.h"
#include "fw/src/mgos_mongoose.h"
#include "fw/src/mgos_utils.h"
static struct mgos_i2c *uOLED_i2c;
static uint8_t from_hex(const char *s) {
#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W')
int a = tolower(*(const unsigned char *) s);
int b = tolower(*(const unsigned char *) (s + 1));
return (HEXTOI(a) << 4) | HEXTOI(b);
}
enum mgos_app_init_result uOLED_i2cSetup() {
struct sys_config_i2c *cfg;
uOLED_i2c = mgos_i2c_create(const struct sys_config_i2c *cfg);
return MGOS_APP_INIT_SUCCESS;
}
void uOLED_send_hexstring(struct mg_str *s) {
bool ret = false;
LOG(LL_INFO, ("Got: [%.*s]", (int) s->len, s->p));
if (s->len >= 2) {
int j = 0;
// struct mgos_i2c *i2c = mgos_i2c_get_global();
for (size_t i = 0; i < s->len; i += 2, j++) {
((uint8_t *) s->p)[j] = from_hex(s->p + i);
}
ret = mgos_i2c_write(uOLED_i2c, s->p[0], s->p + 1, j, true /* stop */);
}
}
/** \brief Write a byte over I2C
Write a byte to I2C device _address_. The DC byte determines whether
the data being sent is a command or display data. Use either I2C_COMMAND
or I2C_DATA in that parameter. The data byte can be any 8-bit value.
**/
void uOLED_i2cWrite(byte address, byte dc, byte data)
{
Wire.beginTransmission(address);
Wire.write(dc); // If data dc = 0, if command dc = 0x40
Wire.write(data);
Wire.endTransmission();
}
#endif

44
doc/fastclock-protocol.md Normal file
View File

@@ -0,0 +1,44 @@
# Fastclock Protocol
## Introduction / General Structure
The fastclock protocol is a text message containing one to many lines of UTF-8 encoded text, each separated by an "end of line".
Following rules apply:
* End of line: Might be \r (CR, 0x08), \n (NL, 0x0A) or a combination of \r and \n in any order.
* The original definition mentioned a "level 1" protocol, which we do not support anymore.
* The first line of a message may contain an arbitrary text or content. This has been used previously for a so called "level 1" protocol. This line is ignored and thus may contain some description, naming or even the level 1 protocol, if someone still wants to use it. Nevertheless, for our use cases, we want to have more detailed content and thus do not use this protocol type anymore.
* The next lines upto the end of the message contain "key=value" pairs, where the keys must be one of:
version, name, text, clock, active, speed, weekday, ip-port, ip-address, clocktype
* The order of the keys is not predefined, thus the server may decide a preferred order.
## Protocol element keys
The details of each key are:
| Key | Meaning | Mandatory / Optional | Allowed Values |
|-----------|---------|----------------------|-----------------|
| clocktype | Type of clock information transmitted | mandatory | fastclock, realclock |
| name | Name of clock information transmitted. Multiple clocks may be transmitted by the same server or by multiple server. But each clock must have its own unique name. | mandatory | UTF-8 character string, max. length 20 characters |
| version | Protocol version. Currently, we use version 2. | mandatory | integer, actual version is 2 |
| text | UTF-8 encoded text message, that might be displayed by clock clients as far as they are able to display additional texts. | mandatory, is empty, if no text is to be displayed | UTF-8 encoded text messages without line breaks; maximum length = 255 characters |
| clock | The current time of the clock. | mandatory | Text string: H:M:S representing hours, minutes and seconds; may use leading zeroes, but does not need to. Thus "12:3:5" is valid as well as "12:03:05" |
| active | Set to "yes", if the clock is running, to "no", if the clock is stopped. | mandatory | string "yes" or "no" without double quotes |
| speed | The given clock speed as a floating number. This is the factor, the fastclock is faster than real time. A Value of "4.5" means a clock speed of "1:4.5" for "real time : fastclock" | mandatory | Float value with "." as a decimal point |
| weekday | Day of week, where 0=no day of week is given, 1=Monday to 7=Sunday | optional | integer value 0-7 |
| ip-address | IP-Address of clock server; needs only to be set, if a clock server control interface exists | optional | String containg ip-Address like 192.168.0.55 or a hostname, that can be resolved by DNS |
| ip-port | Port to be used to control the clock server; needs only to be set, if a clock server control interface exists | optional | positive integer |
| src-protocol | Server remote control protocol, e.g. http for web application, udp for messages, others ... | optional | String containing the protocol name, max. 20 characters |
## Transmission part of the protocol
To transmit the messages, several possibilities exists. Herewith we suggest that servers may choose, which protocols they want to support. It is in their responsibility to keep the different transmission techniques in sync.
| Tranmission Technique | Restrictions | Available clients |
|-|-|-|
| UDP Multicast | Needs a network infrastructure supporting UDP multicasts. The clients must reside in the same network subnet as the server. This is the preferred transmission technique, as it has a very lean bandwidth usage and almost no dependencies between clients and server. | MRClock app [Android](https://play.google.com/store/apps/details?id=com.MRclock), iPhone (iMRclock), [Telechron für iPhone](https://itunes.apple.com/se/app/telechron-fast-clock-client/id907090499?mt=8), [Cafebahn](https://sourceforge.net/p/cafebahn/wiki/Home/), Windows Client |
| Web-Socket | Clients must be able to access the server "as a web server", but this does not need to be in the same network subnet, as long as a route is defined/available. As nothing like a multicast or broadcast exists based on Web-Sockets, the protocol needs to be extended so that clients can register or request current status. | Android watch client (@uwe.lengler) |
| MQTT | Needs an MQTT server, e.g. Apache Mosquitto. | -- none so far -- |
## TN clock integration
FREMO uses a system called RUT to control TN clocks based on a 2-wire connection. For every minute, an impulse is sent with changing polarity. It uses 12V or 24V depending on the types of clocks used.
A client may support such clocks directly or drive a RUT installation. An example for doing so has been published by Dirk Hilberg in Fremo's Hp1 publication 3/2016, pages 4-5.