初始化提交

This commit is contained in:
王立帮
2024-07-20 22:09:06 +08:00
commit c247dd07a6
6876 changed files with 2743096 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
/**
* Wrapper file, which is used to test on PC hardware
*/
#ifndef ARDUINO_WRAP_H
#define ARDUINO_WRAP_H
#include <sys/time.h>
#include <unistd.h>
#define F(string_literal) string_literal
#define ARDUINO_ARCH_ESP8266
#define PAINLESSMESH_BOOST
#ifndef NULL
#define NULL 0
#endif
inline unsigned long millis() {
struct timeval te;
gettimeofday(&te, NULL); // get current time
long long milliseconds =
te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
// printf("milliseconds: %lld\n", milliseconds);
return milliseconds;
}
inline unsigned long micros() {
struct timeval te;
gettimeofday(&te, NULL); // get current time
long long milliseconds = te.tv_sec * 1000000LL + te.tv_usec;
return milliseconds;
}
inline void delay(int i) { usleep(i); }
inline void yield() {}
/**
* Override the configution file.
**/
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
#define _PAINLESS_MESH_CONFIGURATION_HPP_
#define _TASK_PRIORITY // Support for layered scheduling priority
#define _TASK_STD_FUNCTION
#include <TaskSchedulerDeclarations.h>
#define ARDUINOJSON_USE_LONG_LONG 1
#include <ArduinoJson.h>
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
#define ICACHE_FLASH_ATTR
#define PAINLESSMESH_ENABLE_STD_STRING
#define PAINLESSMESH_ENABLE_OTA
#define NODE_TIMEOUT 5 * TASK_SECOND
typedef std::string TSTRING;
#ifdef ESP32
#define MAX_CONN 10
#else
#define MAX_CONN 4
#endif // DEBUG
#include "boost/asynctcp.hpp"
#include "fake_serial.hpp"
typedef enum {
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
WL_IDLE_STATUS = 0,
WL_NO_SSID_AVAIL = 1,
WL_SCAN_COMPLETED = 2,
WL_CONNECTED = 3,
WL_CONNECT_FAILED = 4,
WL_CONNECTION_LOST = 5,
WL_DISCONNECTED = 6
} wl_status_t;
class WiFiClass {
public:
void disconnect() {}
auto status() { return WL_CONNECTED; }
};
class ESPClass {
public:
size_t getFreeHeap() { return 1e6; }
};
extern WiFiClass WiFi;
extern ESPClass ESP;
#endif
#endif

View File

@@ -0,0 +1,399 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "Arduino.h"
#include "catch_utils.hpp"
#include "boost/asynctcp.hpp"
WiFiClass WiFi;
ESPClass ESP;
#include "painlessMeshConnection.h"
#include "painlessmesh/mesh.hpp"
using PMesh = painlessmesh::Mesh<MeshConnection>;
using namespace painlessmesh;
painlessmesh::logger::LogClass Log;
class MeshTest : public PMesh {
public:
MeshTest(Scheduler *scheduler, size_t id, boost::asio::io_service &io)
: io_service(io) {
this->nodeId = id;
this->init(scheduler, this->nodeId);
timeOffset = runif(0, 1e09);
pServer = std::make_shared<AsyncServer>(io_service, this->nodeId);
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, (*this));
}
void connect(MeshTest &mesh) {
auto pClient = new AsyncClient(io_service);
painlessmesh::tcp::connect<MeshConnection, PMesh>(
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"),
mesh.nodeId, (*this));
}
std::shared_ptr<AsyncServer> pServer;
boost::asio::io_service &io_service;
};
class Nodes {
public:
Nodes(Scheduler *scheduler, size_t n, boost::asio::io_service &io)
: io_service(io) {
for (size_t i = 0; i < n; ++i) {
auto m = std::make_shared<MeshTest>(scheduler, i + baseID, io_service);
if (i > 0) m->connect((*nodes[runif(0, i - 1)]));
nodes.push_back(m);
}
}
void update() {
for (auto &&m : nodes) {
m->update();
io_service.poll();
}
}
void stop() {
for (auto &&m : nodes) m->stop();
}
auto size() { return nodes.size(); }
std::shared_ptr<MeshTest> get(size_t nodeId) {
return nodes[nodeId - baseID];
}
size_t baseID = 6481;
std::vector<std::shared_ptr<MeshTest>> nodes;
boost::asio::io_service &io_service;
};
SCENARIO("We can setup and connect two meshes over localport") {
using namespace logger;
Scheduler scheduler;
Log.setLogLevel(ERROR);
boost::asio::io_service io_service;
PMesh mesh1;
mesh1.init(&scheduler, 6841);
std::shared_ptr<AsyncServer> pServer;
pServer = std::make_shared<AsyncServer>(io_service, 6841);
painlessmesh::tcp::initServer<MeshConnection, PMesh>(*pServer, mesh1);
PMesh mesh2;
mesh2.init(&scheduler, 6842);
auto pClient = new AsyncClient(io_service);
painlessmesh::tcp::connect<MeshConnection, PMesh>(
(*pClient), boost::asio::ip::address::from_string("127.0.0.1"), 6841,
mesh2);
for (auto i = 0; i < 100; ++i) {
mesh1.update();
mesh2.update();
io_service.poll();
}
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
}
SCENARIO("The MeshTest class works correctly") {
using namespace logger;
Scheduler scheduler;
Log.setLogLevel(ERROR);
boost::asio::io_service io_service;
MeshTest mesh1(&scheduler, 6841, io_service);
MeshTest mesh2(&scheduler, 6842, io_service);
mesh2.connect(mesh1);
for (auto i = 0; i < 100; ++i) {
mesh1.update();
mesh2.update();
io_service.poll();
}
REQUIRE(layout::size(mesh1.asNodeTree()) == 2);
REQUIRE(layout::size(mesh2.asNodeTree()) == 2);
}
SCENARIO("We can send a message using our Nodes class") {
delay(1000);
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
Nodes n(&scheduler, 12, io_service);
for (auto i = 0; i < 1000; ++i) {
n.update();
delay(10);
}
REQUIRE(layout::size(n.nodes[0]->asNodeTree()) == 12);
int x = 0;
int y = 0;
std::string z;
n.nodes[0]->onReceive([&x, &y, &z](auto id, auto msg) {
++x;
y = id;
z = msg;
});
n.nodes[10]->sendSingle(n.nodes[2]->getNodeId(), "Blaat");
for (auto i = 0; i < 1000; ++i) n.update();
REQUIRE(x == 0);
REQUIRE(y == 0);
REQUIRE(z == "");
n.nodes[10]->sendSingle(n.nodes[0]->getNodeId(), "Blaat");
for (auto i = 0; i < 1000; ++i) n.update();
REQUIRE(z == "Blaat");
REQUIRE(x == 1);
REQUIRE(y == n.nodes[10]->getNodeId());
n.nodes[5]->onReceive([&x, &y, &z](auto id, auto msg) {
++x;
y = id;
z = msg;
});
n.nodes[10]->sendBroadcast("Blargh");
for (auto i = 0; i < 10000; ++i) n.update();
REQUIRE(z == "Blargh");
REQUIRE(x == 3);
REQUIRE(y == n.nodes[10]->getNodeId());
n.stop();
}
SCENARIO("Time sync works") {
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
auto dim = runif(8, 15);
Nodes n(&scheduler, dim, io_service);
int diff = 0;
for (size_t i = 0; i < n.size() - 1; ++i) {
diff += std::abs((int)n.nodes[0]->getNodeTime() -
(int)n.nodes[i + 1]->getNodeTime());
}
REQUIRE(diff / n.size() > 10000);
for (auto i = 0; i < 10000; ++i) {
n.update();
delay(10);
}
diff = 0;
for (size_t i = 0; i < n.size() - 1; ++i) {
diff += std::abs((int)n.nodes[0]->getNodeTime() -
(int)n.nodes[i + 1]->getNodeTime());
}
REQUIRE(diff / n.size() < 10000);
n.stop();
}
SCENARIO("Rooting works") {
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
auto dim = runif(8, 15);
Nodes n(&scheduler, dim, io_service);
n.nodes[5]->setRoot(true);
REQUIRE(n.nodes[5]->isRoot());
REQUIRE(layout::isRooted(n.nodes[5]->asNodeTree()));
for (auto i = 0; i < 10000; ++i) {
n.update();
delay(10);
}
for (auto &&node : n.nodes) {
REQUIRE(layout::isRooted(node->asNodeTree()));
if (n.nodes[5]->getNodeId() == node->getNodeId()) {
REQUIRE(node->isRoot());
} else {
REQUIRE(!node->isRoot());
}
}
n.stop();
}
SCENARIO("Network loops are detected") {
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
MeshTest mesh1(&scheduler, 6841, io_service);
MeshTest mesh2(&scheduler, 6842, io_service);
MeshTest mesh3(&scheduler, 6843, io_service);
MeshTest mesh4(&scheduler, 6844, io_service);
MeshTest mesh5(&scheduler, 6845, io_service);
mesh1.connect(mesh5);
mesh2.connect(mesh1);
mesh3.connect(mesh2);
mesh4.connect(mesh3);
mesh5.connect(mesh4);
for (auto i = 0; i < 10000; ++i) {
mesh1.update();
io_service.poll();
mesh2.update();
io_service.poll();
mesh3.update();
io_service.poll();
mesh4.update();
io_service.poll();
mesh5.update();
io_service.poll();
delay(10);
}
// Looped network, should break up so it can reform
REQUIRE(layout::size(mesh1.asNodeTree()) < 5);
REQUIRE(layout::size(mesh2.asNodeTree()) < 5);
REQUIRE(layout::size(mesh3.asNodeTree()) < 5);
REQUIRE(layout::size(mesh4.asNodeTree()) < 5);
REQUIRE(layout::size(mesh5.asNodeTree()) < 5);
mesh1.stop();
mesh2.stop();
mesh3.stop();
mesh4.stop();
mesh5.stop();
}
SCENARIO("Disconnects are detected and forwarded") {
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
auto dim = runif(10, 15);
Nodes n(&scheduler, dim, io_service);
// Dummy task. This can catch mistaken use of the scheduler
Task dummyT;
int y = 0;
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
scheduler.addTask(dummyT);
dummyT.enable();
for (auto i = 0; i < 1000; ++i) {
n.update();
delay(10);
}
for (auto &&node : n.nodes) {
REQUIRE(layout::size(node->asNodeTree()) == dim);
}
int x = 0;
n.nodes[5]->onChangedConnections([&x]() { ++x; });
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
auto no = n.nodes[5]->subs.size();
REQUIRE(no > 0);
auto ptr = (*n.nodes[5]->subs.begin());
(*n.nodes[5]->subs.begin())->close();
for (auto i = 0; i < 1000; ++i) {
n.update();
delay(10);
}
REQUIRE(n.nodes[5]->subs.size() == no - 1);
REQUIRE(ptr.use_count() == 1);
ptr = NULL;
REQUIRE(x == 3);
for (auto &&node : n.nodes) {
REQUIRE(layout::size(node->asNodeTree()) < dim);
}
n.stop();
REQUIRE(y > 0);
}
SCENARIO("Disconnects don't lead to crashes") {
using namespace logger;
Log.setLogLevel(ERROR);
Scheduler scheduler;
boost::asio::io_service io_service;
auto dim = runif(10, 15);
Nodes n(&scheduler, dim, io_service);
// Dummy task. This can catch mistaken use of the scheduler
Task dummyT;
int y = 0;
dummyT.set(TASK_MILLISECOND, TASK_FOREVER, [&y]() { ++y; });
scheduler.addTask(dummyT);
dummyT.enable();
for (auto i = 0; i < 1000; ++i) {
n.update();
delay(10);
}
for (auto &&node : n.nodes) {
REQUIRE(layout::size(node->asNodeTree()) == dim);
}
int x = 0;
n.nodes[5]->onChangedConnections([&x]() { ++x; });
n.nodes[5]->onDroppedConnection([&x](auto nodeId) { ++x; });
n.nodes[dim - 1]->onChangedConnections([&x]() { ++x; });
auto no = n.nodes[5]->subs.size();
REQUIRE(no > 0);
auto ptr = (*n.nodes[5]->subs.begin());
(*n.nodes[5]->subs.begin())->close();
for (auto i = 0; i < 10; ++i) {
io_service.poll();
}
n.update();
for (auto i = 0; i < 10; ++i) {
io_service.poll();
}
for (auto i = 0; i < 1000; ++i) {
n.update();
delay(10);
}
REQUIRE(n.nodes[5]->subs.size() == no - 1);
REQUIRE(ptr.use_count() == 1);
ptr = NULL;
REQUIRE(x == 3);
for (auto &&node : n.nodes) {
REQUIRE(layout::size(node->asNodeTree()) < dim);
}
n.stop();
REQUIRE(y > 0);
}

View File

@@ -0,0 +1,84 @@
/**
* Wrapper file, which is used to test on PC hardware
*/
#ifndef ARDUINO_WRAP_H
#define ARDUINO_WRAP_H
#include <sys/time.h>
#include <unistd.h>
#define F(string_literal) string_literal
#define ARDUINO_ARCH_ESP8266
#define PAINLESSMESH_BOOST
#ifndef NULL
#define NULL 0
#endif
inline unsigned long millis() {
struct timeval te;
gettimeofday(&te, NULL); // get current time
long long milliseconds =
te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
// printf("milliseconds: %lld\n", milliseconds);
return milliseconds;
}
inline unsigned long micros() {
struct timeval te;
gettimeofday(&te, NULL); // get current time
long long milliseconds = te.tv_sec * 1000000LL + te.tv_usec;
return milliseconds;
}
inline void delay(int i) { usleep(i); }
inline void yield() {}
struct IPAddress {
IPAddress() {}
IPAddress(int, int, int, int) {}
};
/**
* Override the configution file.
**/
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
#define _PAINLESS_MESH_CONFIGURATION_HPP_
#define _TASK_PRIORITY // Support for layered scheduling priority
#define _TASK_STD_FUNCTION
#include <TaskSchedulerDeclarations.h>
#define ARDUINOJSON_USE_LONG_LONG 1
#include <ArduinoJson.h>
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
#define ICACHE_FLASH_ATTR
#define PAINLESSMESH_ENABLE_STD_STRING
// Enable OTA support
#define PAINLESSMESH_ENABLE_OTA
#define NODE_TIMEOUT 5 * TASK_SECOND
typedef std::string TSTRING;
#ifdef ESP32
#define MAX_CONN 10
#else
#define MAX_CONN 4
#endif // DEBUG
#include "fake_asynctcp.hpp"
#include "fake_serial.hpp"
extern WiFiClass WiFi;
extern ESPClass ESP;
#endif
#endif

View File

@@ -0,0 +1,18 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/base64.hpp"
#include "catch_utils.hpp"
SCENARIO("Base64 encoding can succesfully be decoded") {
using namespace painlessmesh;
auto bindata = randomString(100);
auto enc = base64::encode(bindata);
auto dec = base64::decode(enc);
REQUIRE(dec.length() > 0);
REQUIRE(dec == bindata);
}

View File

