Implemented most of the messages.
This commit is contained in:
parent
6eeb9177f9
commit
f6849ea534
37
README.md
37
README.md
|
@ -10,6 +10,43 @@ 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 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
--------- | ------- | --------- | ------
|
||||||
|
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) | ---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 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
|
||||||
|
|
|
@ -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
|
|
|
@ -21,5 +21,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
|
||||||
|
|
|
@ -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; };
|
||||||
|
|
|
@ -221,7 +221,7 @@ void loop(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!requestToRegisterSent) {
|
if (!requestToRegisterSent) {
|
||||||
radio.broadcastRequestRegistration("testClock");
|
radio.broadcastOfferPairing("testClock", nRF_Channel);
|
||||||
requestToRegisterSent = true;
|
requestToRegisterSent = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
139
src/radio.cpp
139
src/radio.cpp
|
@ -3,12 +3,26 @@
|
||||||
#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};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
uint8_t clientNetworkAddress;
|
||||||
|
char clientName[MAX_CLIENT_NAME_LEN];
|
||||||
|
} client[MAX_CLIENTS_MANAGED];
|
||||||
|
static uint8_t nextClientNetworkAddress = 1;
|
||||||
|
|
||||||
|
|
||||||
void Radio::begin(void) {
|
void Radio::begin(void) {
|
||||||
display->addLogMessage("Start RF24 radio");
|
display->addLogMessage("Start RF24 radio");
|
||||||
pinMode(PIN_NRF24_CSN, OUTPUT);
|
pinMode(PIN_NRF24_CSN, OUTPUT);
|
||||||
|
@ -53,8 +67,6 @@ void Radio::begin(void) {
|
||||||
rf24.startListening();
|
rf24.startListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool inTX=1, inRX=0, role=inRX;
|
|
||||||
|
|
||||||
static void switchToSenderRole(RF24 radio)
|
static void switchToSenderRole(RF24 radio)
|
||||||
{
|
{
|
||||||
Serial.println("*** master: CHANGING TO TRANSMIT ROLE");
|
Serial.println("*** master: CHANGING TO TRANSMIT ROLE");
|
||||||
|
@ -73,21 +85,14 @@ static void switchToReceiverRole(RF24 radio)
|
||||||
role = inRX; // Become the primary receiver (pong back)
|
role = inRX; // Become the primary receiver (pong back)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sendFailedCounter = 0;
|
void Radio::broadcastMessage(void *msg, int len) {
|
||||||
static unsigned long stopTime = 0, pauseTime = 0;
|
|
||||||
|
|
||||||
void Radio::broadcastRequestRegistration(const char *clockName) {
|
|
||||||
char sendBuffer[32];
|
|
||||||
|
|
||||||
memset(sendBuffer, 0, 32);
|
|
||||||
sendBuffer[0] = msgType_ReqReg;
|
|
||||||
strncpy(sendBuffer+1, clockName, MAX_CLOCK_NAME_LEN);
|
|
||||||
int msgLength = MAX_CLOCK_NAME_LEN + 1;
|
|
||||||
|
|
||||||
switchToSenderRole(rf24);
|
switchToSenderRole(rf24);
|
||||||
if (!rf24.writeFast(sendBuffer, msgLength, true /*multicast*/)) {
|
if (!rf24.writeFast(msg, len, true /*multicast*/)) {
|
||||||
sendFailedCounter++;
|
sendFailedCounter++;
|
||||||
Serial.print("*** ERROR: failed to send ReqRegistration msg for "); Serial.println(clockName);
|
Serial.print("*** ERROR: failed to send msg type=");
|
||||||
|
Serial.print(*((char *) msg));
|
||||||
|
Serial.print(" for to=0x");
|
||||||
|
Serial.println(*((uint8_t *) (msg+1)), HEX);
|
||||||
}
|
}
|
||||||
//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) {
|
||||||
|
@ -103,32 +108,102 @@ void Radio::broadcastRequestRegistration(const char *clockName) {
|
||||||
Serial.print("*** send finished, fail-counter="); Serial.println(sendFailedCounter);
|
Serial.print("*** send finished, fail-counter="); Serial.println(sendFailedCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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; 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::loop(void) {
|
void Radio::loop(void) {
|
||||||
|
|
62
src/radio.h
62
src/radio.h
|
@ -3,15 +3,75 @@
|
||||||
#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) { };
|
||||||
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);
|
||||||
private:
|
private:
|
||||||
Display *display;
|
Display *display;
|
||||||
bool isMaster;
|
bool isMaster;
|
||||||
|
void broadcastMessage(void *msg, int len);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue