From 06b36ed3d05e1cf5b5dc3ad9b64fff4ba2ad94a0 Mon Sep 17 00:00:00 2001 From: Dirk Jahnke Date: Sat, 1 Dec 2018 09:02:19 +0100 Subject: [PATCH] Introduced advertisement on channel 1 and clock-channel to be configurable. --- README.md | 30 ++++---- src/config.h | 7 ++ src/main.cpp | 15 ++-- src/radio.cpp | 187 ++++++++++++++++++++++++++++++++++++-------------- src/radio.h | 43 +++++++++++- 5 files changed, 211 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index f5ba2e8..447acac 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The tasks of the master clock / controller is: * 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 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 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 -Nachricht | msgType | Attribute | Status ---------- | ------- | --------- | ------ -PairingOffering | P | FastclockName (8), ClockChannel | Implemented -PairingRequest | p | ClientName(10) | Implemented -PairingAck | A | FastclockName (8), ClientName (10), Network-Address | Implemented -UnpairRequest | U | tbd. | --- -UnpairAck | a | tbd. | --- -FastClock | C | FastclockName (8), Hour, Minute, Second, ClockSpeed, Weekday | Implemented -TextMessage | T | FastclockName (8), Message | Implemented -Hello | H | tbd. | --- -HelloEcho | E | RSSI, canRouteToMaster, FastclockName(8) | --- +Nachricht | msgType | Attribute | Status | Description +--------- | ------- | --------- | ------ | ----------- +ClockAdvertisement | c | FastclockName (8)
ClockChannel | done | Using ADVERTISEMENT_CHANNEL (channel 1) to
announce the clock's existence. +PairingOffering | P | FastclockName (8)
ClockChannel | done | Announce clock master and
allow immediate reply by clients
(master waits with further messages
for at least 20ms). +PairingRequest | p | ClientName(10) | done | Message from client as reply
to PairingOffering. +PairingAck | A | FastclockName (8)
ClientName (10)
Network-Address | done | Master acknowledges a PairingRequest. +UnpairRequest | U | tbd. | --- | Remove a client from master's client list,
initiated by client. +UnpairAck | a | tbd. | --- | Acknowledge an UnpairRequest,
sent by master. +FastClock | C | FastclockName (8)
Hour
Minute
Second
ClockSpeed
Weekday
stopped | done | Current fastclock time. +TextMessage | T | FastclockName (8)
Message | done | Additional text sent by master may be displayed
by client, if possible. +Hello | H | tbd. | --- | Test message sent by master
to receive signal quality data. +HelloEcho | E | RSSI
canRouteToMaster
FastclockName(8) | --- | Hello reply with signal quality data
and the ability to route (if a client cannot
receive a master but routing clients, this
is the chance to get access;
routing not clearly defined so far,
it's just an idea) +Quiet | Q | quietTime_ms (1) | --- | Master sends this message and asks for being
quiet for some time, as the master will switch
off (e.g. to the advertisement channel to send
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 diff --git a/src/config.h b/src/config.h index 4fca769..b53883b 100644 --- a/src/config.h +++ b/src/config.h @@ -13,6 +13,13 @@ #define NETWORK_ADDRESS_MASTER_SEND "1ClkM" #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 #define DEFAULT_HOLD_RELAY_MS 150 #define DEFAULT_MIN_RELAY_OFF_TIME_MS 80 diff --git a/src/main.cpp b/src/main.cpp index 489673b..dc665e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,9 @@ char static_ip[16] = "10.0.1.56"; char static_gw[16] = "10.0.1.1"; 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 bool shouldSaveConfig = false; @@ -207,6 +210,9 @@ void setup() { // setupWifiConnection(); + + radio.setClockChannel(clockChannel); + radio.setClockName(clockName); radio.begin(); fastclock.begin(); pinMode(POWER_OFF_PIN, INPUT); @@ -236,7 +242,7 @@ void checkForPowerOffRequest() { } } -bool requestToRegisterSent = false; + void loop(void) { @@ -244,12 +250,6 @@ void loop(void) return; } - if (!requestToRegisterSent) { - radio.broadcastOfferPairing("testClock", nRF_Channel); - requestToRegisterSent = true; - return; - } - checkForPowerOffRequest(); #if defined(AUTOSTART_CLOCK_AFTER_ms) @@ -267,6 +267,7 @@ void loop(void) #endif fastclock.loop(); + radio.loop(); display.showDashboard(); } diff --git a/src/radio.cpp b/src/radio.cpp index 9c40499..bfe2483 100644 --- a/src/radio.cpp +++ b/src/radio.cpp @@ -12,8 +12,6 @@ static int sendFailedCounter = 0; static unsigned long stopTime = 0, pauseTime = 0; static char thisClockName[MAX_CLOCK_NAME_LEN]; -static bool inTX=1, inRX=0, role=inRX; - #define MAX_CLIENTS_MANAGED 20 static uint8_t numberOfKnownClients = 0; static struct { @@ -22,6 +20,23 @@ static struct { } client[MAX_CLIENTS_MANAGED]; 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) { display->addLogMessage("Start RF24 radio"); @@ -31,7 +46,7 @@ void Radio::begin(void) { rf24.begin(); if (rf24.isChipConnected()) { display->addLogMessage("*** RF chip found"); } else { display->addLogMessage("*** ERROR: RF chip not found!"); } - rf24.setChannel(1); + rf24.setChannel(clockChannel); rf24.setPALevel(RF24_PA_MAX); rf24.setDataRate(RF24_2MBPS); 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.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(); + //rf24.startListening(); + switchToReceiverRole(); } -static void switchToSenderRole(RF24 radio) -{ - Serial.println("*** master: CHANGING TO TRANSMIT ROLE"); - radio.openWritingPipe(addresses[1]); - radio.openReadingPipe(1,addresses[0]); - radio.stopListening(); - role = inTX; // Become the primary transmitter (ping out) -} - -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::broadcastMessageOnChannel(void *msg, int len, uint8_t channel) { + Serial.print("# set channel "); Serial.println(channel); + rf24.setChannel(channel); + broadcastMessage(msg, len); + Serial.print("# set channel "); Serial.println(clockChannel); + rf24.setChannel(clockChannel); // switch back + Serial.println("# done"); } 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*/)) { sendFailedCounter++; Serial.print("*** ERROR: failed to send msg type="); Serial.print(*((char *) msg)); 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 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 + 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 } 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 - 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 - 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) { struct offerPairing_s msg; @@ -170,14 +198,14 @@ void Radio::ackPairingRequest(char *clientName, uint8_t clientNetworkAddress) { // handle received messages void Radio::handleRequestPairing(struct requestPairing_s *request) { - int i; + 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 (int i=0; iclientName[0], MAX_CLIENT_NAME_LEN) == 0) found = true; } 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) { // put your main code here, to run repeatedly: /****************** Ping Out Role ***************************/ if (isMaster) { - switchToReceiverRole(rf24); if (rf24.available()) { char buffer[32]; rf24.read(buffer, 32); // Spew it - Serial.print(F("Received new message, type=")); - Serial.print(buffer[0]); - Serial.print(F(", bytes 1=")); - Serial.print(buffer[1]); - Serial.print(", 2="); - Serial.print(buffer[2]); - Serial.print(", 3="); - Serial.println(buffer[3]); + if (buffer[0] > 0) { + Serial.print(F("Rx, t=")); + Serial.print(buffer[0], HEX); + Serial.print(F(", msg=")); + Serial.print(buffer[1], HEX); + Serial.print(", "); + Serial.print(buffer[2], HEX); + 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; - if( radio.available()){ - // Variable for the received timestamp + if (radio.available()) { // Variable for the received timestamp while (radio.available()) { // While there is data ready radio.read( &got_time, sizeof(unsigned long) ); // Get the payload } diff --git a/src/radio.h b/src/radio.h index 6d4e7bb..4334c40 100644 --- a/src/radio.h +++ b/src/radio.h @@ -61,7 +61,29 @@ struct textMessage_s { class Radio { 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 loop(void); void broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond); @@ -69,9 +91,28 @@ public: 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); private: Display *display; 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 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