@@ -0,0 +1,285 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#define ARDUINOJSON_USE_LONG_LONG 1
#include "ArduinoJson.h"
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
#undef PAINLESSMESH_ENABLE_ARDUINO_STRING
#define PAINLESSMESH_ENABLE_STD_STRING
typedef std::string TSTRING;
#include "catch_utils.hpp"
#include "painlessmesh/buffer.hpp"
using namespace painlessmesh::buffer;
SCENARIO("ReceiveBuffer receives strings and needs to process them") {
temp_buffer_t tmp_buffer;
char cstring[3 * tmp_buffer.length];
ReceiveBuffer<std::string> rBuffer = ReceiveBuffer<std::string>();
GIVEN("A random string of short length pushed to the received buffer") {
REQUIRE(rBuffer.empty());
auto length = runif(10, tmp_buffer.length - 10);
randomCString(cstring, length);
// Note we need to send a \0 to know this is the end
rBuffer.push(cstring, length + 1, tmp_buffer);
THEN("It gets copied to the front of the buffer") {
REQUIRE(!rBuffer.empty());
REQUIRE(rBuffer.front() == std::string(cstring));
}
}
GIVEN("A random string of long length pushed to the received buffer") {
REQUIRE(rBuffer.empty());
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
randomCString(cstring, length);
// Note we need to send a \0 to know this is the end
rBuffer.push(cstring, length + 1, tmp_buffer);
THEN("It gets copied to the front of the buffer") {
REQUIRE(!rBuffer.empty());
REQUIRE(rBuffer.front() == std::string(cstring));
}
}
GIVEN("A random string we can push it in multiple parts") {
REQUIRE(rBuffer.empty());
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
size_t part_len = length / 2;
randomCString(cstring, length);
rBuffer.push(cstring, part_len, tmp_buffer);
THEN("The first part doesn't get copied to the front of the buffer") {
REQUIRE(rBuffer.empty());
}
auto data_ptr = cstring + sizeof(char) * part_len;
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
THEN(
"When getting the second part the whole thing gets copied to the front "
"of the buffer") {
REQUIRE(!rBuffer.empty());
REQUIRE(rBuffer.front() == std::string(cstring));
}
}
GIVEN("ReceiveBuffer can receive multiple messages and hold them all") {
REQUIRE(rBuffer.empty());
for (size_t i = 0; i < 10; ++i) {
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
size_t part_len = length / 2;
randomCString(cstring, length);
rBuffer.push(cstring, part_len, tmp_buffer);
auto data_ptr = cstring + sizeof(char) * part_len;
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
}
THEN(
"When getting the second part the whole thing gets copied to the front "
"of the buffer") {
REQUIRE(!rBuffer.empty());
for (size_t i = 0; i < 10; ++i) {
REQUIRE(!rBuffer.empty());
rBuffer.pop_front();
}
REQUIRE(rBuffer.empty());
}
}
GIVEN(
"ReceiveBuffer can receive multiple messages in one char string "
"(separated by \0") {
REQUIRE(rBuffer.empty());
auto length = runif(10, tmp_buffer.length - 10);
randomCString(cstring, length);
auto data_ptr = cstring + sizeof(char) * (length + 1);
auto length2 = runif(10, tmp_buffer.length - 10);
randomCString(data_ptr, length2);
// Note we need to send a \0 to know this is the end
rBuffer.push(cstring, length + length2 + 2, tmp_buffer);
THEN("We have both strings in the buffer") {
REQUIRE(!rBuffer.empty());
REQUIRE(rBuffer.front() == std::string(cstring));
rBuffer.pop_front();
REQUIRE(!rBuffer.empty());
REQUIRE(rBuffer.front() == std::string(data_ptr));
rBuffer.pop_front();
REQUIRE(rBuffer.empty());
}
}
GIVEN(
"ReceiveBuffer has copied the message we can overwrite the previous "
"cstring and buffer without affecting the outcome") {
REQUIRE(rBuffer.empty());
cstring[0] = 'B';
cstring[1] = 'l';
cstring[2] = 'a';
cstring[3] = 'a';
cstring[4] = 't';
cstring[5] = '\0';
rBuffer.push(cstring, 3, tmp_buffer);
cstring[0] = 'r';
cstring[1] = 'n';
cstring[2] = 'd';
randomCString(tmp_buffer.buffer, tmp_buffer.length);
auto data_ptr = cstring + sizeof(char) * 3;
rBuffer.push(data_ptr, 3, tmp_buffer);
THEN("We still have the correct result") {
REQUIRE(rBuffer.front() == std::string("Blaat"));
REQUIRE(std::string(cstring) != std::string("Blaat"));
REQUIRE(std::string(tmp_buffer.buffer, 6) != std::string("Blaat"));
REQUIRE(std::string(tmp_buffer.buffer, 5) != std::string("Blaat"));
}
}
GIVEN("A buffer with multiple messages") {
REQUIRE(rBuffer.empty());
for (size_t i = 0; i < 10; ++i) {
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
size_t part_len = length / 2;
randomCString(cstring, length);
rBuffer.push(cstring, part_len, tmp_buffer);
auto data_ptr = cstring + sizeof(char) * part_len;
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
}
THEN("We can clear it") {
REQUIRE(!rBuffer.empty());
rBuffer.clear();
REQUIRE(rBuffer.empty());
}
}
GIVEN("A buffer with a half written message") {
REQUIRE(rBuffer.empty());
for (size_t i = 0; i < 10; ++i) {
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
size_t part_len = length / 2;
randomCString(cstring, length);
rBuffer.push(cstring, part_len, tmp_buffer);
auto data_ptr = cstring + sizeof(char) * part_len;
rBuffer.push(data_ptr, length - part_len + 1, tmp_buffer);
}
auto length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length);
size_t part_len = length / 2;
randomCString(cstring, length);
rBuffer.push(cstring, part_len, tmp_buffer);
THEN("We can clear removes it") {
REQUIRE(!rBuffer.empty());
rBuffer.clear();
REQUIRE(rBuffer.empty());
}
rBuffer.clear();
randomCString(cstring, length);
rBuffer.push(cstring, length + 1, tmp_buffer);
THEN("Reusing it works correctly") {
REQUIRE(rBuffer.front() == std::string(cstring));
}
}
}
SCENARIO("SentBuffer receives strings and can be read in parts") {
temp_buffer_t tmp_buffer;
SentBuffer<std::string> sBuffer = SentBuffer<std::string>();
GIVEN("A SentBuffer and a string") {
auto length = runif(0, tmp_buffer.length - 10);
auto msg = randomString(length);
THEN("We can pass strings to it") {
REQUIRE(sBuffer.empty());
sBuffer.push(msg);
REQUIRE(!sBuffer.empty());
}
THEN("We can pass it and read it back") {
REQUIRE(sBuffer.empty());
sBuffer.push(msg);
REQUIRE(!sBuffer.empty());
auto rlength = sBuffer.requestLength(2 * length);
REQUIRE(rlength <= 2 * length);
sBuffer.read(rlength, tmp_buffer);
if (rlength == msg.length() + 1)
REQUIRE(std::string(tmp_buffer.buffer, msg.length()) == msg);
// Test free read as well
sBuffer.freeRead();
if (rlength == msg.length() + 1) REQUIRE(sBuffer.empty());
}
}
// We can read in multiple parts
GIVEN("A long string passed to the SentBuffer") {
size_t length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length - 10);
auto msg = randomString(length);
sBuffer.push(msg);
char cstring[length + 1];
auto data_ptr = cstring;
THEN("We can read it in multiple parts") {
while (!sBuffer.empty()) {
auto rlength = sBuffer.requestLength(tmp_buffer.length);
sBuffer.read(rlength, tmp_buffer);
memcpy(data_ptr, tmp_buffer.buffer, rlength);
data_ptr += rlength * sizeof(char);
sBuffer.freeRead();
}
REQUIRE(std::string(cstring) == msg);
}
THEN("We can use direct access to read it in multiple parts") {
while (!sBuffer.empty()) {
auto rlength = sBuffer.requestLength(tmp_buffer.length);
auto ptr = sBuffer.readPtr(rlength);
memcpy(data_ptr, ptr, rlength);
data_ptr += rlength * sizeof(char);
sBuffer.freeRead();
}
REQUIRE(std::string(cstring) == msg);
}
}
GIVEN(
"We have read a message halfway, priority messages can safely be added") {
size_t length = runif(tmp_buffer.length + 10, 2 * tmp_buffer.length - 10);
auto msg1 = randomString(length);
auto msg2 = randomString(length);
auto msgH = randomString(length);
sBuffer.push(msg1);
char cstring[3 * length + 3];
auto data_ptr = cstring;
THEN("We can read it in multiple parts") {
auto rlength = sBuffer.requestLength(tmp_buffer.length);
// Read first message halfway
sBuffer.read(rlength, tmp_buffer);
memcpy(data_ptr, tmp_buffer.buffer, rlength);
data_ptr += rlength * sizeof(char);
sBuffer.freeRead();
sBuffer.push(msg2);
sBuffer.push(msgH, true);
// Read the rest of the messages
while (!sBuffer.empty()) {
rlength = sBuffer.requestLength(tmp_buffer.length);
sBuffer.read(rlength, tmp_buffer);
memcpy(data_ptr, tmp_buffer.buffer, rlength);
data_ptr += rlength * sizeof(char);
sBuffer.freeRead();
}
REQUIRE(std::string(cstring) == msg1);
REQUIRE(std::string(cstring + length + 1) == msgH);
REQUIRE(std::string(cstring + 2 * (length + 1)) == msg2);
}
}
GIVEN("A SentBuffer with a message in it") {
auto length = runif(0, tmp_buffer.length - 10);
auto msg = randomString(length);
sBuffer.push(msg);
REQUIRE(!sBuffer.empty());
THEN("Clear will empty it") {
sBuffer.clear();
REQUIRE(sBuffer.empty());
}
}
}

View File

@@ -0,0 +1,43 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include <Arduino.h>
#include "catch_utils.hpp"
#include "painlessmesh/callback.hpp"
using namespace painlessmesh;
logger::LogClass Log;
SCENARIO("CallbackMap should hold multiple callbacks by ID") {
GIVEN("A callback map with added callbacks") {
auto cbl = callback::PackageCallbackList<int>();
auto i = 0;
auto j = 0;
cbl.onPackage(1, [&i](int z) { ++i; });
cbl.onPackage(1, [&j](int z) { ++j; });
WHEN("We call execute") {
auto cnt = cbl.execute(1, 0);
REQUIRE(cnt == 2);
THEN("The callbacks are called") {
REQUIRE(i == 1);
REQUIRE(j == 1);
}
}
WHEN("We call execute on another event") {
auto cnt = cbl.execute(2, 0);
REQUIRE(cnt == 0);
THEN("The callbacks are not called") {
REQUIRE(i == 0);
REQUIRE(j == 0);
}
}
}
}

View File

@@ -0,0 +1,177 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#define ARDUINOJSON_USE_LONG_LONG 1
#include "ArduinoJson.h"
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
#undef PAINLESSMESH_ENABLE_ARDUINO_STRING
#define PAINLESSMESH_ENABLE_STD_STRING
typedef std::string TSTRING;
#include "catch_utils.hpp"
#include "painlessmesh/layout.hpp"
#include "painlessmesh/protocol.hpp"
using namespace painlessmesh;
SCENARIO("isRoot returns true if the top level Node is the root of the mesh") {
GIVEN("A nodeTree with root as a top node") {
std::string rootJson =
"{\"type\":6,\"root\":true,\"dest\":2428398258,\"from\":3907768579,"
"\"subs\":[{"
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
"1895675348}]}]}";
auto variant = protocol::Variant(rootJson);
auto tree1 = variant.to<protocol::NodeTree>();
THEN("isRoot returns true") { REQUIRE(layout::isRoot(tree1)); }
}
GIVEN("A nodeTree without a root as a top node") {
std::string jsonTree1 =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant1 = protocol::Variant(jsonTree1);
auto tree1 = variant1.to<protocol::NodeTree>();
std::string jsonTree2 =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant2 = protocol::Variant(jsonTree2);
auto tree2 = variant2.to<protocol::NodeTree>();
THEN("isRoot returns false") {
REQUIRE(!layout::isRoot(tree1));
REQUIRE(!layout::isRoot(tree2));
}
}
GIVEN("A random tree with a root at top level") {
auto tree1 = createNodeTree(runif(1, 255), 0);
THEN("isRoot returns true") { REQUIRE(layout::isRoot(tree1)); }
}
GIVEN("A random tree with no root at top level") {
auto noNodes = runif(2, 255);
auto tree1 = createNodeTree(noNodes, runif(1, noNodes - 1));
auto tree2 = createNodeTree(runif(1, 255), -1);
THEN("isRoot returns false") {
REQUIRE(!layout::isRoot(tree1));
REQUIRE(!layout::isRoot(tree2));
}
}
}
SCENARIO("isRooted returns true if any node in the mesh is the root node") {
GIVEN("A nodeTree with root as a top node") {
std::string rootJson =
"{\"type\":6,\"root\":true,\"dest\":2428398258,\"from\":3907768579,"
"\"subs\":[{"
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
"1895675348}]}]}";
auto variant = protocol::Variant(rootJson);
auto tree1 = variant.to<protocol::NodeTree>();
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
}
GIVEN("A nodeTree with a root some where else") {
std::string jsonTree1 =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant1 = protocol::Variant(jsonTree1);
auto tree1 = variant1.to<protocol::NodeTree>();
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
}
GIVEN("A nodeTree without a root any where else") {
std::string jsonTree2 =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant2 = protocol::Variant(jsonTree2);
auto tree2 = variant2.to<protocol::NodeTree>();
THEN("isRooted returns false") { REQUIRE(!layout::isRoot(tree2)); }
}
GIVEN("A random tree with a root") {
auto noNodes = runif(1, 255);
auto tree1 = createNodeTree(noNodes, runif(0, noNodes - 1));
THEN("isRooted returns true") { REQUIRE(layout::isRooted(tree1)); }
}
GIVEN("A random tree without a root") {
auto tree1 = createNodeTree(runif(1, 255), -1);
THEN("isRooted returns false") { REQUIRE(!layout::isRooted(tree1)); }
}
}
SCENARIO("We can get the size of the mesh") {
GIVEN("A tree with a set size") {
std::string jsonTree =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant = protocol::Variant(jsonTree);
auto tree = variant.to<protocol::NodeTree>();
THEN("Size returns the correct size") { REQUIRE(layout::size(tree) == 4); }
}
GIVEN("A random tree with a set size") {
auto noNodes = runif(1, 255);
auto tree = createNodeTree(noNodes, runif(-1, noNodes - 1));
THEN("Size returns the correct size") {
REQUIRE(layout::size(tree) == noNodes);
}
}
}
SCENARIO("We can confirm whether a mesh contains specific nodes") {
GIVEN("A tree with known nodes") {
std::string jsonTree =
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant = protocol::Variant(jsonTree);
auto tree = variant.to<protocol::NodeTree>();
THEN(
"contains should return the true when it contains a node, false "
"otherwise") {
REQUIRE(layout::contains(tree, 1895675348));
REQUIRE(layout::contains(tree, 3907768579));
REQUIRE(layout::contains(tree, 3959373838));
REQUIRE(!layout::contains(tree, 0));
REQUIRE(!layout::contains(tree, 2428398258));
REQUIRE(!layout::contains(tree, 3107768579));
}
}
}
SCENARIO("A layout neighbour knows when to update its sub") {
GIVEN("A Neighbour") {
std::string jsonTree =
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
auto variant = protocol::Variant(jsonTree);
auto tree = variant.to<layout::Neighbour>();
// auto neighbour = std::interpret_cast<layout::Neighbour*>(pTree);
auto neighbour = tree;
THEN("When passed the same tree updateSubs() will return false") {
REQUIRE(!neighbour.updateSubs(tree));
}
auto tree1 = createNodeTree(runif(2, 5), -1);
tree1.nodeId = neighbour.nodeId;
THEN("When passing a different tree it will get updated") {
REQUIRE(neighbour.updateSubs(tree1));
REQUIRE(tree1 == neighbour);
}
THEN("When current nodeId is zero then updateSubs() will return true") {
neighbour.nodeId = 0;
REQUIRE(neighbour.updateSubs(tree));
REQUIRE(neighbour == tree);
REQUIRE(neighbour.nodeId == tree.nodeId);
}
}
}

View File

@@ -0,0 +1,19 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include <Arduino.h>
#include "painlessmesh/logger.hpp"
using namespace painlessmesh::logger;
LogClass Log;
SCENARIO("We can log things") {
Log.setLogLevel(ERROR | DEBUG | COMMUNICATION);
Log(ERROR, "We should see the next %u lines\n", 3);
Log(DEBUG, "We should see the next %u lines\n", 2);
Log(COMMUNICATION, "We should see the next %u lines\n", 1);
Log(ERROR, "But not the next one\n");
Log(S_TIME, "This should not be showing\n");
}

View File

@@ -0,0 +1,13 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include <Arduino.h>
#include "catch_utils.hpp"
#include "painlessmesh/ntp.hpp"
using namespace painlessmesh;
logger::LogClass Log;

View File

@@ -0,0 +1,174 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "Arduino.h"
#include "catch_utils.hpp"
#include "painlessmesh/plugin.hpp"
#include "plugin/performance.hpp"
using namespace painlessmesh;
logger::LogClass Log;
class CustomPackage : public plugin::SinglePackage {
public:
double sensor = 1.0;
CustomPackage() : SinglePackage(20) {}
CustomPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
sensor = jsonObj["sensor"];
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = SinglePackage::addTo(std::move(jsonObj));
jsonObj["sensor"] = sensor;
return jsonObj;
}
size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 1); }
};
class BCustomPackage : public plugin::BroadcastPackage {
public:
double sensor = 1.0;
BCustomPackage() : BroadcastPackage(21) {}
BCustomPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
sensor = jsonObj["sensor"];
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
jsonObj["sensor"] = sensor;
return jsonObj;
}
size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 1); }
};
class MockConnection : public layout::Neighbour {
public:
bool addMessage(TSTRING msg) { return true; }
};
SCENARIO("We can send a custom package") {
GIVEN("A package") {
auto pkg = CustomPackage();
pkg.from = 1;
pkg.dest = 2;
pkg.sensor = 0.5;
REQUIRE(pkg.routing == router::SINGLE);
REQUIRE(pkg.type == 20);
WHEN("Converting it to and from Variant") {
auto var = protocol::Variant(&pkg);
auto pkg2 = var.to<CustomPackage>();
THEN("Should result in the same values") {
REQUIRE(pkg2.sensor == pkg.sensor);
REQUIRE(pkg2.from == pkg.from);
REQUIRE(pkg2.dest == pkg.dest);
REQUIRE(pkg2.routing == pkg.routing);
REQUIRE(pkg2.type == pkg.type);
}
}
}
GIVEN("A broadcast package") {
auto pkg = BCustomPackage();
pkg.from = 1;
pkg.sensor = 0.5;
REQUIRE(pkg.routing == router::BROADCAST);
REQUIRE(pkg.type == 21);
WHEN("Converting it to and from Variant") {
auto var = protocol::Variant(&pkg);
auto pkg2 = var.to<CustomPackage>();
THEN("Should result in the same values") {
REQUIRE(pkg2.sensor == pkg.sensor);
REQUIRE(pkg2.from == pkg.from);
REQUIRE(pkg2.routing == pkg.routing);
REQUIRE(pkg2.type == pkg.type);
}
}
}
GIVEN("A package handler function") {
auto handler = plugin::PackageHandler<MockConnection>();
auto func = [](protocol::Variant variant) {
auto pkg = variant.to<CustomPackage>();
REQUIRE(pkg.routing == router::BROADCAST);
return false;
};
THEN("We can pass it to handler") { handler.onPackage(20, func); }
}
GIVEN("A package") {
auto handler = plugin::PackageHandler<MockConnection>();
auto pkg = CustomPackage();
THEN("We can call sendPackage") {
auto res = handler.sendPackage(&pkg);
REQUIRE(!res);
}
}
}
SCENARIO("We can add tasks to the taskscheduler") {
GIVEN("A couple of tasks added") {
Scheduler mScheduler;
auto handler = plugin::PackageHandler<MockConnection>();
int i = 0;
int j = 0;
int k = 0;
auto task1 = handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
auto task2 = handler.addTask(mScheduler, 0, 3, [&j]() { ++j; });
auto task3 = handler.addTask(mScheduler, 0, 3, [&k]() { ++k; });
auto task4 = handler.addTask(mScheduler, 0, 3, []() {});
WHEN("Executing the tasks") {
THEN("They should be called and automatically removed") {
REQUIRE(i == 0);
REQUIRE(j == 0);
mScheduler.execute();
REQUIRE(i == 1);
mScheduler.execute();
REQUIRE(i == 1);
REQUIRE(j == 2);
// Still kept in handler, because hasn't been executed 3 times yet
task3->disable();
handler.stop();
}
}
}
}
SCENARIO("We can add anonymous tasks to the taskscheduler") {
GIVEN("A couple of tasks added") {
Scheduler mScheduler;
auto handler = plugin::PackageHandler<MockConnection>();
int i = 0;
int j = 0;
int k = 0;
handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
handler.addTask(mScheduler, 0, 3, [&j]() { ++j; });
handler.addTask(mScheduler, 0, 3, [&k]() { ++k; });
WHEN("Executing the tasks") {
THEN("They should be called and automatically removed") {
REQUIRE(i == 0);
REQUIRE(j == 0);
mScheduler.execute();
REQUIRE(i == 1);
mScheduler.execute();
REQUIRE(i == 1);
REQUIRE(j == 2);
handler.addTask(mScheduler, 0, 1, [&i]() { ++i; });
mScheduler.execute();
REQUIRE(i == 2);
handler.stop();
}
}
}
}

View File

