Here’s my quick version of a Mesh Marco-Polo test. The gateway gets flashed with the “Marco” code where it requests that each node report with a “Polo” heartbeat. The nodes get flashed with the “Polo” code where they simple publish to the mesh when requested. The Marco code records the amount of time it takes to report, stores an array of known nodes and a count of how many known nodes responded.
UPDATED CODE ON 11/28/2018 11:46AM. Enabled System Threading for testing. Changed Particle.publish() format for better consumption in Losant. Added System.reset() if cloud or mesh is lost for more than 10 minutes.
Marco code (gateway):
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
bool heartbeat = false;
bool cloudPub = false;
unsigned long beatInterval = 10000;
unsigned long lastBeatTime = 0;
unsigned long beatTimeout = 5000;
unsigned long lastPoloTime = 0;
char knownNodes[10][50];
uint8_t knownNodeCount = 0;
bool reportingNodes[10];
uint16_t nodeReportCount = 0;
bool cloudLost = false;
unsigned long cloudLostTime = 0;
unsigned long cloudResetTimeout = 600000; //10 min = 600000
const char version[] = "MeshMarcoPoloHeartbeat_Marco 0.3";
void setup() {
Serial.begin(9600);
pinMode(D7, OUTPUT);
Particle.variable("version", version);
Particle.publish("Marco-Polo heartbeat test started.");
Mesh.subscribe("Polo", ProcessBeat);
ResetReportingNodes();
}
void loop() {
//Send heartbeat collection message every beatInterval.
if (!heartbeat && ((millis() - lastBeatTime) >= beatInterval)) {
ResetReportingNodes();
nodeReportCount = 0;
heartbeat = true;
lastBeatTime = millis();
lastPoloTime = lastBeatTime;
digitalWrite(D7, HIGH);
if (Mesh.ready()) {
Mesh.publish("Marco");
}
}
//Turn off LED after beat timeout.
if(heartbeat && ((millis() - lastBeatTime) >= beatTimeout)) {
heartbeat = false;
cloudPub = true;
digitalWrite(D7, LOW);
}
//Publish collected heartbeat results to cloud.
if(cloudPub) {
if (Particle.connected()) {
char msg[80];
snprintf(msg, arraySize(msg)-1, "Nodes:%d of %d;Millis:%d", nodeReportCount, knownNodeCount, lastPoloTime - lastBeatTime);
Particle.publish("MarcoPoloHeartbeat", msg, PRIVATE);
cloudPub = false;
//TODO: Report which knownNodes did not report.
}
else {
if (!cloudLost) {
cloudLostTime = millis();
}
cloudPub = false;
cloudLost = true;
}
}
//Check for lost cloud. Reset if down for more than cloudResetTimeout (default 10 min).
if (cloudLost) {
if (Particle.connected()) {
cloudLost = false;
} else {
if (millis() - cloudLostTime > cloudResetTimeout) {
System.reset();
}
}
}
}
void ProcessBeat(const char *name, const char *data) {
//Loop through known nodes array and look for matches.
for (int i; i < arraySize(knownNodes); i++) {
//If we get to a blank array slot, record this node there.
if (strcmp(knownNodes[i],"") == 0) {
snprintf(knownNodes[i], arraySize(knownNodes[i])-1, data);
//knownNodes[i] = data;
reportingNodes[i] = true;
nodeReportCount++;
knownNodeCount++;
lastPoloTime = millis();
break;
}
//If we encounter a node already known, just count it.
if (strcmp(knownNodes[i], data) == 0) {
nodeReportCount++;
reportingNodes[i] = true;
lastPoloTime = millis();
break;
}
}
}
void ResetReportingNodes() {
for (int i; i < arraySize(reportingNodes); i++) {
reportingNodes[i] = false;
}
}
Polo code (nodes):
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
bool heartbeat = false;
bool meshPub = false;
unsigned long lastBeatTime = 0;
unsigned long beatTimeout = 1000;
bool meshLost = false;
unsigned long meshLostTime = 0;
unsigned long meshResetTimeout = 600000; //10 min = 600000
const char version[] = "MeshMarcoPoloHeartbeat_Polo 0.3";
void setup() {
Serial.begin(9600);
pinMode(D7, OUTPUT);
Mesh.subscribe("Marco", ProcessBeat);
}
void loop() {
if (heartbeat && meshPub) {
if (Mesh.ready()) {
Mesh.publish("Polo", System.deviceID());
meshPub = false;
meshLost = false;
} else {
if (!meshLost) {
meshLostTime = millis();
}
meshPub = false;
meshLost = true;
}
}
//Turn off LED after beat timeout.
if(heartbeat && ((millis() - lastBeatTime) >= beatTimeout)) {
heartbeat = false;
digitalWrite(D7, LOW);
}
//Reset if mesh network is down longer than meshResetTimeout (default 10 min).
if (meshLost) {
if (Mesh.ready()) {
meshLost = false;
} else {
if (millis() - meshLostTime > meshResetTimeout) {
System.reset();
}
}
}
}
void ProcessBeat(const char *name, const char *data) {
heartbeat = true;
meshPub = true;
lastBeatTime = millis();
digitalWrite(D7,HIGH);
}
Footnote… since this is a multi-national community and colloquialisms might not translate well: Marco-Polo is a game played in a swimming pool. The person that is “it” closes their eyes and calls out “Marco!” All other persons in the pool that are part of the game are obligated to respond with “Polo!”. The “it” person then tries to tag one of the other players while keeping their eyes closed, while honing in on them by repeatedly calling out “Marco!”. A tagged person is either “out” or they become the “it” person depending on who makes the rules.
Update: The response time is generally under 100 ms but I have seen it spike up to 100-150 ms. I assume the time goes up with the number of devices. When doing OTA updates or adding a device to the network, the latency goes way up.