Compare commits

...

2 Commits

5 changed files with 233 additions and 85 deletions

View File

@ -18,7 +18,7 @@ The tasks of the master clock / controller is:
* addresses are a number in the range of 0..255 * addresses are a number in the range of 0..255
* address 0 is reserved for master/controller * address 0 is reserved for master/controller
* address 255 is reserved for a broadcast to all devices * address 255 is reserved for a broadcast to all devices
* if acknowledges are necessary/wanted, then separate messages needs to be applied * 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 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 * after sending a message, the same device must wait a TBD. period of time before sending another message
@ -33,19 +33,25 @@ P,p,A,U,a,C,T,H,E | 0..255 | 0..255 | msg type dependent
## Message definitions ## Message definitions
Nachricht | msgType | Attribute | Status Nachricht | msgType | Attribute | Status | Description
--------- | ------- | --------- | ------ --------- | ------- | --------- | ------ | -----------
PairingOffering | P | FastclockName (8), ClockChannel | Implemented ClockAdvertisement | c | FastclockName (8)<br>ClockChannel | done | Using ADVERTISEMENT_CHANNEL (channel 1) to<br>announce the clock's existence.
PairingRequest | p | ClientName(10) | Implemented 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).
PairingAck | A | FastclockName (8), ClientName (10), Network-Address | Implemented PairingRequest | p | ClientName(10) | done | Message from client as reply<br>to PairingOffering.
UnpairRequest | U | tbd. | --- PairingAck | A | FastclockName (8)<br>ClientName (10)<br>Network-Address | done | Master acknowledges a PairingRequest.
UnpairAck | a | tbd. | --- UnpairRequest | U | tbd. | --- | Remove a client from master's client list,<br>initiated by client.
FastClock | C | FastclockName (8), Hour, Minute, Second, ClockSpeed, Weekday | Implemented UnpairAck | a | tbd. | --- | Acknowledge an UnpairRequest,<br>sent by master.
TextMessage | T | FastclockName (8), Message | Implemented FastClock | C | FastclockName (8)<br>Hour<br>Minute<br>Second<br>ClockSpeed<br>Weekday<br>stopped | done | Current fastclock time.
Hello | H | tbd. | --- TextMessage | T | FastclockName (8)<br>Message | done | Additional text sent by master may be displayed<br>by client, if possible.
HelloEcho | E | RSSI, canRouteToMaster, FastclockName(8) | --- 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

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

View File

@ -28,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] = "fastclk";
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
//flag for saving data //flag for saving data
bool shouldSaveConfig = false; bool shouldSaveConfig = false;
@ -207,31 +210,23 @@ void setup() {
// setupWifiConnection(); // setupWifiConnection();
radio.setClockChannel(clockChannel);
radio.setClockName(clockName);
radio.begin(); radio.begin();
fastclock.begin(); fastclock.begin();
pinMode(POWER_OFF_PIN, INPUT); pinMode(POWER_OFF_PIN, INPUT);
} }
bool requestToRegisterSent = false; void checkForPowerOffRequest() {
void loop(void)
{
if (!display.showBootSequenceFinished(1000)) {
return;
}
if (!requestToRegisterSent) {
radio.broadcastOfferPairing("testClock", nRF_Channel);
requestToRegisterSent = true;
return;
}
static unsigned long powerOffButtonPressed_ts = 0; static unsigned long powerOffButtonPressed_ts = 0;
static bool powerOffMessagePrinted = false; static bool powerOffRequested = false;
if (!digitalRead(POWER_OFF_PIN)) {
if (!powerOffMessagePrinted) { if (!digitalRead(POWER_OFF_PIN)) { // power off pressed
if (!powerOffRequested) {
Serial.println("Switching off? Keep button for 2 seconds to turn off."); Serial.println("Switching off? Keep button for 2 seconds to turn off.");
powerOffMessagePrinted = true; powerOffRequested = true;
powerOffButtonPressed_ts = millis();
} }
if (millis() - powerOffButtonPressed_ts > 2000) { if (millis() - powerOffButtonPressed_ts > 2000) {
// pressed for longer than 2 seconds, now turn off // pressed for longer than 2 seconds, now turn off
@ -239,10 +234,23 @@ void loop(void)
delay(200); delay(200);
} }
} else { } else {
powerOffButtonPressed_ts = millis(); if (powerOffRequested) {
if (powerOffMessagePrinted) Serial.println("Power off cancelled."); powerOffRequested = false;
powerOffMessagePrinted = false; Serial.println("Power off cancelled.");
powerOffButtonPressed_ts = 0;
} }
}
}
void loop(void)
{
if (!display.showBootSequenceFinished(1000)) {
return;
}
checkForPowerOffRequest();
#if defined(AUTOSTART_CLOCK_AFTER_ms) #if defined(AUTOSTART_CLOCK_AFTER_ms)
#if AUTOSTART_CLOCK_AFTER_ms > 0 #if AUTOSTART_CLOCK_AFTER_ms > 0
@ -259,6 +267,7 @@ void loop(void)
#endif #endif
fastclock.loop(); fastclock.loop();
radio.loop();
display.showDashboard(); display.showDashboard();
} }