@@ -0,0 +1,556 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#define ARDUINOJSON_USE_LONG_LONG 1
#include "ArduinoJson.h"
#undef ARDUINOJSON_ENABLE_ARDUINO_STRING
typedef std::string TSTRING;
#include "catch_utils.hpp"
#include "painlessmesh/protocol.hpp"
using namespace painlessmesh::protocol;
SCENARIO("A variant knows its type", "[Variant][protocol]") {
GIVEN("A json string with the type 9 ") {
std::string str = "{\"type\": 9}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a Single type") {
REQUIRE(variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
}
}
}
GIVEN("A json string with the type 8 ") {
std::string str = "{\"type\": 8}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a Broadcast type") {
REQUIRE(!variant.is<Single>());
REQUIRE(variant.is<Broadcast>());
}
}
}
GIVEN("A json string with the type 6 ") {
std::string str = "{\"type\": 6}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a NodeSyncReply type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(variant.is<NodeSyncReply>());
}
}
}
GIVEN("A json string with the type 5 ") {
std::string str = "{\"type\": 5}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a NodeSyncRequest type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(variant.is<NodeSyncRequest>());
}
}
}
GIVEN("A json string with the type 4 ") {
std::string str = "{\"type\": 4}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a TimeSync type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(variant.is<TimeSync>());
}
}
}
GIVEN("A json string with the type 3 ") {
std::string str = "{\"type\": 3}";
WHEN("Passed to a Variant") {
auto variant = Variant(str);
THEN("The variant is a TimeDelay type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(variant.is<TimeDelay>());
}
}
}
}
SCENARIO("A variant can take a packageinterface", "[Variant][protocol]") {
GIVEN("A Single package") {
auto pkg = createSingle();
WHEN("Passed to a Variant") {
auto variant = Variant(&pkg);
THEN("The variant is a Single type") {
REQUIRE(variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a Single") {
auto newPkg = variant.to<Single>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.msg == pkg.msg);
REQUIRE(newPkg.type == pkg.type);
}
}
}
}
SCENARIO("A variant can take any package", "[Variant][protocol]") {
GIVEN("A Single package") {
auto pkg = createSingle();
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a Single type") {
REQUIRE(variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a Single") {
auto newPkg = variant.to<Single>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.msg == pkg.msg);
REQUIRE(newPkg.type == pkg.type);
}
}
}
GIVEN("A Broadcast package") {
auto pkg = createBroadcast(5);
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a Broadcast type") {
REQUIRE(!variant.is<Single>());
REQUIRE(variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a Broadcast") {
auto newPkg = variant.to<Broadcast>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.msg == pkg.msg);
REQUIRE(newPkg.type == pkg.type);
}
}
}
GIVEN("A NodeSyncReply package") {
auto pkg = createNodeSyncReply(15);
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a NodeSyncReply type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a NodeSyncReply") {
auto newPkg = variant.to<NodeSyncReply>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.nodeId == pkg.nodeId);
REQUIRE(newPkg.root == pkg.root);
REQUIRE(newPkg.subs.size() == pkg.subs.size());
REQUIRE(newPkg.type == pkg.type);
REQUIRE(newPkg == pkg);
}
}
}
GIVEN("A NodeSyncReply package of random size") {
auto pkg = createNodeSyncReply();
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant throws no error") { REQUIRE(!variant.error); }
THEN("The variant is a NodeSyncReply type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a NodeSyncReply") {
auto newPkg = variant.to<NodeSyncReply>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.nodeId == pkg.nodeId);
REQUIRE(newPkg.root == pkg.root);
REQUIRE(newPkg.subs.size() == pkg.subs.size());
REQUIRE(newPkg.type == pkg.type);
REQUIRE(newPkg == pkg);
}
}
}
GIVEN("A NodeSyncRequest package") {
auto pkg = createNodeSyncRequest(5);
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a NodeSyncRequest type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a NodeSyncRequest") {
auto newPkg = variant.to<NodeSyncRequest>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.nodeId == pkg.nodeId);
REQUIRE(newPkg.root == pkg.root);
REQUIRE(newPkg.subs.size() == pkg.subs.size());
REQUIRE(newPkg.type == pkg.type);
REQUIRE(newPkg == pkg);
}
}
}
GIVEN("A TimeSync package") {
auto pkg = createTimeSync();
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a TimeSync type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(variant.is<TimeSync>());
REQUIRE(!variant.is<TimeDelay>());
}
THEN("The variant can be converted to a TimeSync") {
auto newPkg = variant.to<TimeSync>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.type == pkg.type);
REQUIRE(newPkg.msg.type == pkg.msg.type);
REQUIRE(newPkg.msg.t0 == pkg.msg.t0);
REQUIRE(newPkg.msg.t1 == pkg.msg.t1);
REQUIRE(newPkg.msg.t2 == pkg.msg.t2);
}
}
}
GIVEN("A TimeDelay package") {
auto pkg = createTimeDelay();
WHEN("Passed to a Variant") {
auto variant = Variant(pkg);
THEN("The variant is a TimeDelay type") {
REQUIRE(!variant.is<Single>());
REQUIRE(!variant.is<Broadcast>());
REQUIRE(!variant.is<NodeSyncReply>());
REQUIRE(!variant.is<NodeSyncRequest>());
REQUIRE(!variant.is<TimeSync>());
REQUIRE(variant.is<TimeDelay>());
}
THEN("The variant can be converted to a TimeDelay") {
auto newPkg = variant.to<TimeDelay>();
REQUIRE(newPkg.dest == pkg.dest);
REQUIRE(newPkg.from == pkg.from);
REQUIRE(newPkg.type == pkg.type);
REQUIRE(newPkg.msg.type == pkg.msg.type);
REQUIRE(newPkg.msg.t0 == pkg.msg.t0);
REQUIRE(newPkg.msg.t1 == pkg.msg.t1);
REQUIRE(newPkg.msg.t2 == pkg.msg.t2);
}
}
}
}
SCENARIO("NodeSyncReply is backwards compatible", "[Variant][protocol]") {
GIVEN("A json string without a base nodeId") {
std::string old =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"subs\":[{"
"\"nodeId\":3959373838,\"subs\":[{\"nodeId\":416992913},{\"nodeId\":"
"1895675348,\"root\":true}]}]}";
std::string withId =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
WHEN("Converted to a NodeSyncReply") {
auto variant = Variant(old);
auto nsr = variant.to<NodeSyncReply>();
auto variantId = Variant(withId);
auto nsrId = variantId.to<NodeSyncReply>();
THEN("NodeId is set to from") {
REQUIRE(nsr.from == nsr.nodeId);
REQUIRE(nsr == nsrId);
}
}
WHEN("Converted to a NodeTree") {
auto variant = Variant(old);
auto ns = variant.to<NodeTree>();
auto variantId = Variant(withId);
auto nsId = variantId.to<NodeTree>();
auto variantReply = Variant(withId);
auto nsrId = variantId.to<NodeSyncReply>();
THEN("NodeId is set to from value") {
REQUIRE(nsrId.from == ns.nodeId);
REQUIRE(nsrId.nodeId == nsId.nodeId);
REQUIRE(nsrId.subs == ns.subs);
}
}
}
GIVEN("A json string with root explicitly set to false") {
std::string old =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
std::string withId =
"{\"type\":6,\"dest\":2428398258,\"root\":false,\"from\":3907768579,"
"\"nodeId\":3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{"
"\"nodeId\":416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
WHEN("Converted to a NodeSyncReply") {
auto variant = Variant(old);
auto nsr = variant.to<NodeSyncReply>();
auto variantId = Variant(withId);
auto nsrId = variantId.to<NodeSyncReply>();
THEN("NodeId is set to from") { REQUIRE(nsr == nsrId); }
}
}
GIVEN("A json string with subs explicitly set to empty") {
std::string old =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true,\"subs\":[]}]}]}";
std::string withId =
"{\"type\":6,\"dest\":2428398258,\"from\":3907768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348,\"root\":true}]}]}";
WHEN("Converted to a NodeSyncReply") {
auto variant = Variant(old);
auto nsr = variant.to<NodeSyncReply>();
auto variantId = Variant(withId);
auto nsrId = variantId.to<NodeSyncReply>();
THEN("NodeId is set to from") { REQUIRE(nsr == nsrId); }
}
}
}
SCENARIO("NodeSyncReply supports the == operator", "[Variant][protocol]") {
GIVEN("Different NodeSyncReplies") {
auto pkg1 = createNodeSyncReply(5);
auto pkg2 = createNodeSyncReply(5);
// Same subs different base
auto pkg3 = createNodeSyncReply(5);
auto pkg4 = createNodeSyncReply(5);
pkg4.subs = pkg3.subs;
// Same base different subs
auto pkg5 = pkg4;
pkg5.subs = pkg1.subs;
THEN("They are not equal") {
REQUIRE(pkg1 != pkg2);
REQUIRE(pkg2 == pkg2);
REQUIRE(!(pkg2 != pkg2));
REQUIRE(!(pkg1 != pkg1));
REQUIRE(pkg3 != pkg4);
REQUIRE(pkg3.subs == pkg4.subs);
REQUIRE(pkg5 != pkg4);
REQUIRE(pkg5.subs != pkg4.subs);
}
}
}
SCENARIO("A variant can printTo a package", "[Variant][protocol]") {
GIVEN("A NodeSyncReply package printed to a string using Variant") {
auto pkg = createNodeSyncReply(5);
std::string str;
auto variant = Variant(pkg);
variant.printTo(str);
THEN("It can be converted back into an identical pkg") {
auto variant = Variant(str);
auto pkg2 = variant.to<NodeSyncReply>();
REQUIRE(pkg2 == pkg);
}
}
}
SCENARIO("The Variant type properly carries over errors",
"[Variant][protocol][error]") {
GIVEN("A large and small NodeSyncReply pkg") {
auto large_pkg = createNodeSyncReply(100);
auto large_variant = Variant(large_pkg);
std::string large_json;
large_variant.printTo(large_json);
auto small_pkg = createNodeSyncReply(5);
auto small_variant = Variant(small_pkg);
std::string small_json;
small_variant.printTo(small_json);
THEN("It carries over the ArduinoJson error") {
auto large_var = Variant(large_json, 1024);
REQUIRE(large_var.error);
auto small_var = Variant(small_json, 1024);
REQUIRE(!small_var.error);
}
}
}
SCENARIO(
"The construction of a Time package automatically sets the correct time "
"sync type",
"[protocol]") {
GIVEN("Calling the constructor with no time") {
auto pkg1 = TimeSync(10, 11);
THEN("The time type is TIME_SYNC_REQUEST") {
REQUIRE(pkg1.msg.type == TIME_SYNC_REQUEST);
}
}
GIVEN("Calling the constructor with one time") {
auto pkg1 = TimeSync(10, 11, 12);
THEN("The time type is TIME_REQUEST") {
REQUIRE(pkg1.msg.type == TIME_REQUEST);
REQUIRE(pkg1.msg.t0 == 12);
}
}
GIVEN("Calling the constructor with two or three times") {
auto pkg2 = TimeSync(10, 11, 12, 13);
auto pkg3 = TimeSync(10, 11, 12, 13, 14);
THEN("The time type is TIME_REPLY") {
REQUIRE(pkg2.msg.type == TIME_REPLY);
REQUIRE(pkg2.msg.t0 == 12);
REQUIRE(pkg2.msg.t1 == 13);
REQUIRE(pkg3.msg.type == TIME_REPLY);
REQUIRE(pkg3.msg.t0 == 12);
REQUIRE(pkg3.msg.t1 == 13);
REQUIRE(pkg3.msg.t2 == 14);
}
}
GIVEN("Calling the constructor with no time") {
auto pkg1 = TimeDelay(10, 11);
THEN("The time type is TIME_SYNC_REQUEST") {
REQUIRE(pkg1.msg.type == TIME_SYNC_REQUEST);
}
}
GIVEN("Calling the constructor with one time") {
auto pkg1 = TimeDelay(10, 11, 12);
THEN("The time type is TIME_REQUEST") {
REQUIRE(pkg1.msg.type == TIME_REQUEST);
REQUIRE(pkg1.msg.t0 == 12);
}
}
GIVEN("Calling the constructor with two or three times") {
auto pkg2 = TimeDelay(10, 11, 12, 13);
auto pkg3 = TimeDelay(10, 11, 12, 13, 14);
THEN("The time type is TIME_REPLY") {
REQUIRE(pkg2.msg.type == TIME_REPLY);
REQUIRE(pkg2.msg.t0 == 12);
REQUIRE(pkg2.msg.t1 == 13);
REQUIRE(pkg3.msg.type == TIME_REPLY);
REQUIRE(pkg3.msg.t0 == 12);
REQUIRE(pkg3.msg.t1 == 13);
REQUIRE(pkg3.msg.t2 == 14);
}
}
}
SCENARIO("We can construct a reply to Time packages", "[protocol]") {
GIVEN("A reply to a TIME_SYNC_REQUEST") {
auto origPkg1 = TimeSync(10, 11);
auto pkg1 = TimeSync(10, 11);
pkg1.reply(12);
auto origPkg2 = TimeDelay(10, 11);
auto pkg2 = TimeDelay(10, 11);
pkg2.reply(12);
THEN("It will set t0, update type and swap from and dest.") {
REQUIRE(pkg1.msg.type == TIME_REQUEST);
REQUIRE(pkg2.msg.type == TIME_REQUEST);
REQUIRE(pkg1.msg.t0 == 12);
REQUIRE(pkg2.msg.t0 == 12);
REQUIRE(pkg1.from == origPkg1.dest);
REQUIRE(pkg2.from == origPkg2.dest);
REQUIRE(pkg1.dest == origPkg1.from);
REQUIRE(pkg2.dest == origPkg2.from);
REQUIRE(pkg1.type == origPkg1.type);
REQUIRE(pkg2.type == origPkg2.type);
}
}
GIVEN("A reply to a TIME_REQUEST") {
auto origPkg1 = TimeSync(10, 11, 12);
auto pkg1 = TimeSync(10, 11, 12);
pkg1.reply(13, 14);
auto origPkg2 = TimeDelay(10, 11, 12);
auto pkg2 = TimeDelay(10, 11, 12);
pkg2.reply(13, 14);
THEN("It will set t1 and t2, update type and swap from and dest.") {
REQUIRE(pkg1.msg.type == TIME_REPLY);
REQUIRE(pkg2.msg.type == TIME_REPLY);
REQUIRE(pkg1.msg.t0 == 12);
REQUIRE(pkg2.msg.t0 == 12);
REQUIRE(pkg1.msg.t1 == 13);
REQUIRE(pkg2.msg.t1 == 13);
REQUIRE(pkg1.msg.t2 == 14);
REQUIRE(pkg2.msg.t2 == 14);
REQUIRE(pkg1.from == origPkg1.dest);
REQUIRE(pkg2.from == origPkg2.dest);
REQUIRE(pkg1.dest == origPkg1.from);
REQUIRE(pkg2.dest == origPkg2.from);
REQUIRE(pkg1.type == origPkg1.type);
REQUIRE(pkg2.type == origPkg2.type);
}
}
}
SCENARIO("Package constructors work as expected", "[protocol]") {
GIVEN("A Single package constructed with the constructor") {
std::string str = "Blaat";
auto pkg = Single(10, 0, str);
THEN("Message will be set correctly") { REQUIRE(pkg.msg == "Blaat"); }
}
GIVEN("A Broadcast package constructed with the constructor") {
std::string str = "Blaat";
auto pkg = Broadcast(10, 0, str);
THEN("Message will be set correctly") {
REQUIRE(pkg.msg == "Blaat");
REQUIRE(pkg.type == BROADCAST);
}
}
}

