初始化提交

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,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;