Compare commits

..

No commits in common. "06b36ed3d05e1cf5b5dc3ad9b64fff4ba2ad94a0" and "f6849ea5348b32f89bd7218d4e2068fa6237c1d1" have entirely different histories.

5 changed files with 88 additions and 236 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
* 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
* if acknowledges are necessary/wanted, then separate messages needs to be applied
* 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,25 +33,19 @@ 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).
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) | ---
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

View File

@ -13,13 +13,6 @@
#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

View File

@ -28,9 +28,6 @@ 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;
@ -210,39 +207,12 @@ void setup() {
// setupWifiConnection();
radio.setClockChannel(clockChannel);
radio.setClockName(clockName);
radio.begin();
fastclock.begin();
pinMode(POWER_OFF_PIN, INPUT);
}
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
ESP.deepSleep(ESP.deepSleepMax());
delay(200);
}
} else {
if (powerOffRequested) {
powerOffRequested = false;
Serial.println("Power off cancelled.");
powerOffButtonPressed_ts = 0;
}
}
}
bool requestToRegisterSent = false;
void loop(void)
{
@ -250,7 +220,29 @@ void loop(void)
return;
}
checkForPowerOffRequest();
if (!requestToRegisterSent) {
radio.broadcastOfferPairing("testClock", nRF_Channel);
requestToRegisterSent = true;
return;
}
static unsigned long powerOffButtonPressed_ts = 0;
static bool powerOffMessagePrinted = false;
if (!digitalRead(POWER_OFF_PIN)) {
if (!powerOffMessagePrinted) {
Serial.println("Switching off? Keep button for 2 seconds to turn off.");
powerOffMessagePrinted = true;
}
if (millis() - powerOffButtonPressed_ts > 2000) {
// pressed for longer than 2 seconds, now turn off
ESP.deepSleep(ESP.deepSleepMax());
delay(200);
}
} else {
powerOffButtonPressed_ts = millis();
if (powerOffMessagePrinted) Serial.println("Power off cancelled.");
powerOffMessagePrinted = false;
}
#if defined(AUTOSTART_CLOCK_AFTER_ms)
#if AUTOSTART_CLOCK_AFTER_ms > 0
@ -267,7 +259,6 @@ void loop(void)
#endif
fastclock.loop();
radio.loop();
display.showDashboard();
}

View File

@ -12,6 +12,8 @@ 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 {
@ -20,23 +22,6 @@ 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");
@ -46,7 +31,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(clockChannel);
rf24.setChannel(1);
rf24.setPALevel(RF24_PA_MAX);
rf24.setDataRate(RF24_2MBPS);
rf24.setAutoAck(0);
@ -65,77 +50,64 @@ 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();
switchToReceiverRole();
rf24.startListening();
}
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");
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::broadcastMessage(void *msg, int len) {
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");
switchToSenderRole(rf24);
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(m[1], HEX);
Serial.println(*((uint8_t *) (msg+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();
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
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, failed="); Serial.println(sendFailedCounter);
listenOnlyFor_ms(MIN_TIME_BETWEEN_TRANSMISSIONS_ms);
Serial.print("*** send finished, fail-counter="); 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;
@ -198,14 +170,14 @@ void Radio::ackPairingRequest(char *clientName, uint8_t clientNetworkAddress) {
// handle received messages
void Radio::handleRequestPairing(struct requestPairing_s *request) {
int i=0;
int i;
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) {
for (int i=0; i<numberOfKnownClients && !found; ++i) {
if (strncmp(&client[i].clientName[0], &request->clientName[0], MAX_CLIENT_NAME_LEN) == 0) found = true;
}
if (!found) {
@ -234,82 +206,24 @@ 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
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 :-)
}
}
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]);
}
}
@ -323,7 +237,8 @@ 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
}

View File

@ -61,29 +61,7 @@ struct textMessage_s {
class Radio {
public:
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;
};
Radio(Display *d, bool _isMaster):display(d), isMaster(_isMaster) { };
void begin(void);
void loop(void);
void broadcastClock(int day, int hour, int minute, int second, int msPerModelSecond);
@ -91,28 +69,9 @@ 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