Compare commits

...

6 Commits

8 changed files with 548 additions and 125 deletions

View File

@@ -10,6 +10,49 @@ The tasks of the master clock / controller is:
* inform client clocks about the current time to be displayed * inform client clocks about the current time to be displayed
* support multiple clock systems, as we have multiple arrangements of model railroads at the same location having their own times * support multiple clock systems, as we have multiple arrangements of model railroads at the same location having their own times
# Principals
* all messages are sent as broadcast messages without acknowledge on device level
* all devices listen to all messages and filter out those messages addressed to them
* all messages begin with a message type, to-address, from-address
* addresses are a number in the range of 0..255
* address 0 is reserved for master/controller
* address 255 is reserved for a broadcast to all devices
* if acknowledges are necessary/wanted, then separate messages need to be defined
* after receiving a message that needs an acknowledge, no device is allowed to send a message for a TBD. period of time except the requested device, which may send an acknowledge
* after sending a message, the same device must wait a TBD. period of time before sending another message
# Protocol
## Message structure
Type | To | From | Message
-- | -- | -- | --
P,p,A,U,a,C,T,H,E | 0..255 | 0..255 | msg type dependent
## Message definitions
Nachricht | msgType | Attribute | Status | Description
--------- | ------- | --------- | ------ | -----------
ClockAdvertisement | c | FastclockName (8)<br>ClockChannel | done | Using ADVERTISEMENT_CHANNEL (channel 1) to<br>announce the clock's existence.
PairingOffering | P | FastclockName (8)<br>ClockChannel | done | Announce clock master and<br>allow immediate reply by clients<br>(master waits with further messages<br>for at least 20ms).
PairingRequest | p | ClientName(10) | done | Message from client as reply<br>to PairingOffering.
PairingAck | A | FastclockName (8)<br>ClientName (10)<br>Network-Address | done | Master acknowledges a PairingRequest.
UnpairRequest | U | tbd. | --- | Remove a client from master's client list,<br>initiated by client.
UnpairAck | a | tbd. | --- | Acknowledge an UnpairRequest,<br>sent by master.
FastClock | C | FastclockName (8)<br>Hour<br>Minute<br>Second<br>ClockSpeed<br>Weekday<br>stopped | done | Current fastclock time.
TextMessage | T | FastclockName (8)<br>Message | done | Additional text sent by master may be displayed<br>by client, if possible.
Hello | H | tbd. | --- | Test message sent by master<br>to receive signal quality data.
HelloEcho | E | RSSI<br>canRouteToMaster<br>FastclockName(8) | --- | Hello reply with signal quality data<br>and the ability to route (if a client cannot<br>receive a master but routing clients, this<br>is the chance to get access;<br>routing not clearly defined so far,<br>it's just an idea)
Quiet | Q | quietTime_ms (1) | --- | Master sends this message and asks for being<br>quiet for some time, as the master will switch<br>off (e.g. to the advertisement channel to send<br>an announcement).
The communication is done on two (!) channels. One channel is used as an advertisement channel, where all clocks shall send advertisement messages from time to time to inform interested clients, on which channel they can be found. No dialogues are allowed between clock masters and clients on the advertisement channel. The advertisement channel is currently defined as channel \#1.
Each clock master may use an own channel to avoid collisions, although it is possible, that multiple clock systems communicate on the same channel. The clock's channel is announced on the advertisement channel. The master switches back to the clock's channel immediately after sending the advertisement. The same advertised message is sent on the clock's channel as well from time to time as an invitation to register as a client at the master ("Pairing"). This is an optional step and only used to be able to show at the master's display, which clients are displaying the fastclock informations.
As all clock messages from the master are sent as broadcasts, each client can read/follow the clock.
## Links / References ## Links / References
- Digitrax Loconet PE: http://www.digitrax.com/static/apps/cms/media/documents/loconet/loconetpersonaledition.pdf - Digitrax Loconet PE: http://www.digitrax.com/static/apps/cms/media/documents/loconet/loconetpersonaledition.pdf

View File

@@ -1,18 +0,0 @@
#ifndef clockMsg_h_included
#define clockMsg_h_included
#include <Arduino.h>
struct clockMsg_s {
uint8_t msgType;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t speed; // msPerModelSecond / 4 --> 0..250
};
#define msgType_Clock 'c' /* clock update, sent by master using broadcast */
#define msgType_ReqReg 'R' /* Request Registration, sent by Master using broadcast; ask clients to register */
#define msgType_Reg 'r' /* Registration message, sent by Client using 1:1 message to master */
#endif

View File

@@ -13,6 +13,13 @@
#define NETWORK_ADDRESS_MASTER_SEND "1ClkM" #define NETWORK_ADDRESS_MASTER_SEND "1ClkM"
#define NETWORK_ADDRESS_MASTER_RECEIVE "2ClkM" #define NETWORK_ADDRESS_MASTER_RECEIVE "2ClkM"
#define DEFAULT_TIME_BETWEEN_CLOCK_ADVERTISEMENTS_ms 60000
#define DEFAULT_TIME_BETWEEN_PAIRING_OFFERINGS_ms 33000
#define DEFAULT_TIME_FOR_CLIENTS_TO_RESPOND_ms 20
#define MIN_TIME_BETWEEN_TRANSMISSIONS_ms 2
#define ADVERTISEMENT_CHANNEL 1
#define DEFAULT_CLOCK_CHANNEL 11
// relay based clock control behaviour // relay based clock control behaviour
#define DEFAULT_HOLD_RELAY_MS 150 #define DEFAULT_HOLD_RELAY_MS 150
#define DEFAULT_MIN_RELAY_OFF_TIME_MS 80 #define DEFAULT_MIN_RELAY_OFF_TIME_MS 80
@@ -21,5 +28,6 @@
// field sizes // field sizes
#define MAX_CLOCK_NAME_LEN 8 #define MAX_CLOCK_NAME_LEN 8
#define MAX_CLIENT_NAME_LEN 10 #define MAX_CLIENT_NAME_LEN 10
#define MAX_TEXT_MESSAGE_LEN 10
#endif #endif

View File

@@ -62,15 +62,74 @@ void Display::begin(void) {
setLargeTextSize(); setLargeTextSize();
u8g2.drawStr(0,16,"Booting FastClock"); u8g2.drawStr(0,16,"Booting FastClock");
u8g2.sendBuffer(); u8g2.sendBuffer();
delay(200); delay(400);
//addLogMessage("Display initialized"); //addLogMessage("Display initialized");
} }
#define BOOT_CLOCK_WIDTH 16
#define BOOT_CLOCK_PIXEL_WIDTH 2
#define BOOT_CLOCK_PIXELS_PER_SIDE (BOOT_CLOCK_WIDTH / BOOT_CLOCK_PIXEL_WIDTH - 1)
#define BOOT_CLOCK_PIXELS (BOOT_CLOCK_PIXELS_PER_SIDE * 4)
#define BOOT_CLOCK_FORWARD_AFTER_ms 200
// Example: WIDTH=16, PIXEL_WIDTH = 4 --> 3 pixels per side (4 seen, but one would be counted double)
// Result: Pixels a / b / c / d
// a1 a2 a3 b1
// d3 b2
// d2 b3
// d1 c3 c2 c1
//
void Display::showBootClock(void) {
static unsigned long lastClockUpdate_ts = 0;
static int tick = 0;
if (millis() - lastClockUpdate_ts >= BOOT_CLOCK_FORWARD_AFTER_ms) {
++tick;
if (tick >= BOOT_CLOCK_PIXELS) {
tick = 0;
/*
u8g2.setDrawColor(0);
u8g2.drawBox(u8g2.getDisplayWidth()-BOOT_CLOCK_WIDTH, 0, BOOT_CLOCK_WIDTH, BOOT_CLOCK_WIDTH);
u8g2.setDrawColor(1);
*/
}
}
u8g2.setDrawColor(2); // XOR
// draw appropriate pixels
int x=0, y=0, side, pixelOnSide; // side = 0, 1, 2, 3 for a, b, c, d
side = tick / BOOT_CLOCK_PIXELS_PER_SIDE;
pixelOnSide = tick % BOOT_CLOCK_PIXELS_PER_SIDE;
switch (side) {
case 0: // top
x = u8g2.getDisplayWidth() - BOOT_CLOCK_WIDTH + pixelOnSide * BOOT_CLOCK_PIXEL_WIDTH;
y = 0;
break;
case 1: // right
x = u8g2.getDisplayWidth() - BOOT_CLOCK_PIXEL_WIDTH;
y = pixelOnSide * BOOT_CLOCK_PIXEL_WIDTH;
break;
case 2: // bottom
x = u8g2.getDisplayWidth() - (1 + pixelOnSide) * BOOT_CLOCK_PIXEL_WIDTH;
y = BOOT_CLOCK_WIDTH - BOOT_CLOCK_PIXEL_WIDTH;
break;
case 3: // left
x = u8g2.getDisplayWidth() - BOOT_CLOCK_WIDTH;
y = BOOT_CLOCK_WIDTH - (1 + pixelOnSide) * BOOT_CLOCK_PIXEL_WIDTH;
}
u8g2.drawBox(x, y, BOOT_CLOCK_PIXEL_WIDTH, BOOT_CLOCK_PIXEL_WIDTH);
u8g2.setDrawColor(1);
u8g2.drawCircle(u8g2.getDisplayWidth()-BOOT_CLOCK_WIDTH/2, BOOT_CLOCK_WIDTH/2, BOOT_CLOCK_PIXEL_WIDTH, U8G2_DRAW_ALL);
u8g2.setDrawColor(1);
u8g2.sendBuffer();
}
bool Display::showBootSequenceFinished(unsigned int ms_per_step) { bool Display::showBootSequenceFinished(unsigned int ms_per_step) {
static int step = 0; static int step = 0;
static unsigned long lastStep_ms = 0; static unsigned long lastStep_ms = 0;
if (step > 4) return true; // all is done; the value "4" corresponds to the number of steps in following case statement! if (step > 4) return true; // all is done; the value "4" corresponds to the number of steps in following case statement!
showBootClock();
if (ms_per_step > millis() - lastStep_ms) return false; // do nothing, if last step execution is not long enough ago if (ms_per_step > millis() - lastStep_ms) return false; // do nothing, if last step execution is not long enough ago
Serial.print("showBootSequence: step="); Serial.println(step); Serial.print("showBootSequence: step="); Serial.println(step);
lastStep_ms = millis(); lastStep_ms = millis();
@@ -80,20 +139,12 @@ bool Display::showBootSequenceFinished(unsigned int ms_per_step) {
currentScreen = BootSequenceScreen; currentScreen = BootSequenceScreen;
break; break;
case 1: case 1:
u8g2.drawPixel(100, 10);
u8g2.drawPixel(102, 12);
u8g2.drawPixel(104, 14);
u8g2.drawPixel(106, 16);
u8g2.sendBuffer();
break; break;
case 2: case 2:
u8g2.drawLine(100, u8g2.getDisplayHeight()-1, u8g2.getDisplayWidth()-1, u8g2.getDisplayHeight()/2);
u8g2.drawCircle(u8g2.getDisplayWidth()-20, u8g2.getDisplayHeight()-10, 10, U8G2_DRAW_UPPER_LEFT | U8G2_DRAW_LOWER_RIGHT);
u8g2.sendBuffer();
break; break;
case 3: case 3:
u8g2.drawXBMP(60, 0, 16, 16, logo16_glcd_bmp); //u8g2.drawXBMP(60, 0, 16, 16, logo16_glcd_bmp);
u8g2.sendBuffer(); //u8g2.sendBuffer();
break; break;
case 4: case 4:
break; // empty step to be sure, that last step is displayed long enough break; // empty step to be sure, that last step is displayed long enough
@@ -141,7 +192,6 @@ void Display::showDashboard(void) {
lastBlinkChange_ms = lastDisplayUpdate_ms; lastBlinkChange_ms = lastDisplayUpdate_ms;
blinkOnCycle = !blinkOnCycle; blinkOnCycle = !blinkOnCycle;
} }
u8g2.clearBuffer(); u8g2.clearBuffer();
u8g2.setDrawColor(1); u8g2.setDrawColor(1);

View File

@@ -4,7 +4,7 @@
#include "config.h" #include "config.h"
// avoid flickering of the display: // avoid flickering of the display:
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 200 #define TIME_BETWEEN_DISPLAY_UPDATES_ms 300
#define BLINK_ON_OFF_TIME_ms 1000 #define BLINK_ON_OFF_TIME_ms 1000
@@ -28,7 +28,7 @@ public:
clockHour = hour; clockHour = hour;
clockMinute = minute; clockMinute = minute;
Serial.print("display: new time "); Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute); Serial.print("display: new time "); Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
} };
//void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); }; //void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); };
void setClockWeekday(const char *weekday) { strncpy(clockWeekday, weekday, MAX_CLOCK_WEEKDAY_LEN); }; void setClockWeekday(const char *weekday) { strncpy(clockWeekday, weekday, MAX_CLOCK_WEEKDAY_LEN); };
void setClockHalted(bool halted) { clockHalted = halted; }; void setClockHalted(bool halted) { clockHalted = halted; };
@@ -58,5 +58,6 @@ private:
int getTextCharsPerLine(void); int getTextCharsPerLine(void);
void setNormalTextSize(void); void setNormalTextSize(void);
void setLargeTextSize(void); void setLargeTextSize(void);
void showBootClock(void);
}; };
#endif #endif