View File

@@ -0,0 +1,102 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include <Arduino.h>
#include "catch_utils.hpp"
#include "painlessmesh/router.hpp"
using namespace painlessmesh;
logger::LogClass Log;
/*
class MockConnection : public protocol::NodeTree {
public:
void addMessage(TSTRING msg, bool priority = false) { ++cnt; }
int cnt = 0;
};
SCENARIO("findRoute works as expected with different types of connections") {
GIVEN("A layout with Neighbour shared ptrs") {
auto layout = layout::Layout<layout::Neighbour>();
std::string jsonTree1 =
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
"3907768579,\"subs\":[{\"nodeId\":3959373838,\"subs\":[{\"nodeId\":"
"416992913},{\"nodeId\":1895675348}]}]}";
auto variant1 = protocol::Variant(jsonTree1);
auto tree1 =
std::make_shared<layout::Neighbour>(variant1.to<layout::Neighbour>());
std::string jsonTree2 =
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
"3907768580,\"subs\":[{\"nodeId\":3959373839,\"subs\":[{\"nodeId\":"
"416992914},{\"nodeId\":1895675349}]}]}";
auto variant2 = protocol::Variant(jsonTree2);
auto tree2 =
std::make_shared<layout::Neighbour>(variant2.to<layout::Neighbour>());
std::string jsonTree3 =
"{\"type\":6,\"dest\":2428398258,\"from\":3107768579,\"nodeId\":"
"3907768581,\"subs\":[{\"nodeId\":3959373840,\"subs\":[{\"nodeId\":"
"416992915},{\"nodeId\":1895675350}]}]}";
auto variant3 = protocol::Variant(jsonTree3);
auto tree3 =
std::make_shared<layout::Neighbour>(variant3.to<layout::Neighbour>());
layout.subs.push_back(tree1);
layout.subs.push_back(tree2);
layout.subs.push_back(tree3);
layout.nodeId = runif(1, 1000);
THEN("findRoute works") {
auto rt = router::findRoute<layout::Neighbour>(layout, 1895675350);
REQUIRE(rt->nodeId == 3907768581);
rt = router::findRoute<layout::Neighbour>(layout, 1895675351);
REQUIRE(!rt);
}
THEN("It can be converted to a NodeTree") {
auto lay = layout.asNodeTree();
auto nt = protocol::NodeTree();
nt.nodeId = lay.nodeId;
for (auto &&s : lay.subs) {
nt.subs.push_back(s);
}
REQUIRE(nt == lay);
}
}
}
SCENARIO("routePackage should route the package correctly") {
GIVEN("A CallbackList and layout") {
auto cbl = router::CallbackList();
auto lay = layout::Layout<MockConnection>();
lay.nodeId = 1;
lay.subs.push_back(std::make_shared<MockConnection>());
lay.subs.back()->nodeId = 2;
lay.subs.push_back(std::make_shared<MockConnection>());
lay.subs.back()->nodeId = 3;
lay.subs.push_back(std::make_shared<MockConnection>());
lay.subs.back()->nodeId = 4;
// void routePackage(Layout<T>, T conn, TSTRING pkg, CallbackMap)
WHEN("Passed a package with routing NEIGHBOUR") {
auto pkg = createTimeSync();
TSTRING str;
auto var = protocol::Variant(pkg);
REQUIRE(var.routing() == router::NEIGHBOUR);
var.printTo(str);
// message type NEIGHBOUR should result in a callback
}
// message type BROADCAST should result in a callback and being send on
// message type SINGLE if destination is other then send otherwise callback
}
}
*/

View File

@@ -0,0 +1,49 @@
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "Arduino.h"
#include "catch_utils.hpp"
WiFiClass WiFi;
ESPClass ESP;
#include "painlessmesh/logger.hpp"
using namespace painlessmesh;
logger::LogClass Log;
SCENARIO("Fake Async classes behave similar to real ones") {
int i = 0;
std::string j = "";
auto server = AsyncServer();
AsyncClient *conn;
server.onClient([&conn, &i, &j](void *, AsyncClient *client) {
conn = client;
conn->onData([&j](void *, AsyncClient *client, void *data,
size_t len) { j = std::string((char *)data, len); },
NULL);
++i;
});
auto client = AsyncClient(&server);
std::string j2 = "";
client.onData([&j2](void *arg, AsyncClient *client, void *data,
size_t len) { j2 = std::string((char *)data, len); },
NULL);
client.connect(IPAddress(), 0);
THEN("server.onConnect is called") { REQUIRE(i == 1); }
THEN("I can send data") {
client.write("Blaat", 5);
REQUIRE(j == "Blaat");
conn->write("Blaat terug", 11);
REQUIRE(j2 == "Blaat terug");
}
delete conn;
}

View File

