Compare commits
8 Commits
672d5baf23
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e4c173c6c | |||
| 91653e197f | |||
| 06b36ed3d0 | |||
| 97be2b6791 | |||
| f6849ea534 | |||
| 6eeb9177f9 | |||
| 2eda00c694 | |||
| fbf8af5684 |
43
README.md
43
README.md
@@ -10,6 +10,49 @@ The tasks of the master clock / controller is:
|
||||
* 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
|
||||
|
||||
# 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
|
||||
|
||||
- Digitrax Loconet PE: http://www.digitrax.com/static/apps/cms/media/documents/loconet/loconetpersonaledition.pdf
|
||||
|
||||
@@ -8,15 +8,18 @@
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:d1_mini_pro]
|
||||
platform = espressif8266
|
||||
board = d1_mini_pro
|
||||
framework = arduino
|
||||
[platformio]
|
||||
env_default = heltec_wifi_kit_8
|
||||
|
||||
[env:heltec_wifi_kit_8]
|
||||
platform = espressif8266
|
||||
board = heltec_wifi_kit_8
|
||||
framework = arduino
|
||||
|
||||
[env:d1_mini_pro]
|
||||
platform = espressif8266
|
||||
board = d1_mini_pro
|
||||
framework = arduino
|
||||
|
||||
[lib_deps]
|
||||
library = WifiManager, RF24, U8g2
|
||||
|
||||
12
src/config.h
12
src/config.h
@@ -13,9 +13,21 @@
|
||||
#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
|
||||
#define DEFAULT_RELAY_ACTIVE_LOW true
|
||||
|
||||
// field sizes
|
||||
#define MAX_CLOCK_NAME_LEN 8
|
||||
#define MAX_CLIENT_NAME_LEN 10
|
||||
#define MAX_TEXT_MESSAGE_LEN 10
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "display.h"
|
||||
|
||||
|
||||
// display
|
||||
//U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ 16, /* clock=*/ 5, /* data=*/ 4);
|
||||
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=16*/ -1, /* clock=*/ 5, /* data=*/ 4);
|
||||
@@ -63,15 +62,74 @@ void Display::begin(void) {
|
||||
setLargeTextSize();
|
||||
u8g2.drawStr(0,16,"Booting FastClock");
|
||||
u8g2.sendBuffer();
|
||||
delay(200);
|
||||
delay(400);
|
||||
//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) {
|
||||
static int step = 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!
|
||||
showBootClock();
|
||||
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);
|
||||
lastStep_ms = millis();
|
||||
@@ -81,20 +139,12 @@ bool Display::showBootSequenceFinished(unsigned int ms_per_step) {
|
||||
currentScreen = BootSequenceScreen;
|
||||
break;
|
||||
case 1:
|
||||
u8g2.drawPixel(100, 10);
|
||||
u8g2.drawPixel(102, 12);
|
||||
u8g2.drawPixel(104, 14);
|
||||
u8g2.drawPixel(106, 16);
|
||||
u8g2.sendBuffer();
|
||||
break;
|
||||
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;
|
||||
case 3:
|
||||
u8g2.drawXBMP(60, 0, 16, 16, logo16_glcd_bmp);
|
||||
u8g2.sendBuffer();
|
||||
//u8g2.drawXBMP(60, 0, 16, 16, logo16_glcd_bmp);
|
||||
//u8g2.sendBuffer();
|
||||
break;
|
||||
case 4:
|
||||
break; // empty step to be sure, that last step is displayed long enough
|
||||
@@ -142,7 +192,6 @@ void Display::showDashboard(void) {
|
||||
lastBlinkChange_ms = lastDisplayUpdate_ms;
|
||||
blinkOnCycle = !blinkOnCycle;
|
||||
}
|
||||
|
||||
u8g2.clearBuffer();
|
||||
u8g2.setDrawColor(1);
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
#ifndef display_h_included
|
||||
#define display_h_included
|
||||
|
||||
#include "config.h"
|
||||
|
||||
// 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 MAX_CLOCK_NAME_LEN 8
|
||||
|
||||
#define MAX_CLOCK_SPEED_LEN 8
|
||||
#define MAX_CLOCK_WEEKDAY_LEN 4
|
||||
#define MAX_NUMBER_CLIENTS_DISPLAYED 5
|
||||
#define MAX_CLIENT_NAME_LEN 10
|
||||
#define MAX_LOG_MESSAGE_LEN 30
|
||||
#define MAX_NUMBER_LOG_LINES 5
|
||||
|
||||
@@ -27,7 +28,7 @@ public:
|
||||
clockHour = hour;
|
||||
clockMinute = minute;
|
||||
Serial.print("display: new time "); Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
|
||||
}
|
||||
};
|
||||
//void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); };
|
||||
void setClockWeekday(const char *weekday) { strncpy(clockWeekday, weekday, MAX_CLOCK_WEEKDAY_LEN); };
|
||||
void setClockHalted(bool halted) { clockHalted = halted; };
|
||||
@@ -57,5 +58,6 @@ private:
|
||||
int getTextCharsPerLine(void);
|
||||
void setNormalTextSize(void);
|
||||
void setLargeTextSize(void);
|
||||
void showBootClock(void);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -67,6 +67,7 @@ void Fastclock::loop(void) {
|
||||
// time has changed, send an update via rf
|
||||
// @TODO implement sending radio message
|
||||
Serial.println("Would send new time");
|
||||
radio->broadcastClock(weekday, hour, minute, second, msPerModelSecond);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#ifndef fastclock_h_included
|
||||
#define fastclock_h_included
|
||||
|
||||
#include "config.h"
|
||||
#include "display.h"
|
||||
#include "radio.h"
|
||||
|
||||
#define MIN_TIME_ms_BETWEEN_CLOCK_UPDATES 200
|
||||
#define DEFAULT_ms_PER_MODEL_SECOND 250
|
||||
|
||||
class Fastclock {
|
||||
public:
|
||||
Fastclock(Display *d): display(d) { weekday=0; hour=0; minute=0; second=0; millisecond=0; msPerModelSecond=DEFAULT_ms_PER_MODEL_SECOND; halted = true; };
|
||||
Fastclock(Display *d, Radio *r): display(d), radio(r) { weekday=0; hour=0; minute=0; second=0; millisecond=0; msPerModelSecond=DEFAULT_ms_PER_MODEL_SECOND; halted = true; };
|
||||
void begin(void);
|
||||
void loop(void);
|
||||
void incrementClockByMilliseconds(int amount);
|
||||
@@ -20,6 +22,7 @@ public:
|
||||
void setClockHalted(bool setToHalt) { halted = setToHalt; display->setClockHalted(halted); };
|
||||
private:
|
||||
Display *display;
|
||||
Radio *radio;
|
||||
unsigned long lastTimeTickUsed; // used to calculate model time
|
||||
unsigned long msPerModelSecond; // 500 = twice as fast as real time
|
||||
uint8_t weekday;
|
||||
|
||||
61
src/main.cpp
61
src/main.cpp
@@ -16,6 +16,7 @@
|
||||
#include "radio.h"
|
||||
|
||||
#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.
|
||||
//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_sn[16] = "255.255.255.0";
|
||||
|
||||
char clockName[MAX_CLOCK_NAME_LEN+1] = "fastclk";
|
||||
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
|
||||
|
||||
//flag for saving data
|
||||
bool shouldSaveConfig = false;
|
||||
|
||||
@@ -34,8 +38,8 @@ bool shouldSaveConfig = false;
|
||||
bool isMaster = true;
|
||||
|
||||
Display display;
|
||||
Fastclock fastclock(&display);
|
||||
Radio radio(&display, true /*isMaster*/);
|
||||
Fastclock fastclock(&display, &radio);
|
||||
|
||||
//callback notifying us of the need to save config
|
||||
void saveConfigCallback () {
|
||||
@@ -134,9 +138,12 @@ void setupWifiConnection() {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// if coming from deep sleep, we just go to sleep again
|
||||
|
||||
// put your setup code here, to run once:
|
||||
Serial.begin(115200);
|
||||
Serial.println("Starting *** FastclockMasterESP8266");
|
||||
Serial.println(ESP.getResetReason());
|
||||
display.begin();
|
||||
display.showLog();
|
||||
|
||||
@@ -203,16 +210,49 @@ 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
|
||||
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)
|
||||
{
|
||||
if (!display.showBootSequenceFinished(1000)) {
|
||||
if (!display.showBootSequenceFinished(4000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkForPowerOffRequest();
|
||||
|
||||
#if defined(AUTOSTART_CLOCK_AFTER_ms)
|
||||
#if AUTOSTART_CLOCK_AFTER_ms > 0
|
||||
static unsigned long autostart_ms = 0;
|
||||
@@ -227,7 +267,20 @@ void loop(void)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
fastclock.loop();
|
||||
display.showDashboard();
|
||||
// we do not want to call every task on every cycle:
|
||||
switch (millis() & 0x03) {
|
||||
case 0:
|
||||
fastclock.loop();
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
display.showDashboard();
|
||||
break;
|
||||
case 3:
|
||||
break;
|
||||
}
|
||||
yield();
|
||||
radio.loop(); // called always
|
||||
|
||||
}
|
||||
|
||||
371
src/radio.cpp
371
src/radio.cpp
@@ -4,18 +4,45 @@
|
||||
#include "config.h"
|
||||
#include "radio.h"
|
||||
|
||||
|
||||
static RF24 rf24(PIN_NRF24_CE, PIN_NRF24_CSN); // 10, 8
|
||||
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");
|
||||
pinMode(PIN_NRF24_CSN, OUTPUT);
|
||||
pinMode(PIN_NRF24_CE, OUTPUT);
|
||||
return;
|
||||
|
||||
rf24.begin();
|
||||
if (rf24.isChipConnected()) { display->addLogMessage("*** RF chip found"); }
|
||||
else { display->addLogMessage("*** ERROR: RF chip not found!"); }
|
||||
rf24.setChannel(1);
|
||||
if (rf24.isChipConnected()) {
|
||||
display->addLogMessage("*** RF chip found");
|
||||
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.setDataRate(RF24_2MBPS);
|
||||
rf24.setAutoAck(0);
|
||||
@@ -24,91 +51,309 @@ void Radio::begin(void) {
|
||||
rf24.openWritingPipe(addresses[0]);
|
||||
rf24.openReadingPipe(1, addresses[1]);
|
||||
rf24.startListening();
|
||||
rf24.printDetails();
|
||||
rf24.powerUp();
|
||||
// @TODO: real random seed!
|
||||
//randomSeed(analogRead(0));
|
||||
//randomSeed(22);
|
||||
rf24.powerUp();
|
||||
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!"); }
|
||||
}
|
||||
|
||||
void Radio::powerDown(void) {
|
||||
rf24.powerDown();
|
||||
}
|
||||
|
||||
#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]);
|
||||
void Radio::switchToSenderRole(void)
|
||||
{
|
||||
Serial.println(F("# Tx"));
|
||||
rf24.openWritingPipe(addresses[1]);
|
||||
rf24.openReadingPipe(1,addresses[0]);
|
||||
rf24.stopListening();
|
||||
role = inTX; // Become the primary transmitter (ping out)
|
||||
pauseTime = millis();
|
||||
checkRadioFailure();
|
||||
}
|
||||
|
||||
void Radio::switchToReceiverRole(void)
|
||||
{
|
||||
Serial.println(F("# Rx"));
|
||||
rf24.openWritingPipe(addresses[0]);
|
||||
rf24.openReadingPipe(1,addresses[1]);
|
||||
rf24.startListening();
|
||||
role = inRX; // Become the primary receiver (pong back)
|
||||
checkRadioFailure();
|
||||
}
|
||||
|
||||
void Radio::begin(void) {
|
||||
display->addLogMessage("Start RF24 radio");
|
||||
pinMode(PIN_NRF24_CSN, OUTPUT);
|
||||
pinMode(PIN_NRF24_CE, OUTPUT);
|
||||
|
||||
radioInit();
|
||||
Serial.print(F("*** RF payload size=")); Serial.print(rf24.getPayloadSize()); Serial.println(F(" bytes"));
|
||||
if (rf24.testCarrier() || rf24.testRPD()) {
|
||||
display->addLogMessage("*** Carrier/RPD seen on radio");
|
||||
Serial.println(F("*** Carrier/RPD seen on radio"));
|
||||
}
|
||||
if (rf24.failureDetected) {
|
||||
display->addLogMessage("*** Radio error detected!");
|
||||
Serial.println(F("*** ERROR: Radio error detected!"));
|
||||
}
|
||||
|
||||
#endif
|
||||
switchToReceiverRole();
|
||||
}
|
||||
|
||||
// Start the radio listening for data
|
||||
rf24.startListening();
|
||||
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
|
||||
if (millis() - pauseTime > 3) {
|
||||
pauseTime = millis();
|
||||
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
|
||||
}
|
||||
//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
|
||||
//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
|
||||
|
||||
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) {
|
||||
struct clockMsg_s clockMsg;
|
||||
|
||||
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.hour = hour;
|
||||
clockMsg.minute = minute;
|
||||
clockMsg.second = second;
|
||||
clockMsg.speed = (msPerModelSecond >> 2) & 0xff; // divide by 4
|
||||
|
||||
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) {
|
||||
#if 0
|
||||
// put your main code here, to run repeatedly:
|
||||
/****************** Ping Out Role ***************************/
|
||||
if (isMaster) {
|
||||
if (rf24.available()) {
|
||||
char buffer[32];
|
||||
rf24.read(buffer, 32);
|
||||
|
||||
radio.stopListening();
|
||||
|
||||
Serial.println(F("Now sending"));
|
||||
|
||||
unsigned long start_time = micros();
|
||||
if (!radio.write(&start_time, sizeof(unsigned long))) {
|
||||
Serial.println(F("failed"));
|
||||
}
|
||||
|
||||
radio.startListening();
|
||||
|
||||
unsigned long started_waiting_at = micros();
|
||||
boolean timeout = false;
|
||||
|
||||
while (!radio.available()) {
|
||||
if (micros() - started_waiting_at > 200000 ) { // If waited longer than 200ms, indicate timeout and exit while loop
|
||||
timeout = true;
|
||||
break;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
Serial.println(F("Failed, response timed out."));
|
||||
} else {
|
||||
unsigned long got_time;
|
||||
radio.read( &got_time, sizeof(unsigned long) );
|
||||
unsigned long end_time = micros();
|
||||
|
||||
// Spew it
|
||||
Serial.print(F("Sent "));
|
||||
Serial.print(start_time);
|
||||
Serial.print(F(", Got response "));
|
||||
Serial.print(got_time);
|
||||
Serial.print(F(", Round-trip delay "));
|
||||
Serial.print(end_time-start_time);
|
||||
Serial.println(F(" microseconds"));
|
||||
}
|
||||
|
||||
// Try again 1s later
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Tx handling
|
||||
if (allowedToSendNow()) {
|
||||
if (!checkPairingOffering()) {
|
||||
if (!checkClockAdvertisement()) {
|
||||
// nothing sent :-)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
/****************** Pong Back Role ***************************/
|
||||
|
||||
if (!isMaster)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
108
src/radio.h
108
src/radio.h
@@ -3,13 +3,119 @@
|
||||
#include "config.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 {
|
||||
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);
|
||||
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:
|
||||
Display *display;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user