View File

@@ -16,6 +16,7 @@
#include "radio.h" #include "radio.h"
#define AUTOSTART_CLOCK_AFTER_ms 10000 #define AUTOSTART_CLOCK_AFTER_ms 10000
#define POWER_OFF_PIN 0
//define your default values here, if there are different values in config.json, they are overwritten. //define your default values here, if there are different values in config.json, they are overwritten.
//length should be max size + 1 //length should be max size + 1
@@ -27,6 +28,9 @@ char static_ip[16] = "10.0.1.56";
char static_gw[16] = "10.0.1.1"; char static_gw[16] = "10.0.1.1";
char static_sn[16] = "255.255.255.0"; char static_sn[16] = "255.255.255.0";
char clockName[MAX_CLOCK_NAME_LEN+1] = "fastclk";
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
//flag for saving data //flag for saving data
bool shouldSaveConfig = false; bool shouldSaveConfig = false;
@@ -134,9 +138,12 @@ void setupWifiConnection() {
} }
void setup() { void setup() {
// if coming from deep sleep, we just go to sleep again
// put your setup code here, to run once: // put your setup code here, to run once:
Serial.begin(115200); Serial.begin(115200);
Serial.println("Starting *** FastclockMasterESP8266"); Serial.println("Starting *** FastclockMasterESP8266");
Serial.println(ESP.getResetReason());
display.begin(); display.begin();
display.showLog(); display.showLog();
@@ -203,23 +210,48 @@ void setup() {
// setupWifiConnection(); // setupWifiConnection();
radio.setClockChannel(clockChannel);
radio.setClockName(clockName);
radio.begin(); radio.begin();
fastclock.begin(); fastclock.begin();
pinMode(POWER_OFF_PIN, INPUT);
} }
bool requestToRegisterSent = false; void checkForPowerOffRequest() {
static unsigned long powerOffButtonPressed_ts = 0;
static bool powerOffRequested = false;
if (!digitalRead(POWER_OFF_PIN)) { // power off pressed
if (!powerOffRequested) {
Serial.println("Switching off? Keep button for 2 seconds to turn off.");
powerOffRequested = true;
powerOffButtonPressed_ts = millis();
}
if (millis() - powerOffButtonPressed_ts > 2000) {
// pressed for longer than 2 seconds, now turn off
Serial.println(F("OFF"));
ESP.deepSleep(ESP.deepSleepMax());
delay(200);
}
} else {
if (powerOffRequested) {
powerOffRequested = false;
Serial.println("Power off cancelled.");
powerOffButtonPressed_ts = 0;
}
}
}
void loop(void) void loop(void)
{ {
if (!display.showBootSequenceFinished(1000)) { if (!display.showBootSequenceFinished(4000)) {
return; return;
} }
if (!requestToRegisterSent) { checkForPowerOffRequest();
radio.broadcastRequestRegistration("testClock");
requestToRegisterSent = true;
return;
}
#if defined(AUTOSTART_CLOCK_AFTER_ms) #if defined(AUTOSTART_CLOCK_AFTER_ms)
#if AUTOSTART_CLOCK_AFTER_ms > 0 #if AUTOSTART_CLOCK_AFTER_ms > 0
@@ -235,7 +267,20 @@ void loop(void)
#endif #endif
#endif #endif
fastclock.loop(); // we do not want to call every task on every cycle:
display.showDashboard(); switch (millis() & 0x03) {
case 0:
fastclock.loop();
break;
case 1:
break;
case 2:
display.showDashboard();
break;
case 3:
break;
}
yield();
radio.loop(); // called always
} }

View File

@@ -3,21 +3,46 @@
#include <RF24.h> #include <RF24.h>
#include "config.h" #include "config.h"
#include "radio.h" #include "radio.h"
#include "clockMsg.h"
static RF24 rf24(PIN_NRF24_CE, PIN_NRF24_CSN); // 10, 8 static RF24 rf24(PIN_NRF24_CE, PIN_NRF24_CSN); // 10, 8
static byte addresses[][6] = {NETWORK_ADDRESS_MASTER_SEND, NETWORK_ADDRESS_MASTER_RECEIVE}; static byte addresses[][6] = {NETWORK_ADDRESS_MASTER_SEND, NETWORK_ADDRESS_MASTER_RECEIVE};
void Radio::begin(void) { static int sendFailedCounter = 0;
//static unsigned long stopTime = 0;
static unsigned long pauseTime = 0;
static char thisClockName[MAX_CLOCK_NAME_LEN];
#define MAX_CLIENTS_MANAGED 20
static uint8_t numberOfKnownClients = 0;
static struct {
uint8_t clientNetworkAddress;
char clientName[MAX_CLIENT_NAME_LEN+1];
} client[MAX_CLIENTS_MANAGED];
static uint8_t nextClientNetworkAddress = 1;
void Radio::checkRadioFailure(void) {
if (rf24.failureDetected) {
Serial.println(F("Radio failure detected!"));
rf24.failureDetected = 0;
radioInit();
}
}
void Radio::radioInit(void) {
display->addLogMessage("Start RF24 radio"); display->addLogMessage("Start RF24 radio");
pinMode(PIN_NRF24_CSN, OUTPUT); pinMode(PIN_NRF24_CSN, OUTPUT);
pinMode(PIN_NRF24_CE, OUTPUT); pinMode(PIN_NRF24_CE, OUTPUT);
rf24.begin(); rf24.begin();
if (rf24.isChipConnected()) { display->addLogMessage("*** RF chip found"); } if (rf24.isChipConnected()) {
else { display->addLogMessage("*** ERROR: RF chip not found!"); } display->addLogMessage("*** RF chip found");
rf24.setChannel(1); Serial.println(F("*** RF chip found"));
} else {
display->addLogMessage("*** ERROR: RF chip not found!");
Serial.println(F("*** ERROR: RF chip not found!"));
}
rf24.setChannel(clockChannel);
rf24.setPALevel(RF24_PA_MAX); rf24.setPALevel(RF24_PA_MAX);
rf24.setDataRate(RF24_2MBPS); rf24.setDataRate(RF24_2MBPS);
rf24.setAutoAck(0); rf24.setAutoAck(0);
@@ -26,129 +51,295 @@ void Radio::begin(void) {
rf24.openWritingPipe(addresses[0]); rf24.openWritingPipe(addresses[0]);
rf24.openReadingPipe(1, addresses[1]); rf24.openReadingPipe(1, addresses[1]);
rf24.startListening(); rf24.startListening();
Serial.println("*** Check");
//rf24.printDetails();
rf24.powerUp(); rf24.powerUp();
// @TODO: real random seed! // @TODO: real random seed!
//randomSeed(analogRead(0)); //randomSeed(analogRead(0));
//randomSeed(22); //randomSeed(22);
Serial.print("*** RF payload size="); Serial.print(rf24.getPayloadSize()); Serial.println(" bytes");
if (rf24.testCarrier() || rf24.testRPD()) { display->addLogMessage("*** Carrier/RPD seen on radio"); }
if (rf24.failureDetected) { display->addLogMessage("*** Radio error detected!"); }
#if 0
// Open a writing and reading pipe on each radio, with opposite addresses
if (isMaster){
rf24.openWritingPipe(addresses[1]);
rf24.openReadingPipe(1,addresses[0]);
} else {
rf24.openWritingPipe(addresses[0]);
rf24.openReadingPipe(1,addresses[1]);
}
#endif
// Start the radio listening for data
rf24.startListening();
} }
static bool inTX=1, inRX=0, role=inRX; void Radio::powerDown(void) {
rf24.powerDown();
}
static void switchToSenderRole(RF24 radio) void Radio::switchToSenderRole(void)
{ {
Serial.println("*** master: CHANGING TO TRANSMIT ROLE"); Serial.println(F("# Tx"));
radio.openWritingPipe(addresses[1]); rf24.openWritingPipe(addresses[1]);
radio.openReadingPipe(1,addresses[0]); rf24.openReadingPipe(1,addresses[0]);
radio.stopListening(); rf24.stopListening();
role = inTX; // Become the primary transmitter (ping out) role = inTX; // Become the primary transmitter (ping out)
pauseTime = millis();
checkRadioFailure();
} }
static void switchToReceiverRole(RF24 radio) void Radio::switchToReceiverRole(void)
{ {
Serial.println("*** master: CHANGING TO RECEIVER ROLE"); Serial.println(F("# Rx"));
radio.openWritingPipe(addresses[0]); rf24.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]); rf24.openReadingPipe(1,addresses[1]);
radio.startListening(); rf24.startListening();
role = inRX; // Become the primary receiver (pong back) role = inRX; // Become the primary receiver (pong back)
checkRadioFailure();
} }
static int sendFailedCounter = 0; void Radio::begin(void) {
static unsigned long stopTime = 0, pauseTime = 0; display->addLogMessage("Start RF24 radio");
pinMode(PIN_NRF24_CSN, OUTPUT);
pinMode(PIN_NRF24_CE, OUTPUT);
void Radio::broadcastRequestRegistration(const char *clockName) { radioInit();
char sendBuffer[32]; Serial.print(F("*** RF payload size=")); Serial.print(rf24.getPayloadSize()); Serial.println(F(" bytes"));
if (rf24.testCarrier() || rf24.testRPD()) {
memset(sendBuffer, 0, 32); display->addLogMessage("*** Carrier/RPD seen on radio");
sendBuffer[0] = msgType_ReqReg; Serial.println(F("*** Carrier/RPD seen on radio"));
strncpy(sendBuffer+1, clockName, MAX_CLOCK_NAME_LEN);
int msgLength = MAX_CLOCK_NAME_LEN + 1;
switchToSenderRole(rf24);
if (!rf24.writeFast(sendBuffer, msgLength, true /*multicast*/)) {
sendFailedCounter++;
Serial.print("*** ERROR: failed to send ReqRegistration msg for "); Serial.println(clockName);
} }
if (rf24.failureDetected) {
display->addLogMessage("*** Radio error detected!");
Serial.println(F("*** ERROR: Radio error detected!"));
}
switchToReceiverRole();
}
void Radio::broadcastMessageOnChannel(void *msg, int len, uint8_t channel) {
Serial.print(F("# set channel ")); Serial.println(channel);
rf24.setChannel(channel);
broadcastMessage(msg, len);
Serial.print(F("# set channel ")); Serial.println(clockChannel);
rf24.setChannel(clockChannel); // switch back
Serial.println(F("# done"));
}
void Radio::broadcastMessage(void *msg, int len) {
uint8_t *m = (uint8_t *) msg;
Serial.print(F("ts=")); Serial.println(millis());
switchToSenderRole();
if (!rf24.writeFast(msg, len, true /*multicast*/)) {
sendFailedCounter++;
Serial.println(F("*** ERROR: failed to send msg"));
}
Serial.print(F("ts=")); Serial.println(millis());
//This is only required when NO ACK ( enableAutoAck(0) ) payloads are used //This is only required when NO ACK ( enableAutoAck(0) ) payloads are used
if (millis() - pauseTime > 3) { if (millis() - pauseTime > 3) {
pauseTime = millis(); pauseTime = millis();
rf24.txStandBy(); // Need to drop out of TX mode every 4ms if sending a steady stream of multicast data Serial.println(F("@TODO: tx-pause"));
//rf24.txStandBy(); // Need to drop out of TX mode every 4ms if sending a steady stream of multicast data
//delayMicroseconds(130); // This gives the PLL time to sync back up //delayMicroseconds(130); // This gives the PLL time to sync back up
} }
stopTime = millis(); //stopTime = millis();
//Serial.print(F("stopTime=")); Serial.println(stopTime);
//This should be called to wait for completion and put the radio in standby mode after transmission, returns 0 if data still in FIFO (timed out), 1 if success //This should be called to wait for completion and put the radio in standby mode after transmission, returns 0 if data still in FIFO (timed out), 1 if success
if (!rf24.txStandBy()) { sendFailedCounter += 3; } //Standby, block only until FIFO empty or auto-retry timeout. Flush TX FIFO if failed //if (!rf24.txStandBy()) { sendFailedCounter += 3; } //Standby, block only until FIFO empty or auto-retry timeout. Flush TX FIFO if failed
//radio.txStandBy(1000); //Standby, using extended timeout period of 1 second //radio.txStandBy(1000); //Standby, using extended timeout period of 1 second
Serial.print("*** send finished, fail-counter="); Serial.println(sendFailedCounter); listenOnlyFor_ms(MIN_TIME_BETWEEN_TRANSMISSIONS_ms);
switchToReceiverRole();
Serial.print(F("*** sent: len=")); Serial.print(len);
Serial.print(F(", typ="));
Serial.print((uint8_t) m[0], HEX); Serial.print(" ("); Serial.print((char) m[0]);
Serial.print(F("), to=")); Serial.print((uint8_t) m[1], HEX);
Serial.print(F(", from=")); Serial.print((uint8_t) m[2], HEX);
Serial.print(F(", msg=")); Serial.print((uint8_t) m[3], HEX);
Serial.print(' '); Serial.print((uint8_t) m[4], HEX);
Serial.print(' '); Serial.print((uint8_t) m[5], HEX);
Serial.print(' '); Serial.print((uint8_t) m[6], HEX);
Serial.print(' '); Serial.print((uint8_t) m[7], HEX);
Serial.print(' '); Serial.println((uint8_t) m[8], HEX);
Serial.print(F("*** send failed=")); Serial.println(sendFailedCounter);
}
void Radio::broadcastClockAdvertisement(const char *clockName, uint8_t channel) {
struct offerPairing_s msg;
memset(&msg, 0, sizeof(msg));
msg.msgType = msgType_OfferPairing;
msg.from = MESSAGE_FROM_Master;
msg.to = MESSAGE_TO_ALL;
strncpy(msg.clockName, clockName, MAX_CLOCK_NAME_LEN);
strncpy(thisClockName, clockName, MAX_CLOCK_NAME_LEN);
msg.channel = channel;
int msgLength = sizeof(msg);
broadcastMessageOnChannel(&msg, msgLength, ADVERTISEMENT_CHANNEL);
}
// send pairing offering on advertisement channel
void Radio::broadcastOfferPairing(const char *clockName, uint8_t channel) {
struct offerPairing_s msg;
memset(&msg, 0, sizeof(msg));
msg.msgType = msgType_OfferPairing;
msg.from = MESSAGE_FROM_Master;
msg.to = MESSAGE_TO_ALL;
strncpy(msg.clockName, clockName, MAX_CLOCK_NAME_LEN);
strncpy(thisClockName, clockName, MAX_CLOCK_NAME_LEN);
msg.channel = channel;
int msgLength = sizeof(msg);
broadcastMessage(&msg, msgLength);
} }
void Radio::broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond) { void Radio::broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond) {
struct clockMsg_s clockMsg; struct clockMsg_s clockMsg;
clockMsg.msgType = msgType_Clock; clockMsg.msgType = msgType_Clock;
clockMsg.to = MESSAGE_TO_ALL;
clockMsg.from = MESSAGE_FROM_Master;
strncpy(clockMsg.clockName, thisClockName, MAX_CLOCK_NAME_LEN);
clockMsg.day = day; clockMsg.day = day;
clockMsg.hour = hour; clockMsg.hour = hour;
clockMsg.minute = minute; clockMsg.minute = minute;
clockMsg.second = second; clockMsg.second = second;
clockMsg.speed = (msPerModelSecond >> 2) & 0xff; // divide by 4 clockMsg.speed = (msPerModelSecond >> 2) & 0xff; // divide by 4
switchToSenderRole(rf24);
if (!rf24.writeFast(&clockMsg, sizeof(clockMsg), true /*multicast*/)) {
sendFailedCounter++;
Serial.print("*** ERROR: failed to send clock msg for "); Serial.print(hour); Serial.print(":"); Serial.print(minute); Serial.print(":"); Serial.println(second);
}
//This is only required when NO ACK ( enableAutoAck(0) ) payloads are used
if (millis() - pauseTime > 3) {
pauseTime = millis();
rf24.txStandBy(); // Need to drop out of TX mode every 4ms if sending a steady stream of multicast data
//delayMicroseconds(130); // This gives the PLL time to sync back up
}
stopTime = millis();
//This should be called to wait for completion and put the radio in standby mode after transmission, returns 0 if data still in FIFO (timed out), 1 if success
if (!rf24.txStandBy()) { sendFailedCounter += 3; } //Standby, block only until FIFO empty or auto-retry timeout. Flush TX FIFO if failed
//radio.txStandBy(1000); //Standby, using extended timeout period of 1 second
Serial.print("*** send finished, fail-counter="); Serial.println(sendFailedCounter); broadcastMessage(&clockMsg, sizeof(clockMsg));
}
void Radio::broadcastTextMessage(const char *text) {
struct textMessage_s msg;
memset(&msg, 0, sizeof(msg));
msg.msgType = msgType_TextMessage;
msg.from = MESSAGE_FROM_Master;
msg.to = MESSAGE_TO_ALL;
strncpy(msg.text, text, MAX_TEXT_MESSAGE_LEN);
int msgLength = sizeof(msg);
broadcastMessage(&msg, msgLength);
}
void Radio::ackPairingRequest(char *clientName, uint8_t clientNetworkAddress) {
struct ackPairing_s msg;
memset(&msg, 0, sizeof(msg));
msg.msgType = msgType_ackPairingRequest;
msg.from = MESSAGE_FROM_Master;
msg.to = MESSAGE_TO_ALL;
strncpy(&msg.clockName[0], thisClockName, MAX_CLOCK_NAME_LEN);
strncpy(&msg.clientName[0], clientName, MAX_CLIENT_NAME_LEN);
msg.clientNetworkAddress = clientNetworkAddress;
int msgLength = sizeof(msg);
broadcastMessage(&msg, msgLength);
}
// handle received messages
void Radio::handleRequestPairing(struct requestPairing_s *request) {
int i=0;
bool found;
if (strncmp(&request->clientName[0], thisClockName, MAX_CLOCK_NAME_LEN) == 0) {
// add this client to client list
Serial.print("*** Rcv'd pairing request for "); Serial.println(&request->clientName[0]);
found = false;
for (i=0; i<numberOfKnownClients && !found; ++i) {
if (strncmp(&client[i].clientName[0], &request->clientName[0], MAX_CLIENT_NAME_LEN) == 0) found = true;
}
if (!found) {
// create new entry and set "i" to this new entry
if (numberOfKnownClients < MAX_CLIENTS_MANAGED) {
i = numberOfKnownClients;
strncpy(&client[i].clientName[0], &request->clientName[0], MAX_CLIENT_NAME_LEN);
client[i].clientNetworkAddress = nextClientNetworkAddress++;
numberOfKnownClients++;
Serial.print("Added new client to list of clients. Now know ");
Serial.print(numberOfKnownClients);
Serial.print(", new is ");
Serial.println(&client[i].clientName[0]);
} else {
Serial.print("ERROR: Too many client pairing requests, max reached: ");
Serial.println(MAX_CLIENTS_MANAGED);
return;
}
}
ackPairingRequest(&request->clientName[0], client[i].clientNetworkAddress);
}
// else ignore this message, as it is for another clock
else {
Serial.print("*** received pairing request, but it is for another clock named: ");
Serial.println(&request->clientName[0]);
}
}
void Radio::avoidChannelSwitchesFor_ms(unsigned int duration_ms) {
long current_ts = millis();
if (nextClockAdvertisement_ts <= current_ts + duration_ms) {
nextClockAdvertisement_ts = current_ts + duration_ms + 1;
}
}
void Radio::listenOnlyFor_ms(unsigned int duration_ms) {
unsigned long current_ts = millis();
if (nextSendMessage_ts <= current_ts + duration_ms) {
nextSendMessage_ts = current_ts + duration_ms + 1;
}
}
bool Radio::allowedToSendNow() {
if (millis() >= nextSendMessage_ts) return true;
return false;
}
bool Radio::checkClockAdvertisement() {
unsigned long current_ts = millis();
if (current_ts >= nextClockAdvertisement_ts) {
broadcastClockAdvertisement(clockName, clockChannel);
nextClockAdvertisement_ts = current_ts + timeBetweenClockAdvertisements_ms;
return true;
}
return false;
}
bool Radio::checkPairingOffering() {
unsigned long current_ts = millis();
if (current_ts >= nextPairingOfferingAnnouncement_ts) {
broadcastOfferPairing(clockName, clockChannel);
nextPairingOfferingAnnouncement_ts = current_ts + timeBetweenPairingOfferingAnnouncements_ms;
avoidChannelSwitchesFor_ms(timeForClientsToRespond_ms);
listenOnlyFor_ms(timeForClientsToRespond_ms);
return true;
}
return false;
} }
void Radio::loop(void) { void Radio::loop(void) {
// put your main code here, to run repeatedly: // put your main code here, to run repeatedly:
/****************** Ping Out Role ***************************/ /****************** Ping Out Role ***************************/
if (isMaster) { if (isMaster) {
switchToReceiverRole(rf24);
if (rf24.available()) { if (rf24.available()) {
char buffer[32]; char buffer[32];
rf24.read(buffer, 32); rf24.read(buffer, 32);
// Spew it // Spew it
Serial.print(F("Received new message, type=")); if (buffer[0] > 0) {
Serial.print(buffer[0]); Serial.print(F("Rx, t="));
Serial.print(F(", bytes 1=")); Serial.print(buffer[0], HEX);
Serial.print(buffer[1]); Serial.print(F(", msg="));
Serial.print(", 2="); Serial.print(buffer[1], HEX);
Serial.print(buffer[2]); Serial.print(", ");
Serial.print(", 3="); Serial.print(buffer[2], HEX);
Serial.println(buffer[3]); Serial.print(", ");
Serial.print(buffer[3], HEX);
Serial.print(", ");
Serial.println(buffer[4], HEX);
}
}
// Tx handling
if (allowedToSendNow()) {
if (!checkPairingOffering()) {
if (!checkClockAdvertisement()) {
// nothing sent :-)
}
}
} }
} }
@@ -162,8 +353,7 @@ void Radio::loop(void) {
{ {
unsigned long got_time; unsigned long got_time;
if( radio.available()){ if (radio.available()) { // Variable for the received timestamp
// Variable for the received timestamp
while (radio.available()) { // While there is data ready while (radio.available()) { // While there is data ready
radio.read( &got_time, sizeof(unsigned long) ); // Get the payload radio.read( &got_time, sizeof(unsigned long) ); // Get the payload
} }

View File

@@ -3,15 +3,119 @@
#include "config.h" #include "config.h"
#include "display.h" #include "display.h"
// msgType definitions to identify message and be able to use correcnt structure
#define msgType_Clock 'c' /* clock update, sent by master using broadcast */
#define msgType_OfferPairing 'R' /* Request Registration, sent by Master using broadcast; ask clients to register */
#define msgType_RequestPairing 'r' /* Registration message, sent by Client using 1:1 message to master */
#define msgType_ackPairingRequest 'A'
#define msgType_TextMessage 'T'
// address definitions for special addresses
#define MESSAGE_TO_ALL 0xff
#define MESSAGE_TO_Master 0x00
#define MESSAGE_FROM_Master MESSAGE_TO_Master
// message structures for easy access:
struct clockMsg_s {
uint8_t msgType;
uint8_t to;
uint8_t from;
char clockName[MAX_CLOCK_NAME_LEN];
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
uint8_t speed; // msPerModelSecond / 4 --> 0..250
};
struct offerPairing_s {
uint8_t msgType;
uint8_t to;
uint8_t from;
char clockName[MAX_CLOCK_NAME_LEN];
uint8_t channel;
};
struct requestPairing_s {
uint8_t msgType;
uint8_t to;
uint8_t from;
char clientName[MAX_CLIENT_NAME_LEN];
};
struct ackPairing_s {
uint8_t msgType;
uint8_t to;
uint8_t from;
char clockName[MAX_CLOCK_NAME_LEN];
char clientName[MAX_CLIENT_NAME_LEN];
uint8_t clientNetworkAddress; // address assigned to client by controller (instead of using names, used as from/to */
};
struct textMessage_s {
uint8_t msgType;
uint8_t to;
uint8_t from;
char text[MAX_TEXT_MESSAGE_LEN];
};
class Radio { class Radio {
public: public:
Radio(Display *d, bool _isMaster):display(d), isMaster(_isMaster) { }; Radio(Display *d, bool _isMaster):display(d), isMaster(_isMaster) {
inTX = true;
inRX = false;
role = inRX;
nextRequestToRegisterAnnouncement_ts = 0;
nextClockAdvertisement_ts = 0;
nextPairingOfferingAnnouncement_ts = 0;
timeBetweenClockAdvertisements_ms = DEFAULT_TIME_BETWEEN_CLOCK_ADVERTISEMENTS_ms;
timeForClientsToRespond_ms = DEFAULT_TIME_FOR_CLIENTS_TO_RESPOND_ms;
timeBetweenPairingOfferingAnnouncements_ms = DEFAULT_TIME_BETWEEN_PAIRING_OFFERINGS_ms;
nextSendMessage_ts = 0;
clockChannel = DEFAULT_CLOCK_CHANNEL;
strncpy(clockName, "defclk", MAX_CLOCK_NAME_LEN);
};
void setClockName(const char *_clockName) {
strncpy(clockName, _clockName, MAX_CLOCK_NAME_LEN);
};
void setClockChannel(uint8_t _channel) {
clockChannel = _channel;
};
void begin(void); void begin(void);
void loop(void); void loop(void);
void broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond); void broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond);
void broadcastRequestRegistration(const char *clockName); void broadcastOfferPairing(const char *clockName, uint8_t channel);
void broadcastTextMessage(const char *text);
void handleRequestPairing(struct requestPairing_s *request);
void ackPairingRequest(char *clientName, uint8_t clientNetworkAddress);
void avoidChannelSwitchesFor_ms(unsigned int duration_ms);
void listenOnlyFor_ms(unsigned int duration_ms);
bool allowedToSendNow(void);
void checkRadioFailure(void);
void powerDown(void);
private: private:
Display *display; Display *display;
bool isMaster; bool isMaster;
void radioInit(void);
void switchToSenderRole(void);
void switchToReceiverRole(void);
bool inTX, inRX, role;
char clockName[MAX_CLOCK_NAME_LEN+1];
void broadcastMessage(void *msg, int len);
void broadcastMessageOnChannel(void *msg, int len, uint8_t channel);
void broadcastClockAdvertisement(const char *clockName, uint8_t channel);
bool checkPairingOffering(void); // returns true, if a message has been sent
bool checkClockAdvertisement(void); // returns true, if a message has been sent
unsigned long nextClockAdvertisement_ts;
unsigned long timeBetweenClockAdvertisements_ms;
unsigned long nextPairingOfferingAnnouncement_ts;
unsigned long timeBetweenPairingOfferingAnnouncements_ms;
unsigned long nextRequestToRegisterAnnouncement_ts;
unsigned long timeForClientsToRespond_ms;
unsigned long nextSendMessage_ts;
uint8_t clockChannel;
}; };
#endif #endif