@@ -0,0 +1,52 @@
/*
DSM2_tx implements the serial communication protocol used for operating
the RF modules that can be found in many DSM2-compatible transmitters.
Copyrigt (C) 2012 Erik Elmore <erik@ironsavior.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring>
#include <iomanip>
#include <iostream>
#include "fake_serial.hpp"
void FakeSerial::begin(unsigned long speed) { return; }
void FakeSerial::end() { return; }
size_t FakeSerial::write(const unsigned char buf[], size_t size) {
using namespace std;
ios_base::fmtflags oldFlags = cout.flags();
streamsize oldPrec = cout.precision();
char oldFill = cout.fill();
cout << "Serial::write: ";
cout << internal << setfill('0');
for (unsigned int i = 0; i < size; i++) {
cout << setw(2) << hex << (unsigned int)buf[i] << " ";
}
cout << endl;
cout.flags(oldFlags);
cout.precision(oldPrec);
cout.fill(oldFill);
return size;
}
void FakeSerial::print(const char* buf) { std::cout << buf; }
void FakeSerial::println() { std::cout << std::endl; }
FakeSerial Serial;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,209 @@
#ifndef CATCH_UTILS_H_
#define CATCH_UTILS_H_
/*
* Some helper functions to be used in catch based tests
*/
#include <limits>
#include <random>
#include "painlessmesh/protocol.hpp"
static std::random_device
rd; // Will be used to obtain a seed for the random number engine
static std::mt19937 gen(rd());
uint32_t runif(uint32_t from, uint32_t to) {
std::uniform_int_distribution<uint32_t> distribution(from, to);
return distribution(gen);
}
uint32_t rbinom(size_t n, double p) {
std::binomial_distribution<uint32_t> distribution(n, p);
return distribution(gen);
}
std::string randomString(uint32_t length) {
std::string str;
for (uint32_t i = 0; i < length; ++i) {
char rnd = (char)runif(65, 90);
str += rnd;
}
return str;
}
void randomCString(char* str, uint32_t length) {
for (uint32_t i = 0; i < length; ++i) {
char rnd = (char)runif(65, 90);
str[i] = rnd;
}
str[length] = '\0';
}
painlessmesh::protocol::Single createSingle(int length = -1) {
auto pkg = painlessmesh::protocol::Single();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (length < 0) length = runif(0, 4096);
pkg.msg = randomString(length);
return pkg;
}
painlessmesh::protocol::Broadcast createBroadcast(int length = -1) {
auto pkg = painlessmesh::protocol::Broadcast();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (length < 0) length = runif(0, 4096);
pkg.msg = randomString(length);
return pkg;
}
/*
```
{
"dest": ...,
"from": ...,
"type": ...,
"subs": [
{
"nodeId": ...,
"root" : true,
"subs": [
{
"nodeId": ...,
"subs": []
}
]
}
]
}
```
*/
painlessmesh::protocol::NodeTree createNodeTree(int nodes, int contains_root) {
auto pkg = painlessmesh::protocol::NodeTree();
pkg.nodeId = runif(0, std::numeric_limits<uint32_t>::max());
if (contains_root == 0) {
pkg.root = true;
}
--nodes; // The current node
--contains_root;
auto noSubs = runif(1, 5);
for (uint32_t i = 0; i < noSubs; ++i) {
if (nodes > 0) {
if (i == noSubs - 1) {
pkg.subs.push_back(createNodeTree(nodes, contains_root));
} else {
auto newNodes = 1 + rbinom(nodes - 1, 1.0 / noSubs);
nodes -= newNodes;
if (newNodes > 0)
pkg.subs.push_back(createNodeTree(newNodes, contains_root));
contains_root -= newNodes;
}
}
}
return pkg;
}
painlessmesh::protocol::NodeSyncReply createNodeSyncReply(
int nodes = -1, bool contains_root = true) {
auto pkg = painlessmesh::protocol::NodeSyncReply();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (nodes < 0) nodes = runif(1, 254);
auto rt = -1;
if (contains_root) rt = runif(0, nodes - 1);
auto ns = createNodeTree(nodes, rt);
pkg.subs = ns.subs;
pkg.nodeId = ns.nodeId;
pkg.root = ns.root;
return pkg;
}
painlessmesh::protocol::NodeSyncRequest createNodeSyncRequest(
int nodes = -1, bool contains_root = true) {
auto pkg = painlessmesh::protocol::NodeSyncRequest();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (nodes < 0) nodes = runif(1, 254);
auto rt = -1;
if (contains_root) rt = runif(0, nodes - 1);
auto ns = createNodeTree(nodes, rt);
pkg.subs = ns.subs;
pkg.nodeId = ns.nodeId;
pkg.root = ns.root;
return pkg;
}
/*
```
{
"dest": 887034362,
"from": 37418,
"type":4,
"msg":{
"type":0
}
}
{
"dest": 887034362,
"from": 37418,
"type":4,
"msg":{
"type":1,
"t0":32990
}
}
{
"dest": 37418,
"from": 887034362,
"type":4,
"msg":{
"type":2,
"t0":32990,
"t1":448585896,
"t2":448596056,
}
}
```
*/
painlessmesh::protocol::TimeSync createTimeSync(int type = -1) {
auto pkg = painlessmesh::protocol::TimeSync();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (type < 0) type = runif(0, 2);
pkg.msg.type = type;
auto t = runif(0, std::numeric_limits<uint32_t>::max());
if (type >= 1) pkg.msg.t0 = t;
if (type >= 2) {
t += runif(0, 10000);
pkg.msg.t1 = t;
t += runif(0, 10000);
pkg.msg.t2 = t;
}
return pkg;
}
painlessmesh::protocol::TimeDelay createTimeDelay(int type = -1) {
auto pkg = painlessmesh::protocol::TimeDelay();
pkg.dest = runif(0, std::numeric_limits<uint32_t>::max());
pkg.from = runif(0, std::numeric_limits<uint32_t>::max());
if (type < 0) type = runif(0, 2);
pkg.msg.type = type;
auto t = runif(0, std::numeric_limits<uint32_t>::max());
if (type == 1) pkg.msg.t0 = t;
if (type == 2) {
t += runif(0, 10000);
pkg.msg.t1 = t;
t += runif(0, 10000);
pkg.msg.t2 = t;
}
return pkg;
}
#endif

View File

@@ -0,0 +1,133 @@
#pragma once
#include <functional>
#include <cstring>
#include "Arduino.h"
#define ASYNC_WRITE_FLAG_COPY \
0x01 // will allocate new buffer to hold the data while sending (else will
// hold reference to the data given)
#define ASYNC_WRITE_FLAG_MORE \
0x02 // will not send PSH flag, meaning that there should be more data to be
// sent before the application should react.
class AsyncClient;
typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t time)>
AcAckHandler;
typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHandler;
typedef std::function<void(void*, AsyncClient*, void* data, size_t len)>
AcDataHandler;
typedef std::function<void(void*, AsyncClient*, struct pbuf* pb)>
AcPacketHandler;
typedef std::function<void(void*, AsyncClient*, uint32_t time)>
AcTimeoutHandler;
class AsyncServer;
class AsyncClient {
public:
AsyncClient() {}
AsyncClient(AsyncServer* server) : mServer(server) {}
void setNoDelay(bool nodelay) {}
void setRxTimeout(uint32_t timeout) {}
void onData(AcDataHandler cb, void* arg = 0) {_recv_cb = cb;}
void onAck(AcAckHandler cb, void* arg = 0) { _sent_cb = cb; }
void onError(AcErrorHandler cb, void* arg = 0) {}
void onDisconnect(AcConnectHandler cb, void* arg = 0) {_discard_cb = cb;}
void onConnect(AcConnectHandler cb, void* arg = 0) { _connect_cb = cb; }
const char* errorToString(int err) { return ""; }
bool connected() { return mOther; }
bool canSend() { return true; }
void ack(int len) {
if (_sent_cb)
_sent_cb(NULL, this, len, 0);
}
void close(bool now = false) {
/*if (mOther && mOther->_discard_cb) {
mOther->_discard_cb(NULL, this);
mOther = NULL;
}
mServer = NULL;*/
}
bool connect(IPAddress ip, uint16_t port);
size_t space() { return 1000; }
bool send() { return true; }
size_t write(const char* data, size_t size,
uint8_t apiflags = ASYNC_WRITE_FLAG_COPY) {
char* cpy[size];
memcpy(&cpy, data, size);
void * arg = NULL;
if (mOther && mOther->_recv_cb) {
mOther->_recv_cb(arg, mOther, cpy, size);
}
return size;
}
bool freeable() { return true; }
int8_t abort() { return 0; }
bool free() { return true; }
bool operator==(const AsyncClient &other) {
return mOther == other.mOther;
}
protected:
AsyncServer* mServer = NULL;
AsyncClient* mOther = NULL;
AcConnectHandler _connect_cb = NULL;
AcConnectHandler _discard_cb = NULL;
AcDataHandler _recv_cb = NULL;
AcAckHandler _sent_cb = NULL;
};
class AsyncServer : public AsyncClient {
public:
AsyncServer() {}
AsyncServer(uint16_t port) {}
void onClient(AcConnectHandler cb, void* arg = 0) { _connect_cb = cb; }
void begin() {}
};
typedef enum {
WL_NO_SHIELD = 255, // for compatibility with WiFi Shield library
WL_IDLE_STATUS = 0,
WL_NO_SSID_AVAIL = 1,
WL_SCAN_COMPLETED = 2,
WL_CONNECTED = 3,
WL_CONNECT_FAILED = 4,
WL_CONNECTION_LOST = 5,
WL_DISCONNECTED = 6
} wl_status_t;
class WiFiClass {
public:
void disconnect() {}
auto status() {
return WL_CONNECTED;
}
};
class ESPClass {
public:
size_t getFreeHeap() { return 1e6; }
};
inline bool AsyncClient::connect(IPAddress ip, uint16_t port) {
this->mOther = new AsyncClient();
this->mOther->mOther = this;
void * arg = NULL;
if (mServer->_connect_cb)
mServer->_connect_cb(arg, this->mOther);
if (this->_connect_cb)
this->_connect_cb(arg, this);
return true;
}

View File