View File

@ -12,8 +12,6 @@ static int sendFailedCounter = 0;
static unsigned long stopTime = 0, pauseTime = 0; static unsigned long stopTime = 0, pauseTime = 0;
static char thisClockName[MAX_CLOCK_NAME_LEN]; static char thisClockName[MAX_CLOCK_NAME_LEN];
static bool inTX=1, inRX=0, role=inRX;
#define MAX_CLIENTS_MANAGED 20 #define MAX_CLIENTS_MANAGED 20
static uint8_t numberOfKnownClients = 0; static uint8_t numberOfKnownClients = 0;
static struct { static struct {
@ -22,6 +20,23 @@ static struct {
} client[MAX_CLIENTS_MANAGED]; } client[MAX_CLIENTS_MANAGED];
static uint8_t nextClientNetworkAddress = 1; static uint8_t nextClientNetworkAddress = 1;
void Radio::switchToSenderRole(void)
{
Serial.println("*** changing to Tx role");
rf24.openWritingPipe(addresses[1]);
rf24.openReadingPipe(1,addresses[0]);
rf24.stopListening();
role = inTX; // Become the primary transmitter (ping out)
}
void Radio::switchToReceiverRole(void)
{
Serial.println("*** changing to Rx role");
rf24.openWritingPipe(addresses[0]);
rf24.openReadingPipe(1,addresses[1]);
rf24.startListening();
role = inRX; // Become the primary receiver (pong back)
}
void Radio::begin(void) { void Radio::begin(void) {
display->addLogMessage("Start RF24 radio"); display->addLogMessage("Start RF24 radio");
@ -31,7 +46,7 @@ void Radio::begin(void) {
rf24.begin(); rf24.begin();
if (rf24.isChipConnected()) { display->addLogMessage("*** RF chip found"); } if (rf24.isChipConnected()) { display->addLogMessage("*** RF chip found"); }
else { display->addLogMessage("*** ERROR: RF chip not found!"); } else { display->addLogMessage("*** ERROR: RF chip not found!"); }
rf24.setChannel(1); 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);
@ -50,64 +65,77 @@ void Radio::begin(void) {
if (rf24.testCarrier() || rf24.testRPD()) { display->addLogMessage("*** Carrier/RPD seen on radio"); } if (rf24.testCarrier() || rf24.testRPD()) { display->addLogMessage("*** Carrier/RPD seen on radio"); }
if (rf24.failureDetected) { display->addLogMessage("*** Radio error detected!"); } 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 // Start the radio listening for data
rf24.startListening(); //rf24.startListening();
switchToReceiverRole();
} }
static void switchToSenderRole(RF24 radio) void Radio::broadcastMessageOnChannel(void *msg, int len, uint8_t channel) {
{ Serial.print("# set channel "); Serial.println(channel);
Serial.println("*** master: CHANGING TO TRANSMIT ROLE"); rf24.setChannel(channel);
radio.openWritingPipe(addresses[1]); broadcastMessage(msg, len);
radio.openReadingPipe(1,addresses[0]); Serial.print("# set channel "); Serial.println(clockChannel);
radio.stopListening(); rf24.setChannel(clockChannel); // switch back
role = inTX; // Become the primary transmitter (ping out) Serial.println("# done");
}
static void switchToReceiverRole(RF24 radio)
{
Serial.println("*** master: CHANGING TO RECEIVER ROLE");
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1,addresses[1]);
radio.startListening();
role = inRX; // Become the primary receiver (pong back)
} }
void Radio::broadcastMessage(void *msg, int len) { void Radio::broadcastMessage(void *msg, int len) {
switchToSenderRole(rf24); uint8_t *m = (uint8_t *) msg;
Serial.print("*** Tx: ts="); Serial.print(millis());
Serial.print(", len="); Serial.print(len);
Serial.print(", typ=");
Serial.print((uint8_t) m[0], HEX); Serial.print(" ("); Serial.print((char) m[0]);
Serial.print("), to="); Serial.print((uint8_t) m[1], HEX);
Serial.print(", from="); Serial.print((uint8_t) m[2], HEX);
Serial.print(", 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);
switchToSenderRole();
Serial.print("#1");
if (!rf24.writeFast(msg, len, true /*multicast*/)) { if (!rf24.writeFast(msg, len, true /*multicast*/)) {
sendFailedCounter++; sendFailedCounter++;
Serial.print("*** ERROR: failed to send msg type="); Serial.print("*** ERROR: failed to send msg type=");
Serial.print(*((char *) msg)); Serial.print(*((char *) msg));
Serial.print(" for to=0x"); Serial.print(" for to=0x");
Serial.println(*((uint8_t *) (msg+1)), HEX); Serial.println(m[1], HEX);
} }
Serial.print("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.print("pauseTime="); Serial.println(pauseTime);
//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("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); Serial.print("*** send finished, failed="); Serial.println(sendFailedCounter);
listenOnlyFor_ms(MIN_TIME_BETWEEN_TRANSMISSIONS_ms);
} }
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) { void Radio::broadcastOfferPairing(const char *clockName, uint8_t channel) {
struct offerPairing_s msg; struct offerPairing_s msg;
@ -170,14 +198,14 @@ void Radio::ackPairingRequest(char *clientName, uint8_t clientNetworkAddress) {
// handle received messages // handle received messages
void Radio::handleRequestPairing(struct requestPairing_s *request) { void Radio::handleRequestPairing(struct requestPairing_s *request) {
int i; int i=0;
bool found; bool found;
if (strncmp(&request->clientName[0], thisClockName, MAX_CLOCK_NAME_LEN) == 0) { if (strncmp(&request->clientName[0], thisClockName, MAX_CLOCK_NAME_LEN) == 0) {
// add this client to client list // add this client to client list
Serial.print("*** Rcv'd pairing request for "); Serial.println(&request->clientName[0]); Serial.print("*** Rcv'd pairing request for "); Serial.println(&request->clientName[0]);
found = false; found = false;
for (int i=0; i<numberOfKnownClients && !found; ++i) { for (i=0; i<numberOfKnownClients && !found; ++i) {
if (strncmp(&client[i].clientName[0], &request->clientName[0], MAX_CLIENT_NAME_LEN) == 0) found = true; if (strncmp(&client[i].clientName[0], &request->clientName[0], MAX_CLIENT_NAME_LEN) == 0) found = true;
} }
if (!found) { if (!found) {
@ -206,24 +234,82 @@ void Radio::handleRequestPairing(struct requestPairing_s *request) {
} }
} }
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 :-)
}
}
} }
} }
@ -237,8 +323,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

@ -61,7 +61,29 @@ struct textMessage_s {
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);
@ -69,9 +91,28 @@ public:
void broadcastTextMessage(const char *text); void broadcastTextMessage(const char *text);
void handleRequestPairing(struct requestPairing_s *request); void handleRequestPairing(struct requestPairing_s *request);
void ackPairingRequest(char *clientName, uint8_t clientNetworkAddress); void ackPairingRequest(char *clientName, uint8_t clientNetworkAddress);
void avoidChannelSwitchesFor_ms(unsigned int duration_ms);
void listenOnlyFor_ms(unsigned int duration_ms);
bool allowedToSendNow(void);
private: private:
Display *display; Display *display;
bool isMaster; bool isMaster;
void switchToSenderRole(void);
void switchToReceiverRole(void);
bool inTX, inRX, role;
char clockName[MAX_CLOCK_NAME_LEN];
void broadcastMessage(void *msg, int len); 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