@@ -0,0 +1,29 @@
/*
DSM2_tx implements the serial communication protocol used for operating
the RF modules that can be found in many DSM2-compatible transmitters.
Copyrigt (C) 2012 Erik Elmore <erik@ironsavior.net>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <iostream>
class FakeSerial {
public:
void begin(unsigned long);
void end();
size_t write(const unsigned char*, size_t);
void print(const char*);
void println();
};
extern FakeSerial Serial;

View File

@@ -0,0 +1,39 @@
//************************************************************
// this is a simple example that uses the easyMesh library
//
// 1. blinks led once for every node on the mesh
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time between 1
// and 5 seconds
// 4. prints anything it receives to Serial.print
//
//
//************************************************************
#include <painlessMesh.h>
#include "painlessmesh/ota.hpp"
#include "painlessmesh/protocol.hpp"
#include "plugin/performance.hpp"
#define MESH_SSID "otatest"
#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555
using namespace painlessmesh;
painlessMesh mesh;
void setup() {
Serial.begin(115200);
mesh.setDebugMsgTypes(
ERROR | CONNECTION |
DEBUG); // set before init() so that you can see error messages
mesh.init(MESH_SSID, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 6);
mesh.initOTA("performance");
plugin::performance::begin(mesh, 2);
}
void loop() { mesh.update(); }

View File

@@ -0,0 +1,20 @@
[platformio]
src_dir = .
lib_extra_dirs = .piolibdeps/, ../../
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps = ArduinoJson
TaskScheduler
ESPAsyncTCP
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = ArduinoJson
arduinoUnity
TaskScheduler
AsyncTCP

View File

@@ -0,0 +1,20 @@
[platformio]
src_dir = .
lib_extra_dirs = .piolibdeps/, ../../
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps = ArduinoJson
TaskScheduler
ESPAsyncTCP
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = ArduinoJson
arduinoUnity
TaskScheduler
AsyncTCP

View File

@@ -0,0 +1,163 @@
//************************************************************
// this is a simple example that uses the easyMesh library
//
// 1. blinks led once for every node on the mesh
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
// 4. prints anything it receives to Serial.print
//
//
//************************************************************
#include <painlessMesh.h>
#include "painlessmesh/ota.hpp"
#include "plugin/performance.hpp"
// some gpio pin that is connected to an LED...
// on my rig, this is 5, change to the right number of your LED.
#define LED 2 // GPIO number of connected LED, ON ESP-12 IS GPIO2
#define SEND_FREQ 2
#define BLINK_PERIOD 3000 // milliseconds until cycle repeat
#define BLINK_DURATION 100 // milliseconds LED is on for
#define MESH_SSID "otatest"
#define MESH_PASSWORD "somethingSneaky"
#define MESH_PORT 5555
// Prototypes
void sendMessage();
void receivedCallback(uint32_t from, String & msg);
void newConnectionCallback(uint32_t nodeId);
void changedConnectionCallback();
void nodeTimeAdjustedCallback(int32_t offset);
void delayReceivedCallback(uint32_t from, int32_t delay);
Scheduler userScheduler; // to control your personal task
painlessMesh mesh;
bool calc_delay = false;
std::list<uint32_t> nodes;
void sendMessage() ; // Prototype
Task taskSendMessage( TASK_SECOND/SEND_FREQ, TASK_FOREVER, &sendMessage ); // start with a one second interval
// Task to blink the number of nodes
Task blinkNoNodes;
bool onFlag = false;
void setup() {
Serial.begin(115200);
pinMode(LED, OUTPUT);
mesh.setDebugMsgTypes(ERROR | CONNECTION | DEBUG); // set before init() so that you can see error messages
mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6);
mesh.initOTA("otatest");
mesh.setContainsRoot(true);
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
mesh.onNodeDelayReceived(&delayReceivedCallback);
painlessmesh::plugin::performance::begin(mesh);
userScheduler.addTask( taskSendMessage );
taskSendMessage.enable();
blinkNoNodes.set(BLINK_PERIOD, (mesh.getNodeList().size() + 1) * 2, []() {
// If on, switch off, else switch on
if (onFlag)
onFlag = false;
else
onFlag = true;
blinkNoNodes.delay(BLINK_DURATION);
if (blinkNoNodes.isLastIteration()) {
// Finished blinking. Reset task for next run
// blink number of nodes (including this node) times
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
// Calculate delay based on current mesh time and BLINK_PERIOD
// This results in blinks between nodes being synced
blinkNoNodes.enableDelayed(BLINK_PERIOD -
(mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
}
});
userScheduler.addTask(blinkNoNodes);
blinkNoNodes.enable();
randomSeed(analogRead(A0));
}
void loop() {
mesh.update();
digitalWrite(LED, !onFlag);
}
uint32_t lastTime = 0;
void sendMessage() {
String msg = "Hello from node ";
msg += mesh.getNodeId();
msg += " myFreeMemory: " + String(ESP.getFreeHeap());
mesh.sendBroadcast(msg);
if (calc_delay) {
SimpleList<uint32_t>::iterator node = nodes.begin();
while (node != nodes.end()) {
mesh.startDelayMeas(*node);
node++;
}
calc_delay = false;
}
Serial.printf("Mesh stability: %u\n", mesh.stability);
Serial.printf("Sending message: %s with delay %u\n", msg.c_str(), mesh.getNodeTime() - lastTime);
lastTime = mesh.getNodeTime();
}
void receivedCallback(uint32_t from, String & msg) {
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
}
void newConnectionCallback(uint32_t nodeId) {
// Reset blink task
onFlag = false;
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
Serial.printf("--> startHere: New Connection, %s\n", mesh.subConnectionJson(true).c_str());
}
void changedConnectionCallback() {
Serial.printf("Changed connections\n");
// Reset blink task
onFlag = false;
blinkNoNodes.setIterations((mesh.getNodeList().size() + 1) * 2);
blinkNoNodes.enableDelayed(BLINK_PERIOD - (mesh.getNodeTime() % (BLINK_PERIOD*1000))/1000);
nodes = mesh.getNodeList();
Serial.printf("Num nodes: %d\n", nodes.size());
Serial.printf("Connection list:");
SimpleList<uint32_t>::iterator node = nodes.begin();
while (node != nodes.end()) {
Serial.printf(" %u", *node);
node++;
}
Serial.println();
calc_delay = true;
}
void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
}
void delayReceivedCallback(uint32_t from, int32_t delay) {
Serial.printf("Delay to node %u is %d us\n", from, delay);
}

View File

@@ -0,0 +1,20 @@
[platformio]
src_dir = .
lib_extra_dirs = .piolibdeps/, ../../
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
lib_deps = ArduinoJson
TaskScheduler
ESPAsyncTCP
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = ArduinoJson
arduinoUnity
TaskScheduler
AsyncTCP

View File

@@ -0,0 +1,88 @@
//************************************************************
// this is a simple example that uses the easyMesh library
//
// 1. blinks led once for every node on the mesh
// 2. blink cycle repeats every BLINK_PERIOD
// 3. sends a silly message to every node on the mesh at a random time between 1
// and 5 seconds
// 4. prints anything it receives to Serial.print
//
//
//************************************************************
#include "painlessmesh/configuration.hpp"
#include "painlessMeshConnection.h"
#include "painlessmesh/mesh.hpp"
#include "painlessmesh/tcp.hpp"
#include "plugin/performance.hpp"
using namespace painlessmesh;
using namespace logger;
painlessmesh::Mesh<MeshConnection> mesh;
std::shared_ptr<AsyncServer> pServer;
WiFiEventId_t eventSTADisconnectedHandler;
WiFiEventId_t eventSTAGotIPHandler;
void setup() {
Serial.begin(115200);
Log.setLogLevel(ERROR | CONNECTION | DEBUG);
uint8_t mac[] = {0, 0, 0, 0, 0, 0};
/*auto _apIp = IPAddress(10, (nodeId & 0xFF00) >> 8, (nodeId & 0xFF), 1);
IPAddress netmask(255, 255, 255, 0);
WiFi.softAPConfig(_apIp, _apIp, netmask);*/
WiFi.softAP("otatest", "somethingSneaky", 6);
if (WiFi.softAPmacAddress(mac) == 0) {
Log(ERROR, "init(): WiFi.softAPmacAddress(MAC) failed.\n");
}
auto nodeId = painlessmesh::tcp::encodeNodeId(mac);
if (nodeId == 0) Log(ERROR, "NodeId set to 0\n");
Log(ERROR, "Bla %u\n", nodeId);
mesh.init(nodeId);
mesh.setRoot(true);
plugin::performance::begin(mesh);
pServer = std::make_shared<AsyncServer>(5555);
painlessmesh::tcp::initServer<MeshConnection>((*pServer), mesh);
eventSTAGotIPHandler = WiFi.onEvent(
[&](WiFiEvent_t event, WiFiEventInfo_t info) {
// if (this->semaphoreTake()) {
Log(CONNECTION, "eventSTAGotIPHandler: SYSTEM_EVENT_STA_GOT_IP\n");
AsyncClient *pConn = new AsyncClient();
painlessmesh::tcp::connect<MeshConnection>(
(*pConn), IPAddress(192, 168, 1, 69), 5555, mesh);
// this->tcpConnect(); // Connect to TCP port
// this->semaphoreGive();
//}
},
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
eventSTADisconnectedHandler = WiFi.onEvent(
[](WiFiEvent_t event, WiFiEventInfo_t info) {
//if (this->semaphoreTake()) {
Log(CONNECTION,
"eventSTADisconnectedHandler: SYSTEM_EVENT_STA_DISCONNECTED\n");
// WiFi.disconnect();
// Search for APs and connect to the best one
//this->stationScan.connectToAP();
//this->semaphoreGive();
//}
},
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.setAutoConnect(true);
WiFi.begin("BigBird", "eendolleman");
Log(CONNECTION, "Beginning\n");
mesh.addTask(TASK_SECOND, TASK_FOREVER, []() {
Log(CONNECTION, "Connected? %d\n", WiFi.status() == WL_CONNECTED);
});
}
void loop() { mesh.update(); }