初始化提交

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,327 @@
#ifndef _PAINLESS_MESH_ARDUINO_WIFI_HPP_
#define _PAINLESS_MESH_ARDUINO_WIFI_HPP_
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
#include "painlessMeshConnection.h"
#include "painlessMeshSTA.h"
#include "painlessmesh/callback.hpp"
#include "painlessmesh/mesh.hpp"
#include "painlessmesh/router.hpp"
#include "painlessmesh/tcp.hpp"
extern painlessmesh::logger::LogClass Log;
namespace painlessmesh {
namespace wifi {
class Mesh : public painlessmesh::Mesh<MeshConnection> {
public:
/** Initialize the mesh network
*
* Add this to your setup() function. This routine does the following things:
*
* - Starts a wifi network
* - Begins searching for other wifi networks that are part of the mesh
* - Logs on to the best mesh network node it finds… if it doesnt find
* anything, it starts a new search in 5 seconds.
*
* @param ssid The name of your mesh. All nodes share same AP ssid. They are
* distinguished by BSSID.
* @param password Wifi password to your mesh.
* @param port the TCP port that you want the mesh server to run on. Defaults
* to 5555 if not specified.
* @param connectMode Switch between WIFI_AP, WIFI_STA and WIFI_AP_STA
* (default) mode
*/
void init(TSTRING ssid, TSTRING password, uint16_t port = 5555,
WiFiMode_t connectMode = WIFI_AP_STA, uint8_t channel = 1,
uint8_t hidden = 0, uint8_t maxconn = MAX_CONN) {
using namespace logger;
// Init random generator seed to generate delay variance
randomSeed(millis());
// Shut Wifi down and start with a blank slage
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
Log(STARTUP, "init(): %d\n",
WiFi.setAutoConnect(false)); // Disable autoconnect
WiFi.persistent(false);
// start configuration
if (!WiFi.mode(connectMode)) {
Log(GENERAL, "WiFi.mode() false");
}
_meshSSID = ssid;
_meshPassword = password;
_meshChannel = channel;
_meshHidden = hidden;
_meshMaxConn = maxconn;
_meshPort = port;
uint8_t MAC[] = {0, 0, 0, 0, 0, 0};
if (WiFi.softAPmacAddress(MAC) == 0) {
Log(ERROR, "init(): WiFi.softAPmacAddress(MAC) failed.\n");
}
uint32_t nodeId = tcp::encodeNodeId(MAC);
if (nodeId == 0) Log(ERROR, "NodeId set to 0\n");
this->init(nodeId);
tcpServerInit();
eventHandleInit();
_apIp = IPAddress(0, 0, 0, 0);
if (connectMode & WIFI_AP) {
apInit(nodeId); // setup AP
}
if (connectMode & WIFI_STA) {
this->initStation();
}
}
/** Initialize the mesh network
*
* Add this to your setup() function. This routine does the following things:
*
* - Starts a wifi network
* - Begins searching for other wifi networks that are part of the mesh
* - Logs on to the best mesh network node it finds… if it doesnt find
* anything, it starts a new search in 5 seconds.
*
* @param ssid The name of your mesh. All nodes share same AP ssid. They are
* distinguished by BSSID.
* @param password Wifi password to your mesh.
* @param port the TCP port that you want the mesh server to run on. Defaults
* to 5555 if not specified.
* @param connectMode Switch between WIFI_AP, WIFI_STA and WIFI_AP_STA
* (default) mode
*/
void init(TSTRING ssid, TSTRING password, Scheduler *baseScheduler,
uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA,
uint8_t channel = 1, uint8_t hidden = 0,
uint8_t maxconn = MAX_CONN) {
this->setScheduler(baseScheduler);
init(ssid, password, port, connectMode, channel, hidden, maxconn);
}
/**
* Connect (as a station) to a specified network and ip
*
* You can pass {0,0,0,0} as IP to have it connect to the gateway
*
* This stops the node from scanning for other (non specified) nodes
* and you should probably also use this node as an anchor: `setAnchor(true)`
*/
void stationManual(TSTRING ssid, TSTRING password, uint16_t port = 0,
IPAddress remote_ip = IPAddress(0, 0, 0, 0)) {
// Set station config
stationScan.manualIP = remote_ip;
// Start scan
stationScan.init(this, ssid, password, port);
stationScan.manual = true;
}
void initStation() {
stationScan.init(this, _meshSSID, _meshPassword, _meshPort);
mScheduler->addTask(stationScan.task);
stationScan.task.enable();
this->droppedConnectionCallbacks.push_back(
[this](uint32_t nodeId, bool station) {
if (station) {
if (WiFi.status() == WL_CONNECTED) WiFi.disconnect();
this->stationScan.connectToAP();
}
});
}
void tcpServerInit() {
using namespace logger;
Log(GENERAL, "tcpServerInit():\n");
_tcpListener = new AsyncServer(_meshPort);
painlessmesh::tcp::initServer<MeshConnection,
painlessmesh::Mesh<MeshConnection>>(
(*_tcpListener), (*this));
Log(STARTUP, "AP tcp server established on port %d\n", _meshPort);
return;
}
void tcpConnect() {
using namespace logger;
// TODO: move to Connection or StationConnection?
Log(GENERAL, "tcpConnect():\n");
if (stationScan.manual && stationScan.port == 0)
return; // We have been configured not to connect to the mesh
// TODO: We could pass this to tcpConnect instead of loading it here
if (WiFi.status() == WL_CONNECTED && WiFi.localIP()) {
AsyncClient *pConn = new AsyncClient();
IPAddress ip = WiFi.gatewayIP();
if (stationScan.manualIP) {
ip = stationScan.manualIP;
}
painlessmesh::tcp::connect<MeshConnection,
painlessmesh::Mesh<MeshConnection>>(
(*pConn), ip, stationScan.port, (*this));
} else {
Log(ERROR, "tcpConnect(): err Something un expected in tcpConnect()\n");
}
}
bool setHostname(const char *hostname) {
#ifdef ESP8266
return WiFi.hostname(hostname);
#elif defined(ESP32)
if (strlen(hostname) > 32) {
return false;
}
return WiFi.setHostname(hostname);
#endif // ESP8266
}
IPAddress getStationIP() { return WiFi.localIP(); }
IPAddress getAPIP() { return _apIp; }
void stop() {
// remove all WiFi events
#ifdef ESP32
WiFi.removeEvent(eventScanDoneHandler);
WiFi.removeEvent(eventSTAStartHandler);
WiFi.removeEvent(eventSTADisconnectedHandler);
WiFi.removeEvent(eventSTAGotIPHandler);
#elif defined(ESP8266)
eventSTAConnectedHandler = WiFiEventHandler();
eventSTADisconnectedHandler = WiFiEventHandler();
eventSTAGotIPHandler = WiFiEventHandler();
#endif // ESP32
// Stop scanning task
stationScan.task.setCallback(NULL);
mScheduler->deleteTask(stationScan.task);
painlessmesh::Mesh<MeshConnection>::stop();
// Shutdown wifi hardware
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
}
protected:
friend class ::StationScan;
TSTRING _meshSSID;
TSTRING _meshPassword;
uint8_t _meshChannel;
uint8_t _meshHidden;
uint8_t _meshMaxConn;
uint16_t _meshPort;
IPAddress _apIp;
StationScan stationScan;
void init(Scheduler *scheduler, uint32_t id) {
painlessmesh::Mesh<MeshConnection>::init(scheduler, id);
}
void init(uint32_t id) { painlessmesh::Mesh<MeshConnection>::init(id); }
void apInit(uint32_t nodeId) {
_apIp = IPAddress(10, (nodeId & 0xFF00) >> 8, (nodeId & 0xFF), 1);
IPAddress netmask(255, 255, 255, 0);
WiFi.softAPConfig(_apIp, _apIp, netmask);
WiFi.softAP(_meshSSID.c_str(), _meshPassword.c_str(), _meshChannel,
_meshHidden, _meshMaxConn);
}
void eventHandleInit() {
using namespace logger;
#ifdef ESP32
eventScanDoneHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
if (this->semaphoreTake()) {
Log(CONNECTION, "eventScanDoneHandler: SYSTEM_EVENT_SCAN_DONE\n");
this->stationScan.scanComplete();
this->semaphoreGive();
}
},
WiFiEvent_t::SYSTEM_EVENT_SCAN_DONE);
eventSTAStartHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
if (this->semaphoreTake()) {
Log(CONNECTION, "eventSTAStartHandler: SYSTEM_EVENT_STA_START\n");
this->semaphoreGive();
}
},
WiFiEvent_t::SYSTEM_EVENT_STA_START);
eventSTADisconnectedHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
if (this->semaphoreTake()) {
Log(CONNECTION,
"eventSTADisconnectedHandler: SYSTEM_EVENT_STA_DISCONNECTED\n");
this->droppedConnectionCallbacks.execute(0, true);
this->semaphoreGive();
}
},
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
eventSTAGotIPHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
if (this->semaphoreTake()) {
Log(CONNECTION, "eventSTAGotIPHandler: SYSTEM_EVENT_STA_GOT_IP\n");
this->tcpConnect(); // Connect to TCP port
this->semaphoreGive();
}
},
WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
eventSTAConnectedHandler = WiFi.onStationModeConnected(
[&](const WiFiEventStationModeConnected &event) {
// Log(CONNECTION, "Event: Station Mode Connected to \"%s\"\n",
// event.ssid.c_str());
Log(CONNECTION, "Event: Station Mode Connected\n");
});
eventSTADisconnectedHandler = WiFi.onStationModeDisconnected(
[&](const WiFiEventStationModeDisconnected &event) {
Log(CONNECTION, "Event: Station Mode Disconnected\n");
this->droppedConnectionCallbacks.execute(0, true);
});
eventSTAGotIPHandler =
WiFi.onStationModeGotIP([&](const WiFiEventStationModeGotIP &event) {
Log(CONNECTION,
"Event: Station Mode Got IP (IP: %s Mask: %s Gateway: %s)\n",
event.ip.toString().c_str(), event.mask.toString().c_str(),
event.gw.toString().c_str());
this->tcpConnect(); // Connect to TCP port
});
#endif // ESP32
return;
}
#ifdef ESP32
WiFiEventId_t eventScanDoneHandler;
WiFiEventId_t eventSTAStartHandler;
WiFiEventId_t eventSTADisconnectedHandler;
WiFiEventId_t eventSTAGotIPHandler;
#elif defined(ESP8266)
WiFiEventHandler eventSTAConnectedHandler;
WiFiEventHandler eventSTADisconnectedHandler;
WiFiEventHandler eventSTAGotIPHandler;
#endif // ESP8266
AsyncServer *_tcpListener;
};
} // namespace wifi
}; // namespace painlessmesh
#endif
#endif

View File

@@ -0,0 +1,271 @@
#ifndef _BOOST_ASYNCTCP_HPP_
#define _BOOST_ASYNCTCP_HPP_
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <iostream>
#ifndef TCP_MSS
#define TCP_MSS 1024
#endif
using boost::asio::ip::tcp;
#define ASYNC_WRITE_FLAG_COPY 0x01
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;
typedef boost::asio::ip::address IPAddress;
class AsyncClient {
public:
AsyncClient(boost::asio::io_service& io_service)
: _io_service(io_service), mSocket(_io_service) {}
bool connect(IPAddress ipaddress, uint16_t port) {
namespace ip = boost::asio::ip;
auto endpoint = ip::tcp::endpoint(ipaddress, port);
mSocket.async_connect(endpoint, [&](auto& ec) { this->handleConnect(ec); });
return true;
}
void initRead() {
mSocket.async_read_some(
boost::asio::buffer(mInputBuffer, TCP_MSS),
[&](auto& ec, auto len) { this->handleData(ec, len); });
}
size_t write(const void* data, size_t len,
size_t copy = ASYNC_WRITE_FLAG_COPY) {
if (writing) return 0;
writing = true;
if (copy == ASYNC_WRITE_FLAG_COPY) {
memcpy(mWriteBuffer, data, len);
mSocket.async_send(
boost::asio::buffer(mWriteBuffer, len),
[&](auto& ec, auto len) { this->handleWrite(ec, len); });
} else {
mSocket.async_send(
boost::asio::buffer(data, len),
[&](auto& ec, auto len) { this->handleWrite(ec, len); });
}
return len;
}
// Dummy functions for compatibility with ESPAsycnTCP
void send() {}
void setNoDelay(bool value = true) {}
void setRxTimeout(uint32_t timeout) {}
const char* errorToString(int8_t error) { return ""; }
// TODO: check if there is a better one
void abort() { this->close(true); }
void onConnect(AcConnectHandler cb, void* arg = 0) {
_connect_cb = cb;
_connect_cb_arg = arg;
}
void onDisconnect(AcConnectHandler cb, void* arg = 0) {
_discard_cb = cb;
_discard_cb_arg = arg;
}
void onAck(AcAckHandler cb, void* arg = 0) {
_sent_cb = cb;
_sent_cb_arg = arg;
}
void onError(AcErrorHandler cb, void* arg = 0) {
_error_cb = cb;
_error_cb_arg = arg;
}
void onData(AcDataHandler cb, void* arg = 0) {
_recv_cb = cb;
_recv_cb_arg = arg;
}
void onTimeout(AcTimeoutHandler cb, void* arg = 0) {
_timeout_cb = cb;
_timeout_cb_arg = arg;
}
void onPoll(AcConnectHandler cb, void* arg = 0) {
_poll_cb = cb;
_poll_cb_arg = arg;
}
bool connected() { return mSocket.is_open(); }
bool freeable() { return !this->connected(); }
void close(bool now = true) {
if (this->connected()) {
mSocket.close();
}
if (!disconnectCalled) {
disconnectCalled = true;
if (_discard_cb) {
_discard_cb(_discard_cb_arg, this);
}
}
}
~AsyncClient() { close(true); }
size_t space() {
// This could be more intelligent, but simple and safe for now
if (writing) return 0;
return TCP_MSS;
}
bool canSend() { return this->space() > 0; }
size_t ack(size_t len) {
// Currently assumes that len is actually the size of the data that was sent
// Otherwise it will break
initRead();
return len;
}
tcp::socket& socket() { return mSocket; }
protected:
boost::asio::io_service& _io_service;
tcp::socket mSocket;
char mInputBuffer[TCP_MSS];
char mWriteBuffer[TCP_MSS];
bool writing = false;
bool disconnectCalled = false;
AcConnectHandler _connect_cb = 0;
void* _connect_cb_arg = 0;
AcConnectHandler _discard_cb = 0;
void* _discard_cb_arg = 0;
AcAckHandler _sent_cb = 0;
void* _sent_cb_arg = 0;
AcErrorHandler _error_cb = 0;
void* _error_cb_arg = 0;
AcDataHandler _recv_cb = 0;
void* _recv_cb_arg = 0;
AcTimeoutHandler _timeout_cb = 0;
void* _timeout_cb_arg = 0;
AcConnectHandler _poll_cb = 0;
void* _poll_cb_arg = 0;
void handleConnect(const boost::system::error_code& ec) {
if (!ec) {
if (_connect_cb) _connect_cb(_connect_cb_arg, this);
initRead();
} else {
handleError(ec);
if (this->connected()) close(true);
}
}
void handleData(const boost::system::error_code& ec, size_t len) {
if (disconnectCalled) return;
if (!ec) {
if (_recv_cb) {
_recv_cb(_recv_cb_arg, this, (void*)mInputBuffer, len);
}
} else {
handleError(ec);
close(true);
}
}
void handleWrite(const boost::system::error_code& ec, size_t len) {
if (disconnectCalled) return;
if (!ec) {
if (_sent_cb) {
// TODO send actual time
_sent_cb(_sent_cb_arg, this, len, 0);
}
writing = false;
} else {
handleError(ec);
close(true);
}
}
void handleError(const boost::system::error_code& ec) {
if (ec != boost::asio::error::eof && _error_cb) {
_error_cb(_error_cb_arg, this, ec.value());
}
}
};
class AsyncServer {
public:
AsyncServer(boost::asio::io_service& io_service, uint16_t port)
: _io_service(io_service),
// mSocket(io_service),
_port(port),
mAcceptor(io_service) {}
~AsyncServer() { this->end(); }
void onClient(AcConnectHandler cb, void* arg = 0) {
_connect_cb = cb;
_connect_cb_arg = arg;
}
// begin() should do start_accept (see server2):w
void begin() {
mAcceptor.open(tcp::v4());
int one = 1;
setsockopt(mAcceptor.native_handle(), SOL_SOCKET,
SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one));
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), _port);
mAcceptor.bind(endpoint);
mAcceptor.listen();
initAccept();
}
// Acceptor stop?
void end() { mAcceptor.close(); }
// Dummy function for compatibility with ESPAsycnTCP
void setNoDelay(bool value = true) {}
protected:
boost::asio::io_service& _io_service;
uint16_t _port;
tcp::acceptor mAcceptor;
AcConnectHandler _connect_cb = 0;
void* _connect_cb_arg = 0;
void initAccept() {
AsyncClient* client = new AsyncClient(mAcceptor.get_io_service());
mAcceptor.async_accept(
client->socket(), [this, client](const boost::system::error_code& e) {
if (!e && this->_connect_cb) {
this->_connect_cb(this->_connect_cb_arg, client);
this->initAccept();
client->initRead();
} else
std::cout << "Error: " << e.message() << std::endl;
});
}
};
#endif

View File

@@ -0,0 +1,63 @@
#ifndef _EASY_MESH_H_
#define _EASY_MESH_H_
#define _TASK_PRIORITY // Support for layered scheduling priority
#define _TASK_STD_FUNCTION
#include <Arduino.h>
#include <functional>
#include <list>
#include <memory>
#include "painlessmesh/configuration.hpp"
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif // ESP32
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
#include "painlessMeshConnection.h"
#include "painlessMeshSTA.h"
#include "arduino/wifi.hpp"
#endif
#ifdef PAINLESSMESH_ENABLE_OTA
#include "painlessmesh/ota.hpp"
#endif
#include "painlessmesh/buffer.hpp"
#include "painlessmesh/layout.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/mesh.hpp"
#include "painlessmesh/ntp.hpp"
#include "painlessmesh/plugin.hpp"
#include "painlessmesh/protocol.hpp"
#include "painlessmesh/router.hpp"
#include "painlessmesh/tcp.hpp"
using namespace painlessmesh::logger;
#define MIN_FREE_MEMORY \
4000 // Minimum free memory, besides here all packets in queue are discarded.
#define MAX_MESSAGE_QUEUE \
50 // MAX number of unsent messages in queue. Newer messages are discarded
#define MAX_CONSECUTIVE_SEND 5 // Max message burst
/*! \mainpage painlessMesh: A painless way to setup a mesh.
*
* painlessMesh is designed in a modular way, with many parent classes. The best
* place to get started with the documentation is to have a look at
* painlessmesh::wifi::Mesh (the main painlessMesh class is an alias (typedef)
* of the painlessmesh::wifi::Mesh class). Make sure to also explore the public
* member functions inherited from other classes, to get full information on the
* functions available to you.
*/
#ifndef PAINLESSMESH_ENABLE_ARDUINO_WIFI
class MeshConnection;
using painlessMesh = painlessmesh::Mesh<MeshConnection>;
#endif
#endif // _EASY_MESH_H_

View File

@@ -0,0 +1,277 @@
//
// painlessMeshConnection.cpp
//
//
// Created by Bill Gray on 7/26/16.
//
//
#include "painlessMeshConnection.h"
#include "painlessMesh.h"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
using namespace painlessmesh;
//#include "lwip/priv/tcpip_priv.h"
extern LogClass Log;
static painlessmesh::buffer::temp_buffer_t shared_buffer;
ICACHE_FLASH_ATTR MeshConnection::MeshConnection(
AsyncClient *client_ptr, painlessmesh::Mesh<MeshConnection> *pMesh,
bool is_station) {
using namespace painlessmesh;
station = is_station;
mesh = pMesh;
client = client_ptr;
client->setNoDelay(true);
if (station) { // we are the station, start nodeSync
Log(CONNECTION, "meshConnectedCb(): we are STA\n");
} else {
Log(CONNECTION, "meshConnectedCb(): we are AP\n");
}
}
ICACHE_FLASH_ATTR MeshConnection::~MeshConnection() {
Log(CONNECTION, "~MeshConnection():\n");
this->close();
if (!client->freeable()) {
Log(CONNECTION, "~MeshConnection(): Closing pcb\n");
client->close(true);
}
client->abort();
delete client;
}
void MeshConnection::initTCPCallbacks() {
using namespace logger;
client->onDisconnect(
[self = this->shared_from_this()](void *arg, AsyncClient *client) {
// Making a copy of self->mesh pointer, because self->close() can
// invalidate the original pointer, causing a segmentation fault
// when trying to access self->mesh afterwards
auto m = self->mesh;
if (m->semaphoreTake()) {
Log(CONNECTION, "onDisconnect(): dropping %u now= %u\n", self->nodeId,
m->getNodeTime());
self->close();
m->semaphoreGive();
}
},
NULL);
client->onData(
[self = this->shared_from_this()](void *arg, AsyncClient *client,
void *data, size_t len) {
using namespace logger;
if (self->mesh->semaphoreTake()) {
Log(COMMUNICATION, "onData(): fromId=%u\n", self ? self->nodeId : 0);
self->receiveBuffer.push(static_cast<const char *>(data), len,
shared_buffer);
// Signal that we are done
self->client->ack(len);
self->readBufferTask.forceNextIteration();
self->mesh->semaphoreGive();
}
},
NULL);
client->onAck(
[self = this->shared_from_this()](void *arg, AsyncClient *client,
size_t len, uint32_t time) {
using namespace logger;
if (self->mesh->semaphoreTake()) {
self->sentBufferTask.forceNextIteration();
self->mesh->semaphoreGive();
}
},
NULL);
client->onError(
[self = this->shared_from_this()](void *arg, AsyncClient *client,
int8_t err) {
if (self->mesh->semaphoreTake()) {
// When AsyncTCP gets an error it will call both
// onError and onDisconnect
// so we handle this in the onDisconnect callback
Log(CONNECTION, "tcp_err(): MeshConnection %s\n",
client->errorToString(err));
self->mesh->semaphoreGive();
}
},
NULL);
}
void MeshConnection::initTasks() {
using namespace logger;
timeOutTask.set(NODE_TIMEOUT, TASK_ONCE, [self = this->shared_from_this()]() {
Log(CONNECTION, "Time out reached\n");
self->close();
});
mesh->mScheduler->addTask(timeOutTask);
this->nodeSyncTask.set(
TASK_MINUTE, TASK_FOREVER, [self = this->shared_from_this()]() {
Log(SYNC, "nodeSyncTask(): request with %u\n", self->nodeId);
router::send<protocol::NodeSyncRequest, MeshConnection>(
self->request(self->mesh->asNodeTree()), self);
self->timeOutTask.disable();
self->timeOutTask.restartDelayed();
});
mesh->mScheduler->addTask(this->nodeSyncTask);
if (station)
this->nodeSyncTask.enable();
else
this->nodeSyncTask.enableDelayed(10 * TASK_SECOND);
receiveBuffer = painlessmesh::buffer::ReceiveBuffer<TSTRING>();
readBufferTask.set(
TASK_SECOND, TASK_FOREVER, [self = this->shared_from_this()]() {
Log(GENERAL, "readBufferTask()\n");
if (!self->receiveBuffer.empty()) {
TSTRING frnt = self->receiveBuffer.front();
self->receiveBuffer.pop_front();
if (!self->receiveBuffer.empty())
self->readBufferTask.forceNextIteration();
router::routePackage<MeshConnection>(
(*self->mesh), self->shared_from_this(), frnt,
self->mesh->callbackList, self->mesh->getNodeTime());
}
});
mesh->mScheduler->addTask(readBufferTask);
readBufferTask.enableDelayed();
sentBufferTask.set(
TASK_SECOND, TASK_FOREVER, [self = this->shared_from_this()]() {
Log(GENERAL, "sentBufferTask()\n");
if (!self->sentBuffer.empty() && self->client->canSend()) {
auto ret = self->writeNext();
if (ret)
self->sentBufferTask.forceNextIteration();
else
self->sentBufferTask.delay(100 * TASK_MILLISECOND);
}
});
mesh->mScheduler->addTask(sentBufferTask);
sentBufferTask.enableDelayed();
}
void ICACHE_FLASH_ATTR MeshConnection::close() {
if (!connected) return;
Log(CONNECTION, "MeshConnection::close() %u.\n", this->nodeId);
this->connected = false;
this->timeSyncTask.setCallback(NULL);
this->nodeSyncTask.setCallback(NULL);
this->readBufferTask.setCallback(NULL);
this->sentBufferTask.setCallback(NULL);
this->timeOutTask.setCallback(NULL);
this->timeSyncTask.disable();
this->nodeSyncTask.disable();
this->readBufferTask.disable();
this->sentBufferTask.disable();
this->timeOutTask.disable();
this->client->onDisconnect(NULL, NULL);
this->client->onError(NULL, NULL);
this->client->onData(NULL, NULL);
this->client->onAck(NULL, NULL);
mesh->addTask(
[mesh = this->mesh, nodeId = this->nodeId, station = this->station]() {
Log(CONNECTION, "closingTask(): dropping %u now= %u\n", nodeId,
mesh->getNodeTime());
mesh->changedConnectionCallbacks.execute(nodeId);
mesh->droppedConnectionCallbacks.execute(nodeId, station);
});
if (client->connected()) {
Log(CONNECTION, "close(): Closing pcb\n");
client->close();
}
receiveBuffer.clear();
sentBuffer.clear();
NodeTree::clear();
Log(CONNECTION, "MeshConnection::close() done. Was station: %d.\n",
this->station);
}
bool ICACHE_FLASH_ATTR MeshConnection::addMessage(TSTRING &message,
bool priority) {
if (ESP.getFreeHeap() - message.length() >=
MIN_FREE_MEMORY) { // If memory heap is enough, queue the message
if (priority) {
sentBuffer.push(message, priority);
Log(COMMUNICATION,
"addMessage(): Package sent to queue beginning -> %d , "
"FreeMem: %d\n",
sentBuffer.size(), ESP.getFreeHeap());
} else {
if (sentBuffer.size() < MAX_MESSAGE_QUEUE) {
sentBuffer.push(message, priority);
Log(COMMUNICATION,
"addMessage(): Package sent to queue end -> %d , FreeMem: "
"%d\n",
sentBuffer.size(), ESP.getFreeHeap());
} else {
Log(ERROR, "addMessage(): Message queue full -> %d , FreeMem: %d\n",
sentBuffer.size(), ESP.getFreeHeap());
sentBufferTask.forceNextIteration();
return false;
}
}
sentBufferTask.forceNextIteration();
return true;
} else {
// connection->sendQueue.clear(); // Discard all messages if free memory is
// low
Log(DEBUG, "addMessage(): Memory low, message was discarded\n");
sentBufferTask.forceNextIteration();
return false;
}
}
bool ICACHE_FLASH_ATTR MeshConnection::writeNext() {
if (sentBuffer.empty()) {
Log(COMMUNICATION, "writeNext(): sendQueue is empty\n");
return false;
}
auto len = sentBuffer.requestLength(shared_buffer.length);
auto snd_len = client->space();
if (len > snd_len) len = snd_len;
if (len > 0) {
// sentBuffer.read(len, shared_buffer);
// auto written = client->write(shared_buffer.buffer, len, 1);
auto data_ptr = sentBuffer.readPtr(len);
auto written = client->write(data_ptr, len, 1);
if (written == len) {
Log(COMMUNICATION, "writeNext(): Package sent\n");
client->send(); // TODO only do this for priority messages
sentBuffer.freeRead();
sentBufferTask.forceNextIteration();
return true;
} else if (written == 0) {
Log(COMMUNICATION,
"writeNext(): tcp_write Failed node=%u. Resending later\n", nodeId);
return false;
} else {
Log(ERROR,
"writeNext(): Less written than requested. Please report bug on "
"the issue tracker\n");
return false;
}
} else {
Log(COMMUNICATION, "writeNext(): tcp_sndbuf not enough space\n");
return false;
}
}

View File

@@ -0,0 +1,62 @@
#ifndef _PAINLESS_MESH_CONNECTION_H_
#define _PAINLESS_MESH_CONNECTION_H_
#define _TASK_PRIORITY // Support for layered scheduling priority
#define _TASK_STD_FUNCTION
#include <TaskSchedulerDeclarations.h>
#ifdef ESP32
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#endif // ESP32
#include "painlessmesh/buffer.hpp"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/layout.hpp"
#include "painlessmesh/mesh.hpp"
#ifndef PAINLESSMESH_ENABLE_ARDUINO_WIFI
class MeshConnection;
using painlessMesh = painlessmesh::Mesh<MeshConnection>;
#endif
class MeshConnection : public painlessmesh::layout::Neighbour,
public std::enable_shared_from_this<MeshConnection> {
public:
AsyncClient *client;
painlessmesh::Mesh<MeshConnection> *mesh;
bool newConnection = true;
bool connected = true;
bool station = true;
// Timestamp to be compared in manageConnections() to check response
// for timeout
uint32_t timeDelayLastRequested = 0;
bool addMessage(TSTRING &message, bool priority = false);
bool writeNext();
painlessmesh::buffer::ReceiveBuffer<TSTRING> receiveBuffer;
painlessmesh::buffer::SentBuffer<TSTRING> sentBuffer;
Task nodeSyncTask;
Task timeSyncTask;
Task readBufferTask;
Task sentBufferTask;
Task timeOutTask;
MeshConnection(AsyncClient *client, painlessmesh::Mesh<MeshConnection> *pMesh,
bool station);
~MeshConnection();
void initTCPCallbacks();
void initTasks();
void handleMessage(TSTRING msg, uint32_t receivedAt);
void close();
friend painlessmesh::Mesh<MeshConnection>;
};
#endif

View File

@@ -0,0 +1,206 @@
//
// painlessMeshSTA.cpp
//
//
// Created by Bill Gray on 7/26/16.
//
//
#include "painlessmesh/configuration.hpp"
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
#include <Arduino.h>
#include <algorithm>
#include <memory>
#include "arduino/wifi.hpp"
#include "painlessmesh/layout.hpp"
#include "painlessmesh/tcp.hpp"
extern painlessmesh::logger::LogClass Log;
//***********************************************************************
// Calculate NodeID from a hardware MAC address
void ICACHE_FLASH_ATTR StationScan::init(painlessmesh::wifi::Mesh *pMesh,
TSTRING pssid, TSTRING ppassword,
uint16_t pport) {
ssid = pssid;
password = ppassword;
mesh = pMesh;
port = pport;
task.set(SCAN_INTERVAL, TASK_FOREVER, [this]() { stationScan(); });
}
// Starts scan for APs whose name is Mesh SSID
void ICACHE_FLASH_ATTR StationScan::stationScan() {
using namespace painlessmesh::logger;
Log(CONNECTION, "stationScan(): %s\n", ssid.c_str());
#ifdef ESP32
WiFi.scanNetworks(true, true);
#elif defined(ESP8266)
WiFi.scanNetworksAsync([&](int networks) { this->scanComplete(); }, true);
#endif
task.delay(10 * SCAN_INTERVAL); // Scan should be completed by then and next
// step called. If not then we restart here.
return;
}
void ICACHE_FLASH_ATTR StationScan::scanComplete() {
using namespace painlessmesh::logger;
Log(CONNECTION, "scanComplete(): Scan finished\n");
aps.clear();
Log(CONNECTION, "scanComplete():-- > Cleared old APs.\n");
auto num = WiFi.scanComplete();
if (num == WIFI_SCAN_RUNNING || num == WIFI_SCAN_FAILED) return;
Log(CONNECTION, "scanComplete(): num = %d\n", num);
for (auto i = 0; i < num; ++i) {
WiFi_AP_Record_t record;
record.ssid = WiFi.SSID(i);
if (record.ssid != ssid) {
if (record.ssid.equals("") && mesh->_meshHidden) {
// Hidden mesh
record.ssid = ssid;
} else {
continue;
}
}
record.rssi = WiFi.RSSI(i);
if (record.rssi == 0) continue;
memcpy((void *)&record.bssid, (void *)WiFi.BSSID(i), sizeof(record.bssid));
aps.push_back(record);
Log(CONNECTION, "\tfound : %s, %ddBm\n", record.ssid.c_str(),
(int16_t)record.rssi);
}
Log(CONNECTION, "\tFound %d nodes\n", aps.size());
task.yield([this]() {
// Task filter all unknown
filterAPs();
// Next task is to sort by strength
task.yield([this] {
aps.sort([](WiFi_AP_Record_t a, WiFi_AP_Record_t b) {
return a.rssi > b.rssi;
});
// Next task is to connect to the top ap
task.yield([this]() { connectToAP(); });
});
});
}
void ICACHE_FLASH_ATTR StationScan::filterAPs() {
auto ap = aps.begin();
while (ap != aps.end()) {
auto apNodeId = painlessmesh::tcp::encodeNodeId(ap->bssid);
if (painlessmesh::router::findRoute<MeshConnection>((*mesh), apNodeId) !=
NULL) {
ap = aps.erase(ap);
} else {
ap++;
}
}
}
void ICACHE_FLASH_ATTR StationScan::requestIP(WiFi_AP_Record_t &ap) {
using namespace painlessmesh::logger;
Log(CONNECTION, "connectToAP(): Best AP is %u<---\n",
painlessmesh::tcp::encodeNodeId(ap.bssid));
WiFi.begin(ap.ssid.c_str(), password.c_str(), mesh->_meshChannel, ap.bssid);
return;
}
void ICACHE_FLASH_ATTR StationScan::connectToAP() {
using namespace painlessmesh;
using namespace painlessmesh::logger;
// Next task will be to rescan
task.setCallback([this]() { stationScan(); });
if (manual) {
if ((WiFi.SSID() == ssid) && WiFi.status() == WL_CONNECTED) {
Log(CONNECTION,
"connectToAP(): Already connected using manual connection. "
"Disabling scanning.\n");
task.disable();
return;
} else {
if (WiFi.status() == WL_CONNECTED) {
mesh->closeConnectionSTA();
task.enableDelayed(10 * SCAN_INTERVAL);
return;
} else if (aps.empty() || !ssid.equals(aps.begin()->ssid)) {
task.enableDelayed(SCAN_INTERVAL);
return;
}
}
}
if (aps.empty()) {
// No unknown nodes found
if (WiFi.status() == WL_CONNECTED &&
!(mesh->shouldContainRoot && !layout::isRooted(mesh->asNodeTree()))) {
// if already connected -> scan slow
Log(CONNECTION,
"connectToAP(): Already connected, and no unknown nodes found: "
"scan rate set to slow\n");
task.delay(random(2, 4) * SCAN_INTERVAL);
} else {
// else scan fast (SCAN_INTERVAL)
Log(CONNECTION,
"connectToAP(): No unknown nodes found scan rate set to "
"normal\n");
task.setInterval(0.5 * SCAN_INTERVAL);
}
mesh->stability += min(1000 - mesh->stability, (size_t)25);
} else {
if (WiFi.status() == WL_CONNECTED) {
Log(CONNECTION,
"connectToAP(): Unknown nodes found. Current stability: %s\n",
String(mesh->stability).c_str());
int prob = mesh->stability;
if (!mesh->shouldContainRoot)
// Slower when part of bigger network
prob /= 2 * (1 + layout::size(mesh->asNodeTree()));
if (!layout::isRooted(mesh->asNodeTree()) && random(0, 1000) < prob) {
Log(CONNECTION, "connectToAP(): Reconfigure network: %s\n",
String(prob).c_str());
// close STA connection, this will trigger station disconnect which
// will trigger connectToAP()
mesh->closeConnectionSTA();
mesh->stability = 0; // Discourage switching again
// wifiEventCB should be triggered before this delay runs out
// and reset the connecting
task.delay(3 * SCAN_INTERVAL);
} else {
if (mesh->shouldContainRoot)
// Increase scanning rate, because we want to find root
task.delay(0.5 * SCAN_INTERVAL);
else
task.delay(random(2, 4) * SCAN_INTERVAL);
}
} else {
// Else try to connect to first
auto ap = aps.front();
aps.pop_front(); // drop bestAP from mesh list, so if doesn't work out,
// we can try the next one
requestIP(ap);
// Trying to connect, if that fails we will reconnect later
Log(CONNECTION,
"connectToAP(): Trying to connect, scan rate set to "
"4*normal\n");
task.delay(2 * SCAN_INTERVAL);
}
}
}
#endif

View File

@@ -0,0 +1,42 @@
#ifndef _PAINLESS_MESH_STA_H_
#define _PAINLESS_MESH_STA_H_
#include "painlessmesh/configuration.hpp"
#include <list>
typedef struct {
uint8_t bssid[6];
TSTRING ssid;
int8_t rssi;
} WiFi_AP_Record_t;
class StationScan {
public:
Task task; // Station scanning for connections
StationScan() {}
void init(painlessmesh::wifi::Mesh *pMesh, TSTRING ssid, TSTRING password,
uint16_t port);
void stationScan();
void scanComplete();
void filterAPs();
void connectToAP();
protected:
TSTRING ssid;
TSTRING password;
painlessMesh *mesh;
uint16_t port;
std::list<WiFi_AP_Record_t> aps;
void requestIP(WiFi_AP_Record_t &ap);
// Manually configure network and ip
bool manual = false;
IPAddress manualIP = IPAddress(0, 0, 0, 0);
friend painlessMesh;
};
#endif

View File

@@ -0,0 +1,123 @@
#ifndef _PAINLESS_MESH_BASE64_HPP_
#define _PAINLESS_MESH_BASE64_HPP_
#include <string>
#include "painlessmesh/configuration.hpp"
namespace painlessmesh {
namespace base64 {
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
static const TSTRING chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
inline TSTRING encode(unsigned char const* bytes_to_encode,
unsigned int in_len) {
TSTRING ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++) ret += chars[char_array_4[i]];
i = 0;
}
}
if (i) {
for (j = i; j < 3; j++) char_array_3[j] = '\0';
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] =
((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] =
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (j = 0; (j < i + 1); j++) ret += chars[char_array_4[j]];
while ((i++ < 3)) ret += '=';
}
return ret;
}
inline TSTRING encode(const TSTRING& str64)
{
return encode((unsigned char*)str64.c_str(), str64.length());
}
static const int B64index[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0,
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63,
0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
inline const TSTRING decode(const void* data, const size_t &len)
{
if (len == 0) return "";
unsigned char *p = (unsigned char*) data;
size_t j = 0,
pad1 = len % 4 || p[len - 1] == '=',
pad2 = pad1 && (len % 4 > 2 || p[len - 2] != '=');
const size_t last = (len - pad1) / 4 << 2;
#ifdef PAINLESSMESH_ENABLE_STD_STRING
TSTRING result(last / 4 * 3 + pad1 + pad2, '\0');
#else
TSTRING result;
result.reserve(last / 4 * 3 + pad1 + pad2);
for (size_t i = 0; i < last / 4 * 3 + pad1 + pad2; ++i)
result.concat('\0');
#endif
unsigned char *str = (unsigned char*) &result[0];
for (size_t i = 0; i < last; i += 4)
{
int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
str[j++] = n >> 16;
str[j++] = n >> 8 & 0xFF;
str[j++] = n & 0xFF;
}
if (pad1)
{
int n = B64index[p[last]] << 18 | B64index[p[last + 1]] << 12;
str[j++] = n >> 16;
if (pad2)
{
n |= B64index[p[last + 2]] << 6;
str[j++] = n >> 8 & 0xFF;
}
}
return result;
}
inline TSTRING decode(const TSTRING& str64)
{
return decode(str64.c_str(), str64.length());
}
}
}
#endif

View File

@@ -0,0 +1,227 @@
#ifndef _PAINLESS_MESH_BUFFER_HPP_
#define _PAINLESS_MESH_BUFFER_HPP_
#include <list>
#include "Arduino.h"
#include "painlessmesh/configuration.hpp"
#ifndef TCP_MSS
#define TCP_MSS 1024
#endif
namespace painlessmesh {
namespace buffer {
// Temporary buffer used by ReceiveBuffer and SentBuffer
struct temp_buffer_t {
size_t length = TCP_MSS;
char buffer[TCP_MSS];
};
/**
* \brief ReceivedBuffer handles cstrings and stores them as strings
*/
template <class T>
class ReceiveBuffer {
public:
ReceiveBuffer() { buffer = T(); }
/**
* Push a message into the buffer
*/
void push(const char *cstr, size_t length, temp_buffer_t &buf) {
auto data_ptr = cstr;
do {
auto len = strnlen(data_ptr, length);
do {
auto read_len = std::min(len, buf.length);
memcpy(buf.buffer, data_ptr, read_len);
buf.buffer[read_len] = '\0';
auto newBuffer = T(buf.buffer);
stringAppend(buffer, newBuffer);
len -= newBuffer.length();
length -= newBuffer.length();
data_ptr += newBuffer.length() * sizeof(char);
} while (len > 0);
if (length > 0) {
// Skip/remove the '\0' between the messages
length -= 1;
data_ptr += 1 * sizeof(char);
if (buffer.length() > 0) { // skip empty buffers
jsonStrings.push_back(buffer);
buffer = T();
}
}
} while (length > 0);
}
/**
* Get the oldest message from the buffer
*/
T front() {
if (!empty()) return (*jsonStrings.begin());
return T();
}
/**
* Remove the oldest message from the buffer
*/
void pop_front() { jsonStrings.pop_front(); }
/**
* Is the buffer empty
*/
bool empty() { return jsonStrings.empty(); }
/**
* Clear the buffer
*/
void clear() {
jsonStrings.clear();
buffer = T();
}
private:
T buffer;
std::list<T> jsonStrings;
/**
* Helper function to deal with difference Arduino String
* and std::string
*/
inline void stringAppend(T &buffer, T &newBuffer) { buffer.concat(newBuffer); };
};
#ifdef PAINLESSMESH_ENABLE_STD_STRING
template <>
inline void ReceiveBuffer<std::string>::stringAppend(std::string &buffer,
std::string &newBuffer) {
buffer.append(newBuffer);
}
#endif
/**
* \brief SentBuffer stores messages (strings) and allows them to be read in any
* length
*/
template <class T>
class SentBuffer {
public:
SentBuffer(){};
/**
* push a message into the buffer.
*
* \param priority Whether this is a high priority message.
*
* High priority messages will be sent to the front of the buffer
*/
void push(T message, bool priority = false) {
if (priority) {
if (clean)
jsonStrings.push_front(message);
else
jsonStrings.insert((++jsonStrings.begin()), message);
} else
jsonStrings.push_back(message);
}
/**
* Request whether the passed length is readable
*
* Returns the actual length available (<= the requested length
*/
size_t requestLength(size_t buffer_length) {
if (jsonStrings.empty())
return 0;
else
// String.toCharArray automatically turns the last character into
// a \0, we need the extra space to deal with that annoyance
return std::min(buffer_length - 1, jsonStrings.begin()->length() + 1);
}
/**
* Read the given length into the passed buffer
*
* Note the user should first make sure the requested length is available
* using `SentBuffer.requestLength()`, otherwise this function might fail.
* Note that if multiple messages are read then they are separated using '\0'.
*/
void read(size_t length, temp_buffer_t &buf) {
// TODO: I don't think we actually need to copy here, and/or
// we should add a non-copy mode, that returns a pointer directly
// to the data (using c_str()).
//
// Note that toCharrArray always null terminates
// independent of whether the whole string was read so we use one extra
// space
jsonStrings.front().toCharArray(buf.buffer, length + 1);
last_read_size = length;
}
/**
* Returns a pointer directly to the oldest message
*
* Note the user should first make sure the requested length is available
* using `SentBuffer.requestLength()`, otherwise this function might fail.
* Note that if multiple messages are read then they are separated using '\0'.
*/
const char* readPtr(size_t length) {
last_read_size = length;
return jsonStrings.front().c_str();
}
/**
* Clear the previously read messages from the buffer.
*
* Should be called after a call of read() to clear the buffer.
*/
void freeRead() {
if (last_read_size == jsonStrings.begin()->length() + 1) {
jsonStrings.pop_front();
clean = true;
} else {
// jsonStrings.begin()->remove(0, last_read_size);
stringEraseFront((*jsonStrings.begin()), last_read_size);
clean = false;
}
last_read_size = 0;
}
bool empty() { return jsonStrings.empty(); }
void clear() { jsonStrings.clear(); }
size_t size() { return jsonStrings.size(); }
private:
size_t last_read_size = 0;
bool clean = true;
std::list<T> jsonStrings;
inline void stringEraseFront(T &string, size_t length) { string.remove(0, length); };
};
#ifdef PAINLESSMESH_ENABLE_STD_STRING
template <>
inline void SentBuffer<std::string>::read(size_t length, temp_buffer_t &buf) {
jsonStrings.front().copy(buf.buffer, length);
// Mimic String.toCharArray behaviour, which will insert
// null termination at the end of original string and the last
// character
if (length == jsonStrings.front().length() + 1) buf.buffer[length - 1] = '\0';
buf.buffer[length] = '\0';
last_read_size = length;
}
template <>
inline void SentBuffer<std::string>::stringEraseFront(std::string &string,
size_t length) {
string.erase(0, length);
};
#endif
} // namespace buffer
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,79 @@
#ifndef _PAINLESS_MESH_CALLBACK_HPP_
#define _PAINLESS_MESH_CALLBACK_HPP_
#include<map>
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
extern painlessmesh::logger::LogClass Log;
namespace painlessmesh {
/**
* Helper functions to work with multiple callbacks
*/
namespace callback {
template <typename... Args>
class List {
public:
int execute(Args... args) {
for (auto&& f : callbacks) {
f(args...);
}
return callbacks.size();
}
/*
* Needs to be wrapped into semaphore
*
int executeWithScheduler(Scheduler& scheduler, Args... args) {
scheduler.execute();
for (auto&& f : callbacks) {
f(args...);
scheduler.execute();
}
return callbacks.size();
}*/
/** Add callbacks to the end of the list.
*/
void push_back(std::function<void(Args...)> func) {
callbacks.push_back(func);
}
protected:
std::list<std::function<void(Args...)>> callbacks;
};
/**
* Manage callbacks for receiving packages
*/
template <typename... Args>
class PackageCallbackList {
public:
/**
* Add a callback for specific package id
*/
void onPackage(int id, std::function<void(Args...)> func) {
callbackMap[id].push_back(func);
}
/**
* Execute all the callbacks associated with a certain package
*/
int execute(int id, Args... args) { return callbackMap[id].execute(args...); }
protected:
std::map<int, List<Args...>> callbackMap;
};
template <typename T>
using MeshPackageCallbackList =
PackageCallbackList<protocol::Variant, std::shared_ptr<T>, uint32_t>;
} // namespace callback
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,57 @@
#include "Arduino.h"
#ifndef _PAINLESS_MESH_CONFIGURATION_HPP_
#define _PAINLESS_MESH_CONFIGURATION_HPP_
#include<list>
#define _TASK_PRIORITY // Support for layered scheduling priority
#define _TASK_STD_FUNCTION
#include <TaskSchedulerDeclarations.h>
#define ARDUINOJSON_USE_LONG_LONG 1
#undef ARDUINOJSON_ENABLE_STD_STRING
#include <ArduinoJson.h>
#undef ARDUINOJSON_ENABLE_STD_STRING
// Enable (arduino) wifi support
#define PAINLESSMESH_ENABLE_ARDUINO_WIFI
// Enable OTA support
#define PAINLESSMESH_ENABLE_OTA
#define NODE_TIMEOUT 5 * TASK_SECOND
#define SCAN_INTERVAL 30 * TASK_SECOND // AP scan period in ms
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif // ESP32
typedef String TSTRING;
// backward compatibility
template <typename T>
using SimpleList = std::list<T>;
namespace painlessmesh {
namespace wifi {
class Mesh;
};
}; // namespace painlessmesh
/** A convenience typedef to access the mesh class*/
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
using painlessMesh = painlessmesh::wifi::Mesh;
#endif
#ifdef ESP32
#define MAX_CONN 10
#else
#define MAX_CONN 4
#endif // DEBUG
#endif

View File

@@ -0,0 +1,183 @@
#ifndef _PAINLESS_MESH_LAYOUT_HPP_
#define _PAINLESS_MESH_LAYOUT_HPP_
#include <list>
#include <memory>
#include "painlessmesh/protocol.hpp"
namespace painlessmesh {
namespace layout {
#include <memory>
/**
* Whether the tree contains the given nodeId
*/
inline bool contains(protocol::NodeTree nodeTree, uint32_t nodeId) {
if (nodeTree.nodeId == nodeId) {
return true;
}
for (auto&& s : nodeTree.subs) {
if (contains(s, nodeId)) return true;
}
return false;
}
inline protocol::NodeTree excludeRoute(protocol::NodeTree&& tree,
uint32_t exclude) {
// Make sure to exclude any subs with nodeId == 0,
// even if exlude is not set to zero
tree.subs.remove_if([exclude](protocol::NodeTree s) {
return s.nodeId == 0 || s.nodeId == exclude;
});
return tree;
}
template <class T>
class Layout {
public:
size_t stability = 0;
std::list<std::shared_ptr<T> > subs;
/** Return the nodeId of the node that we are running on.
*
* On the ESP hardware nodeId is uniquely calculated from the MAC address of
* the node.
*/
uint32_t getNodeId() { return nodeId; }
protocol::NodeTree asNodeTree() {
auto nt = protocol::NodeTree(nodeId, root);
for (auto&& s : subs) {
if (s->nodeId == 0) continue;
nt.subs.push_back(protocol::NodeTree(*s));
}
return nt;
}
protected:
uint32_t nodeId = 0;
bool root = false;
};
template <class T>
void syncLayout(Layout<T>& layout, uint32_t changedId) {
// TODO: this should be called from changed connections and dropped
// connections events
for (auto&& sub : layout.subs) {
if (sub->connected && !sub->newConnection && sub->nodeId != 0 &&
sub->nodeId != changedId) { // Exclude current
sub->nodeSyncTask.forceNextIteration();
}
}
layout.stability /= 2;
}
class Neighbour : public protocol::NodeTree {
public:
// Inherit constructors
using protocol::NodeTree::NodeTree;
/**
* Is the passed nodesync valid
*
* If not then the caller of this function should probably disconnect
* this neighbour.
*/
bool validSubs(protocol::NodeTree tree) {
if (nodeId == 0) // Cant really know, so valid as far as we know
return true;
if (nodeId != tree.nodeId) return false;
for (auto&& s : tree.subs) {
if (layout::contains(s, nodeId)) return false;
}
return true;
}
/**
* Update subs with the new subs
*
* \param tree The possible new tree with this node as base
*
* Generally one probably wants to call validSubs before calling this
* function.
*
* \return Whether we adopted the new tree
*/
bool updateSubs(protocol::NodeTree tree) {
if (nodeId == 0 || tree != (*this)) {
nodeId = tree.nodeId;
subs = tree.subs;
root = tree.root;
return true;
}
return false;
}
/**
* Create a request
*/
protocol::NodeSyncRequest request(NodeTree&& layout) {
auto subTree = excludeRoute(std::move(layout), nodeId);
return protocol::NodeSyncRequest(subTree.nodeId, nodeId, subTree.subs,
subTree.root);
}
/**
* Create a reply
*/
protocol::NodeSyncReply reply(NodeTree&& layout) {
auto subTree = excludeRoute(std::move(layout), nodeId);
return protocol::NodeSyncReply(subTree.nodeId, nodeId, subTree.subs,
subTree.root);
}
};
/**
* The size of the mesh (the number of nodes)
*/
inline uint32_t size(protocol::NodeTree nodeTree) {
auto no = 1;
for (auto&& s : nodeTree.subs) {
no += size(s);
}
return no;
}
/**
* Whether the top node in the tree is also the root of the mesh
*/
inline bool isRoot(protocol::NodeTree nodeTree) {
if (nodeTree.root) return true;
return false;
}
/**
* Whether any node in the tree is also root of the mesh
*/
inline bool isRooted(protocol::NodeTree nodeTree) {
if (isRoot(nodeTree)) return true;
for (auto&& s : nodeTree.subs) {
if (isRooted(s)) return true;
}
return false;
}
/**
* Return all nodes in a list container
*/
inline std::list<uint32_t> asList(protocol::NodeTree nodeTree,
bool includeSelf = true) {
std::list<uint32_t> lst;
if (includeSelf) lst.push_back(nodeTree.nodeId);
for (auto&& s : nodeTree.subs) {
lst.splice(lst.end(), asList(s));
}
return lst;
}
} // namespace layout
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,131 @@
#ifndef _PAINLESS_MESH_LOGGER_HPP_
#define _PAINLESS_MESH_LOGGER_HPP_
namespace painlessmesh {
namespace logger {
#include <stdarg.h>
#include "Arduino.h"
typedef enum {
ERROR = 1 << 0,
STARTUP = 1 << 1,
MESH_STATUS = 1 << 2,
CONNECTION = 1 << 3,
SYNC = 1 << 4,
S_TIME = 1 << 5,
COMMUNICATION = 1 << 6,
GENERAL = 1 << 7,
MSG_TYPES = 1 << 8,
REMOTE = 1 << 9, // not yet implemented
APPLICATION = 1 << 10,
DEBUG = 1 << 11
} LogLevel;
class LogClass {
public:
void setLogLevel(uint16_t newTypes) {
// set the different kinds of debug messages you want to generate.
types = newTypes;
Serial.print(F("\nsetLogLevel:"));
if (types & ERROR) {
Serial.print(F(" ERROR |"));
}
if (types & STARTUP) {
Serial.print(F(" STARTUP |"));
}
if (types & MESH_STATUS) {
Serial.print(F(" MESH_STATUS |"));
}
if (types & CONNECTION) {
Serial.print(F(" CONNECTION |"));
}
if (types & SYNC) {
Serial.print(F(" SYNC |"));
}
if (types & S_TIME) {
Serial.print(F(" S_TIME |"));
}
if (types & COMMUNICATION) {
Serial.print(F(" COMMUNICATION |"));
}
if (types & GENERAL) {
Serial.print(F(" GENERAL |"));
}
if (types & MSG_TYPES) {
Serial.print(F(" MSG_TYPES |"));
}
if (types & REMOTE) {
Serial.print(F(" REMOTE |"));
}
if (types & APPLICATION) {
Serial.print(F(" APPLICATION |"));
}
if (types & DEBUG) {
Serial.print(F(" DEBUG |"));
}
Serial.println();
return;
}
void operator()(LogLevel type, const char* format...) {
if (type & types) { // Print only the message types set for output
va_list args;
va_start(args, format);
vsnprintf(str, 200, format, args);
if (types) {
switch (type) {
case ERROR:
Serial.print(F("ERROR: "));
break;
case STARTUP:
Serial.print(F("STARTUP: "));
break;
case MESH_STATUS:
Serial.print(F("MESH_STATUS: "));
break;
case CONNECTION:
Serial.print(F("CONNECTION: "));
break;
case SYNC:
Serial.print(F("SYNC: "));
break;
case S_TIME:
Serial.print(F("S_TIME: "));
break;
case COMMUNICATION:
Serial.print(F("COMMUNICATION: "));
break;
case GENERAL:
Serial.print(F("GENERAL: "));
break;
case MSG_TYPES:
Serial.print(F("MSG_TYPES: "));
break;
case REMOTE:
Serial.print(F("REMOTE: "));
break;
case APPLICATION:
Serial.print(F("APPLICATION: "));
break;
case DEBUG:
Serial.print(F("DEBUG: "));
break;
}
}
Serial.print(str);
va_end(args);
}
}
private:
uint16_t types = 0;
char str[200];
};
} // namespace logger
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,440 @@
#ifndef _PAINLESS_MESH_MESH_HPP_
#define _PAINLESS_MESH_MESH_HPP_
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/ntp.hpp"
#include "painlessmesh/plugin.hpp"
#include "painlessmesh/tcp.hpp"
#ifdef PAINLESSMESH_ENABLE_OTA
#include "painlessmesh/ota.hpp"
#endif
namespace painlessmesh {
typedef std::function<void(uint32_t nodeId)> newConnectionCallback_t;
typedef std::function<void(uint32_t nodeId)> droppedConnectionCallback_t;
typedef std::function<void(uint32_t from, TSTRING &msg)> receivedCallback_t;
typedef std::function<void()> changedConnectionsCallback_t;
typedef std::function<void(int32_t offset)> nodeTimeAdjustedCallback_t;
typedef std::function<void(uint32_t nodeId, int32_t delay)> nodeDelayCallback_t;
/**
* Main api class for the mesh
*
* Brings all the functions together except for the WiFi functions
*/
template <class T>
class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
public:
void init(uint32_t id) {
using namespace logger;
if (!isExternalScheduler) {
mScheduler = new Scheduler();
}
this->nodeId = id;
#ifdef ESP32
xSemaphore = xSemaphoreCreateMutex();
#endif
mScheduler->enableAll();
// Add package handlers
this->callbackList = painlessmesh::ntp::addPackageCallback(
std::move(this->callbackList), (*this));
this->callbackList = painlessmesh::router::addPackageCallback(
std::move(this->callbackList), (*this));
this->changedConnectionCallbacks.push_back([this](uint32_t nodeId) {
Log(MESH_STATUS, "Changed connections in neighbour %u\n", nodeId);
if (nodeId != 0) layout::syncLayout<T>((*this), nodeId);
});
this->droppedConnectionCallbacks.push_back([this](uint32_t nodeId,
bool station) {
Log(MESH_STATUS, "Dropped connection %u, station %d\n", nodeId, station);
this->eraseClosedConnections();
});
this->newConnectionCallbacks.push_back([this](uint32_t nodeId) {
Log(MESH_STATUS, "New connection %u\n", nodeId);
});
}
void init(Scheduler *scheduler, uint32_t id) {
this->setScheduler(scheduler);
this->init(id);
}
#ifdef PAINLESSMESH_ENABLE_OTA
void initOTA(TSTRING role = "") {
painlessmesh::plugin::ota::addPackageCallback(*this->mScheduler, (*this),
role);
}
#endif
/**
* Set the node as an root/master node for the mesh
*
* This is an optional setting that can speed up mesh formation.
* At most one node in the mesh should be a root, or you could
* end up with multiple subMeshes.
*
* We recommend any AP_ONLY nodes (e.g. a bridgeNode) to be set
* as a root node.
*
* If one node is root, then it is also recommended to call
* painlessMesh::setContainsRoot() on all the nodes in the mesh.
*/
void setRoot(bool on = true) { this->root = on; };
/**
* The mesh should contains a root node
*
* This will cause the mesh to restructure more quickly around the root node.
* Note that this could have adverse effects if set, while there is no root
* node present. Also see painlessMesh::setRoot().
*/
void setContainsRoot(bool on = true) { shouldContainRoot = on; };
/**
* Check whether this node is a root node.
*/
bool isRoot() { return this->root; };
void setDebugMsgTypes(uint16_t types) { Log.setLogLevel(types); }
/**
* Disconnect and stop this node
*/
void stop() {
using namespace logger;
// Close all connections
while (this->subs.size() > 0) {
auto conn = this->subs.begin();
(*conn)->close();
this->eraseClosedConnections();
}
plugin::PackageHandler<T>::stop();
}
/** Perform crucial maintenance task
*
* Add this to your loop() function. This routine runs various maintenance
* tasks.
*/
void update(void) {
if (semaphoreTake()) {
mScheduler->execute();
semaphoreGive();
}
return;
}
/** Send message to a specific node
*
* @param destId The nodeId of the node to send it to.
* @param msg The message to send
*
* @return true if everything works, false if not.
*/
bool sendSingle(uint32_t destId, TSTRING msg) {
Log(logger::COMMUNICATION, "sendSingle(): dest=%u msg=%s\n", destId,
msg.c_str());
auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
return painlessmesh::router::send<T>(single, (*this));
}
/** Broadcast a message to every node on the mesh network.
*
* @param includeSelf Send message to myself as well. Default is false.
*
* @return true if everything works, false if not
*/
bool sendBroadcast(TSTRING msg, bool includeSelf = false) {
using namespace logger;
Log(COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str());
auto pkg = painlessmesh::protocol::Broadcast(this->nodeId, 0, msg);
auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
if (success && includeSelf) {
auto variant = protocol::Variant(pkg);
this->callbackList.execute(pkg.type, pkg, NULL, 0);
}
if (success > 0) return true;
return false;
}
/** Sends a node a packet to measure network trip delay to that node.
*
* After calling this function, user program have to wait to the response in
* the form of a callback specified by onNodeDelayReceived().
*
* @return true if nodeId is connected to the mesh, false otherwise
*/
bool startDelayMeas(uint32_t id) {
using namespace logger;
Log(S_TIME, "startDelayMeas(): NodeId %u\n", id);
auto conn = painlessmesh::router::findRoute<T>((*this), id);
if (!conn) return false;
return router::send<protocol::TimeDelay, T>(
protocol::TimeDelay(this->nodeId, id, this->getNodeTime()), conn);
}
/** Set a callback routine for any messages that are addressed to this node.
*
* Every time this node receives a message, this callback routine will the
* called. “from” is the id of the original sender of the message, and “msg”
* is a string that contains the message. The message can be anything. A
* JSON, some other text string, or binary data.
*
* \code
* mesh.onReceive([](auto nodeId, auto msg) {
* // Do something with the message
* Serial.println(msg);
* });
* \endcode
*/
void onReceive(receivedCallback_t onReceive) {
using namespace painlessmesh;
this->callbackList.onPackage(
protocol::SINGLE,
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
auto pkg = variant.to<protocol::Single>();
onReceive(pkg.from, pkg.msg);
return false;
});
this->callbackList.onPackage(
protocol::BROADCAST,
[onReceive](protocol::Variant variant, std::shared_ptr<T>, uint32_t) {
auto pkg = variant.to<protocol::Broadcast>();
onReceive(pkg.from, pkg.msg);
return false;
});
}
/** Callback that gets called every time the local node makes a new
* connection.
*
* \code
* mesh.onNewConnection([](auto nodeId) {
* // Do something with the event
* Serial.println(String(nodeId));
* });
* \endcode
*/
void onNewConnection(newConnectionCallback_t onNewConnection) {
Log(logger::GENERAL, "onNewConnection():\n");
newConnectionCallbacks.push_back([onNewConnection](uint32_t nodeId) {
if (nodeId != 0) onNewConnection(nodeId);
});
}
/** Callback that gets called every time the local node drops a connection.
*
* \code
* mesh.onDroppedConnection([](auto nodeId) {
* // Do something with the event
* Serial.println(String(nodeId));
* });
* \endcode
*/
void onDroppedConnection(droppedConnectionCallback_t onDroppedConnection) {
droppedConnectionCallbacks.push_back(
[onDroppedConnection](uint32_t nodeId, bool station) {
if (nodeId != 0) onDroppedConnection(nodeId);
});
}
/** Callback that gets called every time the layout of the mesh changes
*
* \code
* mesh.onChangedConnections([]() {
* // Do something with the event
* });
* \endcode
*/
void onChangedConnections(changedConnectionsCallback_t onChangedConnections) {
Log(logger::GENERAL, "onChangedConnections():\n");
changedConnectionCallbacks.push_back(
[onChangedConnections](uint32_t nodeId) {
if (nodeId != 0) onChangedConnections();
});
}
/** Callback that gets called every time node time gets adjusted
*
* Node time is automatically kept in sync in the mesh. This gets called
* whenever the time is to far out of sync with the rest of the mesh and gets
* adjusted.
*
* \code
* mesh.onNodeTimeAdjusted([](auto offset) {
* // Do something with the event
* Serial.println(String(offset));
* });
* \endcode
*/
void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t onTimeAdjusted) {
Log(logger::GENERAL, "onNodeTimeAdjusted():\n");
nodeTimeAdjustedCallback = onTimeAdjusted;
}
/** Callback that gets called when a delay measurement is received.
*
* This fires when a time delay masurement response is received, after a
* request was sent.
*
* \code
* mesh.onNodeDelayReceived([](auto nodeId, auto delay) {
* // Do something with the event
* Serial.println(String(delay));
* });
* \endcode
*/
void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived) {
Log(logger::GENERAL, "onNodeDelayReceived():\n");
nodeDelayReceivedCallback = onDelayReceived;
}
/**
* Are we connected/know a route to the given node?
*
* @param nodeId The nodeId we are looking for
*/
bool isConnected(uint32_t nodeId) {
return painlessmesh::router::findRoute<T>((*this), nodeId) != NULL;
}
/** Get a list of all known nodes.
*
* This includes nodes that are both directly and indirectly connected to the
* current node.
*/
std::list<uint32_t> getNodeList(bool includeSelf = false) {
return painlessmesh::layout::asList(this->asNodeTree(), includeSelf);
}
/**
* Return a json representation of the current mesh layout
*/
inline TSTRING subConnectionJson(bool pretty = false) {
return this->asNodeTree().toString(pretty);
}
inline std::shared_ptr<Task> addTask(unsigned long aInterval,
long aIterations,
std::function<void()> aCallback) {
return plugin::PackageHandler<T>::addTask((*this->mScheduler), aInterval,
aIterations, aCallback);
}
inline std::shared_ptr<Task> addTask(std::function<void()> aCallback) {
return plugin::PackageHandler<T>::addTask((*this->mScheduler), aCallback);
}
~Mesh() {
this->stop();
if (!isExternalScheduler) delete mScheduler;
}
protected:
void setScheduler(Scheduler *baseScheduler) {
this->mScheduler = baseScheduler;
isExternalScheduler = true;
}
void startTimeSync(std::shared_ptr<T> conn) {
using namespace logger;
Log(S_TIME, "startTimeSync(): from %u with %u\n", this->nodeId,
conn->nodeId);
painlessmesh::protocol::TimeSync timeSync;
if (ntp::adopt(this->asNodeTree(), (*conn))) {
timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId,
this->getNodeTime());
Log(S_TIME, "startTimeSync(): Requesting time from %u\n", conn->nodeId);
} else {
timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId);
Log(S_TIME, "startTimeSync(): Requesting %u to adopt our time\n",
conn->nodeId);
}
router::send<protocol::TimeSync, T>(timeSync, conn, true);
}
bool closeConnectionSTA() {
auto connection = this->subs.begin();
while (connection != this->subs.end()) {
if ((*connection)->station) {
// We found the STA connection, close it
(*connection)->close();
return true;
}
++connection;
}
return false;
}
void eraseClosedConnections() {
using namespace logger;
Log(CONNECTION, "eraseClosedConnections():\n");
this->subs.remove_if(
[](const std::shared_ptr<T> &conn) { return !conn->connected; });
}
// Callback functions
callback::List<uint32_t> newConnectionCallbacks;
callback::List<uint32_t, bool> droppedConnectionCallbacks;
callback::List<uint32_t> changedConnectionCallbacks;
nodeTimeAdjustedCallback_t nodeTimeAdjustedCallback;
nodeDelayCallback_t nodeDelayReceivedCallback;
#ifdef ESP32
SemaphoreHandle_t xSemaphore = NULL;
#endif
bool isExternalScheduler = false;
/// Is the node a root node
bool shouldContainRoot;
Scheduler *mScheduler;
/**
* Wrapper function for ESP32 semaphore function
*
* Waits for the semaphore to be available and then returns true
*
* Always return true on ESP8266
*/
bool semaphoreTake() {
#ifdef ESP32
return xSemaphoreTake(xSemaphore, (TickType_t)10) == pdTRUE;
#else
return true;
#endif
}
/**
* Wrapper function for ESP32 semaphore give function
*
* Does nothing on ESP8266 hardware
*/
void semaphoreGive() {
#ifdef ESP32
xSemaphoreGive(xSemaphore);
#endif
}
friend T;
friend void onDataCb(void *, AsyncClient *, void *, size_t);
friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t);
friend void meshRecvCb(void *, AsyncClient *, void *, size_t);
friend void painlessmesh::ntp::handleTimeSync<Mesh, T>(
Mesh &, painlessmesh::protocol::TimeSync, std::shared_ptr<T>, uint32_t);
friend void painlessmesh::ntp::handleTimeDelay<Mesh, T>(
Mesh &, painlessmesh::protocol::TimeDelay, std::shared_ptr<T>, uint32_t);
friend void painlessmesh::router::handleNodeSync<Mesh, T>(
Mesh &, protocol::NodeTree, std::shared_ptr<T> conn);
friend void painlessmesh::tcp::initServer<T, Mesh>(AsyncServer &, Mesh &);
friend void painlessmesh::tcp::connect<T, Mesh>(AsyncClient &, IPAddress,
uint16_t, Mesh &);
}; // namespace painlessmesh
}; // namespace painlessmesh
#endif

View File

@@ -0,0 +1,245 @@
#ifndef _PAINLESS_MESH_NTP_HPP_
#define _PAINLESS_MESH_NTP_HPP_
#ifndef TIME_SYNC_INTERVAL
#define TIME_SYNC_INTERVAL 1 * TASK_MINUTE // Time resync period
#endif
#ifndef TIME_SYNC_ACCURACY
#define TIME_SYNC_ACCURACY 5000 // Minimum time sync accuracy (5ms
#endif
#include "Arduino.h"
#include "painlessmesh/callback.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/router.hpp"
extern painlessmesh::logger::LogClass Log;
namespace painlessmesh {
namespace ntp {
class MeshTime {
public:
/** Returns the mesh time in microsecond precision.
*
* Time rolls over every 71 minutes.
*
* Nodes try to keep a common time base synchronizing to each other using [an
* SNTP based
* protocol](https://gitlab.com/painlessMesh/painlessMesh/wikis/mesh-protocol#time-sync)
*/
uint32_t getNodeTime() { return micros() + timeOffset; }
protected:
uint32_t timeOffset = 0;
};
/**
* Calculate the offset of the local clock using the ntp algorithm
*
* See ntp overview for more information
*/
inline int32_t clockOffset(uint32_t time0, uint32_t time1, uint32_t time2,
uint32_t time3) {
uint32_t offset =
((int32_t)(time1 - time0) / 2) + ((int32_t)(time2 - time3) / 2);
// Take small steps to avoid over correction
if (offset < 0.5 * TASK_SECOND && offset > 4) offset = offset / 4;
return offset;
}
/**
* Calculate the time it took to get reply from other node
*
* See ntp algorithm for more information
*/
inline int32_t tripDelay(uint32_t time0, uint32_t time1, uint32_t time2,
uint32_t time3) {
return ((time3 - time0) - (time2 - time1)) / 2;
}
inline bool adopt(protocol::NodeTree mesh, protocol::NodeTree connection) {
auto mySubCount =
layout::size(layout::excludeRoute(std::move(mesh), connection.nodeId));
auto remoteSubCount = layout::size(connection);
if (mySubCount > remoteSubCount) return false;
if (mySubCount == remoteSubCount) {
if (connection.nodeId == 0)
Log(logger::ERROR, "Adopt called on uninitialized connection\n");
return mesh.nodeId < connection.nodeId;
}
return true;
}
template <class T>
void initTimeSync(protocol::NodeTree mesh, std::shared_ptr<T> connection,
uint32_t nodeTime) {
using namespace painlessmesh::logger;
painlessmesh::protocol::TimeSync timeSync;
if (adopt(mesh, (*connection))) {
timeSync = painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId,
nodeTime);
Log(S_TIME, "initTimeSync(): Requesting time from %u\n",
connection->nodeId);
} else {
timeSync =
painlessmesh::protocol::TimeSync(mesh.nodeId, connection->nodeId);
Log(S_TIME, "initTimeSync(): Requesting %u to adopt our time\n",
connection->nodeId);
}
router::send<protocol::TimeSync, T>(timeSync, connection, true);
}
template <class T, class U>
void handleTimeSync(T& mesh, painlessmesh::protocol::TimeSync timeSync,
std::shared_ptr<U> conn, uint32_t receivedAt) {
switch (timeSync.msg.type) {
case (painlessmesh::protocol::TIME_SYNC_ERROR):
Log(logger::ERROR,
"handleTimeSync(): Received time sync error. Restarting time "
"sync.\n");
conn->timeSyncTask.forceNextIteration();
break;
case (painlessmesh::protocol::TIME_SYNC_REQUEST): // Other party request me
// to ask it for time
Log(logger::S_TIME,
"handleTimeSync(): Received requesto to start TimeSync with "
"node: %u\n",
conn->nodeId);
timeSync.reply(mesh.getNodeTime());
router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
break;
case (painlessmesh::protocol::TIME_REQUEST):
timeSync.reply(receivedAt, mesh.getNodeTime());
router::send<painlessmesh::protocol::TimeSync>(timeSync, conn, true);
Log(logger::S_TIME,
"handleTimeSync(): timeSyncStatus with %u completed\n", conn->nodeId);
// After response is sent I assume sync is completed
conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
break;
case (painlessmesh::protocol::TIME_REPLY): {
Log(logger::S_TIME,
"handleTimeSync(): %u adopting TIME_RESPONSE from %u\n", mesh.nodeId,
conn->nodeId);
int32_t offset = painlessmesh::ntp::clockOffset(
timeSync.msg.t0, timeSync.msg.t1, timeSync.msg.t2, receivedAt);
mesh.timeOffset += offset; // Accumulate offset
// flag all connections for re-timeSync
if (mesh.nodeTimeAdjustedCallback) {
mesh.nodeTimeAdjustedCallback(offset);
}
if (offset < TIME_SYNC_ACCURACY && offset > -TIME_SYNC_ACCURACY) {
// mark complete only if offset was less than 10 ms
conn->timeSyncTask.delay(TIME_SYNC_INTERVAL);
Log(logger::S_TIME,
"handleTimeSync(): timeSyncStatus with %u completed\n",
conn->nodeId);
// Time has changed, update other nodes
for (auto&& connection : mesh.subs) {
if (connection->nodeId != conn->nodeId) { // exclude this connection
connection->timeSyncTask.forceNextIteration();
Log(logger::S_TIME,
"handleTimeSync(): timeSyncStatus with %u brought forward\n",
connection->nodeId);
}
}
} else {
// Iterate sync procedure if accuracy was not enough
conn->timeSyncTask.delay(200 * TASK_MILLISECOND); // Small delay
Log(logger::S_TIME,
"handleTimeSync(): timeSyncStatus with %u needs further tries\n",
conn->nodeId);
}
break;
}
default:
Log(logger::ERROR, "handleTimeSync(): unkown type %u, %u\n",
timeSync.msg.type, painlessmesh::protocol::TIME_SYNC_REQUEST);
break;
}
Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
}
template <class T, class U>
void handleTimeDelay(T& mesh, painlessmesh::protocol::TimeDelay timeDelay,
std::shared_ptr<U> conn, uint32_t receivedAt) {
Log(logger::S_TIME, "handleTimeDelay(): from %u in timestamp\n",
timeDelay.from);
switch (timeDelay.msg.type) {
case (painlessmesh::protocol::TIME_SYNC_ERROR):
Log(logger::ERROR,
"handleTimeDelay(): Error in requesting time delay. Please try "
"again.\n");
break;
case (painlessmesh::protocol::TIME_REQUEST):
// conn->timeSyncStatus == IN_PROGRESS;
Log(logger::S_TIME, "handleTimeDelay(): TIME REQUEST received.\n");
// Build time response
timeDelay.reply(receivedAt, mesh.getNodeTime());
router::send<protocol::TimeDelay, U>(timeDelay, conn);
break;
case (painlessmesh::protocol::TIME_REPLY): {
Log(logger::S_TIME, "handleTimeDelay(): TIME RESPONSE received.\n");
int32_t delay = painlessmesh::ntp::tripDelay(
timeDelay.msg.t0, timeDelay.msg.t1, timeDelay.msg.t2, receivedAt);
Log(logger::S_TIME, "handleTimeDelay(): Delay is %d\n", delay);
// conn->timeSyncStatus == COMPLETE;
if (mesh.nodeDelayReceivedCallback)
mesh.nodeDelayReceivedCallback(timeDelay.from, delay);
} break;
default:
Log(logger::ERROR,
"handleTimeDelay(): Unknown timeSyncMessageType received. Ignoring "
"for now.\n");
}
Log(logger::S_TIME, "handleTimeSync(): ----------------------------------\n");
}
template <class T, typename U>
callback::MeshPackageCallbackList<U> addPackageCallback(
callback::MeshPackageCallbackList<U>&& callbackList, T& mesh) {
// TimeSync
callbackList.onPackage(
protocol::TIME_SYNC,
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
uint32_t receivedAt) {
auto timeSync = variant.to<protocol::TimeSync>();
handleTimeSync<T, U>(mesh, timeSync, connection, receivedAt);
return false;
});
// TimeDelay
callbackList.onPackage(
protocol::TIME_DELAY,
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
uint32_t receivedAt) {
auto timeDelay = variant.to<protocol::TimeDelay>();
handleTimeDelay<T, U>(mesh, timeDelay, connection, receivedAt);
return false;
});
return callbackList;
}
} // namespace ntp
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,409 @@
#ifndef _PAINLESS_MESH_PLUGIN_OTA_HPP_
#define _PAINLESS_MESH_PLUGIN_OTA_HPP_
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/base64.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/plugin.hpp"
#if defined(ESP32) || defined(ESP8266)
#ifdef ESP32
#include <SPIFFS.h>
#include <Update.h>
#else
#include <FS.h>
#endif
#endif
namespace painlessmesh {
namespace plugin {
/** OTA over the mesh
*
* OTA is implemented as a painlessmesh::plugin.
*
* The protocol consists of three message types: ota::Announce, ota::DataRequest
* and ota::Data. The first message is generally send by the node that
* distributes the firmware and announces the current version of firmware
* available for each hardware and role. Firmware version is determined by
* its MD5 signature. See
* [painlessMeshBoost](http://gitlab.com/painlessMesh/painlessMeshBoost) for a
* possible implementation of a distribution node.
*
* Once a node receives a announce message it will check it against its own role
* and hardware to discover if it is suitable this node. If that checks out and
* the MD5 is different than its own MD5 it will send a data request back to the
* firmware distribution node. This request also includes a partNo, to determine
* which part of the data it needs (starting from zero).
*
* When the distribution node receives a data request, it sends the data back to
* the node (with a data message). The node will then write this data and
* request the next part of the data. This exchange continuous until the node
* has all the data, written it and reboots into the new firmware.
*/
namespace ota {
/** Package used by the firmware distribution node to announce new version
* available
*
* This is based on the general BroadcastPackage to ensure it is being
* broadcasted. It is possible to define a Announce::role, which defines the
* node role the firmware is meant for.
*
* The package type/identifier is set to 10.
*/
class Announce : public BroadcastPackage {
public:
TSTRING md5;
TSTRING hardware;
/**
* \brief The type of node the firmware is meant for
*
* Nodes can fulfill different roles, which require specific firmware. E.g a
* node can be a sensor and therefore needs the firmware meant for sensor
* nodes. This allows one to set the type of node (role) this firmware is
* aimed at.
*
* Note that the role should not contain underscores or dots.
*/
TSTRING role;
/** Force an update even if the node already has this firmware version
*
* Mainly usefull when testing updates etc.
*/
bool forced = false;
size_t noPart;
Announce() : BroadcastPackage(10) {}
Announce(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
md5 = jsonObj["md5"].as<TSTRING>();
hardware = jsonObj["hardware"].as<TSTRING>();
role = jsonObj["role"].as<TSTRING>();
if (jsonObj.containsKey("forced")) forced = jsonObj["forced"];
noPart = jsonObj["noPart"];
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
jsonObj["md5"] = md5;
jsonObj["hardware"] = hardware;
jsonObj["role"] = role;
if (forced) jsonObj["forced"] = forced;
jsonObj["noPart"] = noPart;
return jsonObj;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(noJsonFields + 5) +
round(1.1 * (md5.length() + hardware.length() + role.length()));
}
protected:
Announce(int type, router::Type routing) : BroadcastPackage(type) {
this->routing = routing;
}
};
class Data;
/** Request (part of) the firmware update
*
* This is send by the node needing the new firmware, to the firmware
* distribution node to request a part (DataRequest::partNo) of the data.
*
* The package type/identifier is set to 11.
*/
class DataRequest : public Announce {
public:
size_t partNo = 0;
uint32_t dest = 0;
DataRequest() : Announce(11, router::SINGLE) {}
DataRequest(JsonObject jsonObj) : Announce(jsonObj) {
dest = jsonObj["dest"];
partNo = jsonObj["partNo"];
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = Announce::addTo(std::move(jsonObj));
jsonObj["dest"] = dest;
jsonObj["partNo"] = partNo;
return jsonObj;
}
static DataRequest replyTo(const Announce& ann, uint32_t from,
size_t partNo) {
DataRequest req;
req.dest = ann.from;
req.md5 = ann.md5;
req.hardware = ann.hardware;
req.role = ann.role;
req.forced = ann.forced;
req.noPart = ann.noPart;
req.partNo = partNo;
req.from = from;
return req;
}
static DataRequest replyTo(const Data& d, size_t partNo);
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(noJsonFields + 5 + 2) +
round(1.1 * (md5.length() + hardware.length() + role.length()));
}
protected:
DataRequest(int type) : Announce(type, router::SINGLE) {}
};
/** Package containing part of the firmware
*
* The package type/identifier is set to 12.
*/
class Data : public DataRequest {
public:
TSTRING data;
Data() : DataRequest(12) {}
Data(JsonObject jsonObj) : DataRequest(jsonObj) {
data = jsonObj["data"].as<TSTRING>();
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = DataRequest::addTo(std::move(jsonObj));
jsonObj["data"] = data;
return jsonObj;
}
static Data replyTo(const DataRequest& req, TSTRING data, size_t partNo) {
Data d;
d.from = req.dest;
d.dest = req.from;
d.md5 = req.md5;
d.hardware = req.hardware;
d.role = req.role;
d.forced = req.forced;
d.noPart = req.noPart;
d.partNo = partNo;
d.data = data;
return d;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(noJsonFields + 5 + 2 + 1) +
round(1.1 * (md5.length() + hardware.length() + role.length() +
data.length()));
}
};
inline DataRequest DataRequest::replyTo(const Data& d, size_t partNo) {
DataRequest req;
req.from = d.dest;
req.dest = d.from;
req.md5 = d.md5;
req.hardware = d.hardware;
req.role = d.role;
req.forced = d.forced;
req.noPart = d.noPart;
req.partNo = partNo;
return req;
}
/** Data related to the current state of the node update
*
* This class is used by the OTA algorithm to keep track of both the current
* version of the software and the ongoing update.
*
* The firmware md5 uniquely identifies each firmware version
*/
class State : public protocol::PackageInterface {
public:
TSTRING md5;
#ifdef ESP32
TSTRING hardware = "ESP32";
#else
TSTRING hardware = "ESP8266";
#endif
TSTRING role;
size_t noPart = 0;
size_t partNo = 0;
TSTRING ota_fn = "/ota_fw.json";
State() {}
State(JsonObject jsonObj) {
md5 = jsonObj["md5"].as<TSTRING>();
hardware = jsonObj["hardware"].as<TSTRING>();
role = jsonObj["role"].as<TSTRING>();
}
State(const Announce& ann) {
md5 = ann.md5;
hardware = ann.hardware;
role = ann.role;
noPart = ann.noPart;
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["role"] = role;
jsonObj["md5"] = md5;
jsonObj["hardware"] = hardware;
return jsonObj;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(3) +
round(1.1 * (md5.length() + hardware.length() + role.length()));
}
std::shared_ptr<Task> task;
};
template <class T>
void addPackageCallback(Scheduler& scheduler, plugin::PackageHandler<T>& mesh,
TSTRING role = "") {
using namespace logger;
#if defined(ESP32) || defined(ESP8266)
auto currentFW = std::make_shared<State>();
currentFW->role = role;
auto updateFW = std::make_shared<State>();
updateFW->role = role;
#ifdef ESP32
SPIFFS.begin(true); // Start the SPI Flash Files System
#else
SPIFFS.begin(); // Start the SPI Flash Files System
#endif
if (SPIFFS.exists(currentFW->ota_fn)) {
auto file = SPIFFS.open(currentFW->ota_fn, "r");
TSTRING msg = "";
while (file.available()) {
msg += (char)file.read();
}
auto var = protocol::Variant(msg);
auto fw = var.to<State>();
if (fw.role == role && fw.hardware == currentFW->hardware) {
Log(DEBUG, "MD5 found %s\n", fw.md5.c_str());
currentFW->md5 = fw.md5;
}
}
mesh.onPackage(10, [currentFW, updateFW, &mesh,
&scheduler](protocol::Variant variant) {
// convert variant to Announce
auto pkg = variant.to<Announce>();
// Check if we want the update
if (currentFW->role == pkg.role && currentFW->hardware == pkg.hardware) {
if ((currentFW->md5 == pkg.md5 && !pkg.forced) ||
updateFW->md5 == pkg.md5)
// Either already have it, or already updating to it
return false;
else {
auto request = DataRequest::replyTo(pkg, mesh.getNodeId(), updateFW->partNo);
updateFW->md5 = pkg.md5;
// enable the request task
updateFW->task =
mesh.addTask(scheduler, 30 * TASK_SECOND, 10,
[request, &mesh]() { mesh.sendPackage(&request); });
updateFW->task->setOnDisable([updateFW]() {
Log(ERROR, "OTA: Did not receive the requested data.\n");
updateFW->md5 = "";
});
}
}
return false;
});
mesh.onPackage(11, [currentFW](protocol::Variant variant) {
Log(ERROR, "Data request should not be send to this node\n");
return false;
});
mesh.onPackage(12, [currentFW, updateFW, &mesh,
&scheduler](protocol::Variant variant) {
auto pkg = variant.to<Data>();
// Check whether it is a new part, of correct md5 role etc etc
if (updateFW->partNo == pkg.partNo && updateFW->md5 == pkg.md5 &&
updateFW->role == pkg.role && updateFW->hardware == pkg.hardware) {
// If so write
if (pkg.partNo == 0) {
#ifdef ESP32
uint32_t maxSketchSpace = UPDATE_SIZE_UNKNOWN;
#else
uint32_t maxSketchSpace =
(ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
#endif
Log(DEBUG, "Sketch size %d\n", maxSketchSpace);
if (Update.isRunning()) {
Update.end(false);
}
if (!Update.begin(maxSketchSpace)) { // start with max available size
Log(DEBUG, "handleOTA(): OTA start failed!");
Update.printError(Serial);
Update.end();
} else {
Update.setMD5(pkg.md5.c_str());
}
}
// write data
auto b64Data = base64::decode(pkg.data);
if (Update.write((uint8_t*)b64Data.c_str(), b64Data.length()) !=
b64Data.length()) {
Log(ERROR, "handleOTA(): OTA write failed!");
Update.printError(Serial);
Update.end();
updateFW->md5 = "";
updateFW->partNo = 0;
return false;
}
// If last part then write ota_fn and reboot
if (pkg.partNo == pkg.noPart - 1) {
// check md5, reboot
if (Update.end(true)) { // true to set the size to the
// current progress
auto file = SPIFFS.open(updateFW->ota_fn, "w");
String msg;
auto var = protocol::Variant(updateFW.get());
var.printTo(msg);
file.print(msg);
file.close();
Log(DEBUG, "handleOTA(): OTA Success! %s, %s\n", msg.c_str(),
updateFW->role.c_str());
ESP.restart();
} else {
Log(DEBUG, "handleOTA(): OTA failed!\n");
Update.printError(Serial);
updateFW->md5 = "";
updateFW->partNo = 0;
}
updateFW->task->setOnDisable(NULL);
updateFW->task->disable();
} else {
// else request more
++updateFW->partNo;
auto request = DataRequest::replyTo(pkg, updateFW->partNo);
updateFW->task->setCallback(
[request, &mesh]() { mesh.sendPackage(&request); });
updateFW->task->disable();
updateFW->task->restart();
}
}
return false;
});
#endif
}
} // namespace ota
} // namespace plugin
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,187 @@
#ifndef _PAINLESS_MESH_PLUGIN_HPP_
#define _PAINLESS_MESH_PLUGIN_HPP_
#include "Arduino.h"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/router.hpp"
namespace painlessmesh {
/** Plugin interface for painlessMesh packages/messages
*
* This interface allows one to design their own messages types/packages, and
* add handlers that are called when the new package type arrives at a node.
* Here you can think of things like sensor packages, which hold the
* measurements done by the sensors. The packages related to OTA updates are
* also implemented as a plugin system (see plugin::ota). Each package type is
* uniquely identified using the protocol::PackageInterface::type. Currently
* default package types use numbers up to 12, so to be on the safe side we
* recommend your own packages to use higher type values, e.g. start counting at
* 20 at the lowest.
*
* An important piece of information is how a package should be routed.
* Currently we have three main routing algorithms (router::Type).
*
* \code
* using namespace painlessmesh;
*
* // Inherit from SinglePackage, the most basic package with
* router::Type::SINGLE class SensorPackage : public plugin::SinglePackage {
*
* };
*
* \endcode
*/
namespace plugin {
class SinglePackage : public protocol::PackageInterface {
public:
uint32_t from;
uint32_t dest;
router::Type routing;
int type;
int noJsonFields = 4;
SinglePackage(int type) : routing(router::SINGLE), type(type) {}
SinglePackage(JsonObject jsonObj) {
from = jsonObj["from"];
dest = jsonObj["dest"];
type = jsonObj["type"];
routing = static_cast<router::Type>(jsonObj["routing"].as<int>());
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["from"] = from;
jsonObj["dest"] = dest;
jsonObj["routing"] = static_cast<int>(routing);
jsonObj["type"] = type;
return jsonObj;
}
};
class BroadcastPackage : public protocol::PackageInterface {
public:
uint32_t from;
router::Type routing;
int type;
int noJsonFields = 3;
BroadcastPackage(int type) : routing(router::BROADCAST), type(type) {}
BroadcastPackage(JsonObject jsonObj) {
from = jsonObj["from"];
type = jsonObj["type"];
routing = static_cast<router::Type>(jsonObj["routing"].as<int>());
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["from"] = from;
jsonObj["routing"] = static_cast<int>(routing);
jsonObj["type"] = type;
return jsonObj;
}
};
class NeighbourPackage : public plugin::SinglePackage {
public:
NeighbourPackage(int type) : SinglePackage(type) {
routing = router::NEIGHBOUR;
}
NeighbourPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {}
};
/**
* Handle different plugins
*
* Responsible for
* - having a list of plugin types
* - the functions defined to handle the different plugin types
* - tasks?
*/
template <typename T>
class PackageHandler : public layout::Layout<T> {
public:
void stop() {
for (auto&& task : taskList) {
task->disable();
task->setCallback(NULL);
}
taskList.clear();
}
~PackageHandler() {
if (taskList.size() > 0)
Log(logger::ERROR,
"~PackageHandler(): Always call PackageHandler::stop(scheduler) "
"before calling this destructor");
}
bool sendPackage(const protocol::PackageInterface* pkg) {
auto variant = protocol::Variant(pkg);
// if single or neighbour with direction
if (variant.routing() == router::SINGLE ||
(variant.routing() == router::NEIGHBOUR && variant.dest() != 0)) {
return router::send(variant, (*this));
}
// if broadcast or neighbour without direction
if (variant.routing() == router::BROADCAST ||
(variant.routing() == router::NEIGHBOUR && variant.dest() == 0)) {
auto i = router::broadcast(variant, (*this), 0);
if (i > 0) return true;
return false;
}
return false;
}
void onPackage(int type, std::function<bool(protocol::Variant)> function) {
auto func = [function](protocol::Variant var, std::shared_ptr<T>,
uint32_t) { return function(var); };
this->callbackList.onPackage(type, func);
}
/**
* Add a task to the scheduler
*
* The task will be stored in a list and a shared_ptr to the task will be
* returned. If the task is anonymous (i.e. no shared_ptr to it is held
* anywhere else) and disabled then it will be reused when a new task is
* added.
*/
std::shared_ptr<Task> addTask(Scheduler& scheduler, unsigned long aInterval,
long aIterations,
std::function<void()> aCallback) {
using namespace painlessmesh::logger;
for (auto&& task : taskList) {
if (task.use_count() == 1 && !task->isEnabled()) {
task->set(aInterval, aIterations, aCallback, NULL, NULL);
task->enable();
return task;
}
}
std::shared_ptr<Task> task =
std::make_shared<Task>(aInterval, aIterations, aCallback);
scheduler.addTask((*task));
task->enable();
taskList.push_front(task);
return task;
}
std::shared_ptr<Task> addTask(Scheduler& scheduler,
std::function<void()> aCallback) {
return this->addTask(scheduler, 0, TASK_ONCE, aCallback);
}
protected:
callback::MeshPackageCallbackList<T> callbackList;
std::list<std::shared_ptr<Task> > taskList = {};
};
} // namespace plugin
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,636 @@
#ifndef _PAINLESS_MESH_PROTOCOL_HPP_
#define _PAINLESS_MESH_PROTOCOL_HPP_
#include <cmath>
#include <list>
#include "Arduino.h"
#include "painlessmesh/configuration.hpp"
namespace painlessmesh {
namespace router {
/** Different ways to route packages
*
* NEIGHBOUR packages are send to the neighbour and will be immediately handled
* there. The TIME_SYNC and NODE_SYNC packages are NEIGHBOUR. SINGLE messages
* are meant for a specific node. When another node receives this message, it
* will look in its routing information and send it on to the correct node,
* withouth processing the message in any other way. Only the targetted node
* will actually parse/handle this message (without sending it on). Finally,
* BROADCAST message are send to every node and processed/handled by every node.
* */
enum Type { ROUTING_ERROR = -1, NEIGHBOUR, SINGLE, BROADCAST };
} // namespace router
namespace protocol {
enum Type {
TIME_DELAY = 3,
TIME_SYNC = 4,
NODE_SYNC_REQUEST = 5,
NODE_SYNC_REPLY = 6,
CONTROL = 7, // deprecated
BROADCAST = 8, // application data for everyone
SINGLE = 9 // application data for a single node
};
enum TimeType {
TIME_SYNC_ERROR = -1,
TIME_SYNC_REQUEST,
TIME_REQUEST,
TIME_REPLY
};
class PackageInterface {
public:
virtual JsonObject addTo(JsonObject&& jsonObj) const = 0;
virtual size_t jsonObjectSize() const = 0;
};
/**
* Single package
*
* Message send to a specific node
*/
class Single : public PackageInterface {
public:
int type = SINGLE;
uint32_t from;
uint32_t dest;
TSTRING msg = "";
Single() {}
Single(uint32_t fromID, uint32_t destID, TSTRING& message) {
from = fromID;
dest = destID;
msg = message;
}
Single(JsonObject jsonObj) {
dest = jsonObj["dest"].as<uint32_t>();
from = jsonObj["from"].as<uint32_t>();
msg = jsonObj["msg"].as<TSTRING>();
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["type"] = type;
jsonObj["dest"] = dest;
jsonObj["from"] = from;
jsonObj["msg"] = msg;
return jsonObj;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(4) + round(1.1 * msg.length());
}
};
/**
* Broadcast package
*/
class Broadcast : public Single {
public:
int type = BROADCAST;
using Single::Single;
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = Single::addTo(std::move(jsonObj));
jsonObj["type"] = type;
return jsonObj;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(4) + round(1.1 * msg.length());
}
};
class NodeTree : public PackageInterface {
public:
uint32_t nodeId = 0;
bool root = false;
std::list<NodeTree> subs;
NodeTree() {}
NodeTree(uint32_t nodeID, bool iAmRoot) {
nodeId = nodeID;
root = iAmRoot;
}
NodeTree(JsonObject jsonObj) {
if (jsonObj.containsKey("root")) root = jsonObj["root"].as<bool>();
if (jsonObj.containsKey("nodeId"))
nodeId = jsonObj["nodeId"].as<uint32_t>();
else
nodeId = jsonObj["from"].as<uint32_t>();
if (jsonObj.containsKey("subs")) {
auto jsonArr = jsonObj["subs"].as<JsonArray>();
for (size_t i = 0; i < jsonArr.size(); ++i) {
subs.push_back(NodeTree(jsonArr[i].as<JsonObject>()));
}
}
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["nodeId"] = nodeId;
if (root) jsonObj["root"] = root;
if (subs.size() > 0) {
JsonArray subsArr = jsonObj.createNestedArray("subs");
for (auto&& s : subs) {
JsonObject subObj = subsArr.createNestedObject();
subObj = s.addTo(std::move(subObj));
}
}
return jsonObj;
}
bool operator==(const NodeTree& b) const {
if (!(this->nodeId == b.nodeId && this->root == b.root &&
this->subs.size() == b.subs.size()))
return false;
auto itA = this->subs.begin();
auto itB = b.subs.begin();
for (size_t i = 0; i < this->subs.size(); ++i) {
if ((*itA) != (*itB)) {
return false;
}
++itA;
++itB;
}
return true;
}
bool operator!=(const NodeTree& b) const { return !this->operator==(b); }
TSTRING toString(bool pretty = false);
size_t jsonObjectSize() const {
size_t base = 1;
if (root) ++base;
if (subs.size() > 0) ++base;
size_t size = JSON_OBJECT_SIZE(base);
if (subs.size() > 0) size += JSON_ARRAY_SIZE(subs.size());
for (auto&& s : subs) size += s.jsonObjectSize();
return size;
}
void clear() {
nodeId = 0;
subs.clear();
root = false;
}
};
/**
* NodeSyncRequest package
*/
class NodeSyncRequest : public NodeTree {
public:
int type = NODE_SYNC_REQUEST;
uint32_t from;
uint32_t dest;
NodeSyncRequest() {}
NodeSyncRequest(uint32_t fromID, uint32_t destID, std::list<NodeTree> subTree,
bool iAmRoot = false) {
from = fromID;
dest = destID;
subs = subTree;
nodeId = fromID;
root = iAmRoot;
}
NodeSyncRequest(JsonObject jsonObj) : NodeTree(jsonObj) {
dest = jsonObj["dest"].as<uint32_t>();
from = jsonObj["from"].as<uint32_t>();
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = NodeTree::addTo(std::move(jsonObj));
jsonObj["type"] = type;
jsonObj["dest"] = dest;
jsonObj["from"] = from;
return jsonObj;
}
bool operator==(const NodeSyncRequest& b) const {
if (!(this->from == b.from && this->dest == b.dest)) return false;
return NodeTree::operator==(b);
}
bool operator!=(const NodeSyncRequest& b) const {
return !this->operator==(b);
}
size_t jsonObjectSize() const {
size_t base = 4;
if (root) ++base;
if (subs.size() > 0) ++base;
size_t size = JSON_OBJECT_SIZE(base);
if (subs.size() > 0) size += JSON_ARRAY_SIZE(subs.size());
for (auto&& s : subs) size += s.jsonObjectSize();
return size;
}
};
/**
* NodeSyncReply package
*/
class NodeSyncReply : public NodeSyncRequest {
public:
int type = NODE_SYNC_REPLY;
using NodeSyncRequest::NodeSyncRequest;
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = NodeSyncRequest::addTo(std::move(jsonObj));
jsonObj["type"] = type;
return jsonObj;
}
};
struct time_sync_msg_t {
int type = TIME_SYNC_ERROR;
uint32_t t0 = 0;
uint32_t t1 = 0;
uint32_t t2 = 0;
};
/**
* TimeSync package
*/
class TimeSync : public PackageInterface {
public:
int type = TIME_SYNC;
uint32_t dest;
uint32_t from;
time_sync_msg_t msg;
TimeSync() {}
TimeSync(uint32_t fromID, uint32_t destID) {
from = fromID;
dest = destID;
msg.type = TIME_SYNC_REQUEST;
}
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0) {
from = fromID;
dest = destID;
msg.type = TIME_REQUEST;
msg.t0 = t0;
}
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0, uint32_t t1) {
from = fromID;
dest = destID;
msg.type = TIME_REPLY;
msg.t0 = t0;
msg.t1 = t1;
}
TimeSync(uint32_t fromID, uint32_t destID, uint32_t t0, uint32_t t1,
uint32_t t2) {
from = fromID;
dest = destID;
msg.type = TIME_REPLY;
msg.t0 = t0;
msg.t1 = t1;
msg.t2 = t2;
}
TimeSync(JsonObject jsonObj) {
dest = jsonObj["dest"].as<uint32_t>();
from = jsonObj["from"].as<uint32_t>();
msg.type = jsonObj["msg"]["type"].as<int>();
if (jsonObj["msg"].containsKey("t0"))
msg.t0 = jsonObj["msg"]["t0"].as<uint32_t>();
if (jsonObj["msg"].containsKey("t1"))
msg.t1 = jsonObj["msg"]["t1"].as<uint32_t>();
if (jsonObj["msg"].containsKey("t2"))
msg.t2 = jsonObj["msg"]["t2"].as<uint32_t>();
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["type"] = type;
jsonObj["dest"] = dest;
jsonObj["from"] = from;
auto msgObj = jsonObj.createNestedObject("msg");
msgObj["type"] = msg.type;
if (msg.type >= 1) msgObj["t0"] = msg.t0;
if (msg.type >= 2) {
msgObj["t1"] = msg.t1;
msgObj["t2"] = msg.t2;
}
return jsonObj;
}
/**
* Create a reply to the current message with the new time set
*/
void reply(uint32_t newT0) {
msg.t0 = newT0;
++msg.type;
std::swap(from, dest);
}
/**
* Create a reply to the current message with the new time set
*/
void reply(uint32_t newT1, uint32_t newT2) {
msg.t1 = newT1;
msg.t2 = newT2;
++msg.type;
std::swap(from, dest);
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4);
}
};
/**
* TimeDelay package
*/
class TimeDelay : public TimeSync {
public:
int type = TIME_DELAY;
using TimeSync::TimeSync;
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = TimeSync::addTo(std::move(jsonObj));
jsonObj["type"] = type;
return jsonObj;
}
};
/**
* Can store any package variant
*
* Internally stores packages as a JsonObject. Main use case is to convert
* different packages from and to Json (using ArduinoJson).
*/
class Variant {
public:
#ifdef ARDUINOJSON_ENABLE_STD_STRING
/**
* Create Variant object from a json string
*
* @param json The json string containing a package
*/
Variant(std::string json)
: jsonBuffer(JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4) +
2 * json.length()) {
error = deserializeJson(jsonBuffer, json,
DeserializationOption::NestingLimit(255));
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
}
/**
* Create Variant object from a json string
*
* @param json The json string containing a package
* @param capacity The capacity to reserve for parsing the string
*/
Variant(std::string json, size_t capacity) : jsonBuffer(capacity) {
error = deserializeJson(jsonBuffer, json,
DeserializationOption::NestingLimit(255));
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
}
#endif
#ifdef ARDUINOJSON_ENABLE_ARDUINO_STRING
/**
* Create Variant object from a json string
*
* @param json The json string containing a package
*/
Variant(String json)
: jsonBuffer(JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(4) +
2 * json.length()) {
error = deserializeJson(jsonBuffer, json,
DeserializationOption::NestingLimit(255));
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
}
/**
* Create Variant object from a json string
*
* @param json The json string containing a package
* @param capacity The capacity to reserve for parsing the string
*/
Variant(String json, size_t capacity) : jsonBuffer(capacity) {
error = deserializeJson(jsonBuffer, json,
DeserializationOption::NestingLimit(255));
if (!error) jsonObj = jsonBuffer.as<JsonObject>();
}
#endif
/**
* Create Variant object from any package implementing PackageInterface
*/
Variant(const PackageInterface* pkg) : jsonBuffer(pkg->jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = pkg->addTo(std::move(jsonObj));
}
/**
* Create Variant object from a Single package
*
* @param single The single package
*/
Variant(Single single) : jsonBuffer(single.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = single.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a Broadcast package
*
* @param broadcast The broadcast package
*/
Variant(Broadcast broadcast) : jsonBuffer(broadcast.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = broadcast.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a NodeTree
*
* @param nodeTree The NodeTree
*/
Variant(NodeTree nodeTree) : jsonBuffer(nodeTree.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = nodeTree.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a NodeSyncReply package
*
* @param nodeSyncReply The nodeSyncReply package
*/
Variant(NodeSyncReply nodeSyncReply)
: jsonBuffer(nodeSyncReply.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = nodeSyncReply.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a NodeSyncRequest package
*
* @param nodeSyncRequest The nodeSyncRequest package
*/
Variant(NodeSyncRequest nodeSyncRequest)
: jsonBuffer(nodeSyncRequest.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = nodeSyncRequest.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a TimeSync package
*
* @param timeSync The timeSync package
*/
Variant(TimeSync timeSync) : jsonBuffer(timeSync.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = timeSync.addTo(std::move(jsonObj));
}
/**
* Create Variant object from a TimeDelay package
*
* @param timeDelay The timeDelay package
*/
Variant(TimeDelay timeDelay) : jsonBuffer(timeDelay.jsonObjectSize()) {
jsonObj = jsonBuffer.to<JsonObject>();
jsonObj = timeDelay.addTo(std::move(jsonObj));
}
/**
* Whether this package is of the given type
*/
template <typename T>
inline bool is() {
return false;
}
/**
* Convert Variant to the given type
*/
template <typename T>
inline T to() {
return T(jsonObj);
}
/**
* Return package type
*/
int type() { return jsonObj["type"].as<int>(); }
/**
* Package routing method
*/
router::Type routing() {
if (jsonObj.containsKey("routing"))
return (router::Type)jsonObj["routing"].as<int>();
auto type = this->type();
if (type == SINGLE || type == TIME_DELAY) return router::SINGLE;
if (type == BROADCAST) return router::BROADCAST;
if (type == NODE_SYNC_REQUEST || type == NODE_SYNC_REPLY ||
type == TIME_SYNC)
return router::NEIGHBOUR;
return router::ROUTING_ERROR;
}
/**
* Destination node of the package
*/
uint32_t dest() {
if (jsonObj.containsKey("dest")) return jsonObj["dest"].as<uint32_t>();
return 0;
}
#ifdef ARDUINOJSON_ENABLE_STD_STRING
/**
* Print a variant to a string
*
* @return A json representation of the string
*/
void printTo(std::string& str, bool pretty = false) {
if (pretty)
serializeJsonPretty(jsonObj, str);
else
serializeJson(jsonObj, str);
}
#endif
#ifdef ARDUINOJSON_ENABLE_ARDUINO_STRING
/**
* Print a variant to a string
*
* @return A json representation of the string
*/
void printTo(String& str, bool pretty = false) {
if (pretty)
serializeJsonPretty(jsonObj, str);
else
serializeJson(jsonObj, str);
}
#endif
DeserializationError error = DeserializationError::Ok;
private:
DynamicJsonDocument jsonBuffer;
JsonObject jsonObj;
};
template <>
inline bool Variant::is<Single>() {
return jsonObj["type"].as<int>() == SINGLE;
}
template <>
inline bool Variant::is<Broadcast>() {
return jsonObj["type"].as<int>() == BROADCAST;
}
template <>
inline bool Variant::is<NodeSyncReply>() {
return jsonObj["type"].as<int>() == NODE_SYNC_REPLY;
}
template <>
inline bool Variant::is<NodeSyncRequest>() {
return jsonObj["type"].as<int>() == NODE_SYNC_REQUEST;
}
template <>
inline bool Variant::is<TimeSync>() {
return jsonObj["type"].as<int>() == TIME_SYNC;
}
template <>
inline bool Variant::is<TimeDelay>() {
return jsonObj["type"].as<int>() == TIME_DELAY;
}
template <>
inline JsonObject Variant::to<JsonObject>() {
return jsonObj;
}
inline TSTRING NodeTree::toString(bool pretty) {
TSTRING str;
auto variant = Variant(*this);
variant.printTo(str, pretty);
return str;
}
} // namespace protocol
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,231 @@
#ifndef _PAINLESS_MESH_ROUTER_HPP_
#define _PAINLESS_MESH_ROUTER_HPP_
#include <algorithm>
#include <map>
#include "painlessmesh/callback.hpp"
#include "painlessmesh/layout.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/protocol.hpp"
extern painlessmesh::logger::LogClass Log;
namespace painlessmesh {
/**
* Helper functions to route messages
*/
namespace router {
template <class T>
std::shared_ptr<T> findRoute(layout::Layout<T> tree,
std::function<bool(std::shared_ptr<T>)> func) {
auto route = std::find_if(tree.subs.begin(), tree.subs.end(), func);
if (route == tree.subs.end()) return NULL;
return (*route);
}
template <class T>
std::shared_ptr<T> findRoute(layout::Layout<T> tree, uint32_t nodeId) {
return findRoute<T>(tree, [nodeId](std::shared_ptr<T> s) {
return layout::contains((*s), nodeId);
});
}
template <class T, class U>
bool send(T package, std::shared_ptr<U> conn, bool priority = false) {
auto variant = painlessmesh::protocol::Variant(package);
TSTRING msg;
variant.printTo(msg);
return conn->addMessage(msg, priority);
}
template <class U>
bool send(protocol::Variant variant, std::shared_ptr<U> conn,
bool priority = false) {
TSTRING msg;
variant.printTo(msg);
return conn->addMessage(msg, priority);
}
template <class T, class U>
bool send(T package, layout::Layout<U> layout) {
auto variant = painlessmesh::protocol::Variant(package);
TSTRING msg;
variant.printTo(msg);
auto conn = findRoute<U>(layout, variant.dest);
if (conn) return conn->addMessage(msg);
return false;
}
template <class U>
bool send(protocol::Variant variant, layout::Layout<U> layout) {
TSTRING msg;
variant.printTo(msg);
auto conn = findRoute<U>(layout, variant.dest());
if (conn) return conn->addMessage(msg);
return false;
}
template <class T, class U>
size_t broadcast(T package, layout::Layout<U> layout, uint32_t exclude) {
auto variant = painlessmesh::protocol::Variant(package);
TSTRING msg;
variant.printTo(msg);
size_t i = 0;
for (auto&& conn : layout.subs) {
if (conn->nodeId != 0 && conn->nodeId != exclude) {
auto sent = conn->addMessage(msg);
if (sent) ++i;
}
}
return i;
}
template <class T>
size_t broadcast(protocol::Variant variant, layout::Layout<T> layout,
uint32_t exclude) {
TSTRING msg;
variant.printTo(msg);
size_t i = 0;
for (auto&& conn : layout.subs) {
if (conn->nodeId != 0 && conn->nodeId != exclude) {
auto sent = conn->addMessage(msg);
if (sent) ++i;
}
}
return i;
}
template <class T>
void routePackage(layout::Layout<T> layout, std::shared_ptr<T> connection,
TSTRING pkg, callback::MeshPackageCallbackList<T> cbl, uint32_t receivedAt) {
using namespace logger;
static size_t baseCapacity = 512;
Log(COMMUNICATION, "routePackage(): Recvd from %u: %s\n", connection->nodeId,
pkg.c_str());
// Using a ptr so we can overwrite it if we need to grow capacity.
// Bug in copy constructor with grown capacity can cause segmentation fault
auto variant =
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
while (variant->error == 3 && baseCapacity <= 20480) {
// Not enough memory, adapt scaling (variant::capacityScaling) and log the
// new value
Log(DEBUG,
"routePackage(): parsing failed. err=%u, increasing capacity: %u\n",
variant->error, baseCapacity);
baseCapacity += 256;
variant =
std::make_shared<protocol::Variant>(pkg, pkg.length() + baseCapacity);
}
if (variant->error) {
Log(ERROR,
"routePackage(): parsing failed. err=%u, total_length=%d, data=%s<--\n",
variant->error, pkg.length(), pkg.c_str());
return;
}
if (variant->routing() == SINGLE && variant->dest() != layout.getNodeId()) {
// Send on without further processing
send<T>((*variant), layout);
return;
} else if (variant->routing() == BROADCAST) {
broadcast<T>((*variant), layout, connection->nodeId);
}
auto calls = cbl.execute(variant->type(), (*variant), connection, receivedAt);
if (calls == 0)
Log(DEBUG, "routePackage(): No callbacks executed; %u, %s\n", variant->type(), pkg.c_str());
}
template <class T, class U>
void handleNodeSync(T& mesh, protocol::NodeTree newTree,
std::shared_ptr<U> conn) {
Log(logger::SYNC, "handleNodeSync(): with %u\n", conn->nodeId);
if (!conn->validSubs(newTree)) {
Log(logger::SYNC, "handleNodeSync(): invalid new connection\n");
conn->close();
return;
}
if (conn->newConnection) {
auto oldConnection = router::findRoute<U>(mesh, newTree.nodeId);
if (oldConnection) {
Log(logger::SYNC,
"handleNodeSync(): already connected to %u. Closing the new "
"connection \n",
conn->nodeId);
conn->close();
return;
}
mesh.addTask([&mesh, remoteNodeId = newTree.nodeId]() {
Log(logger::CONNECTION, "newConnectionTask():\n");
Log(logger::CONNECTION, "newConnectionTask(): adding %u now= %u\n",
remoteNodeId, mesh.getNodeTime());
mesh.newConnectionCallbacks.execute(remoteNodeId);
});
// Initially interval is every 10 seconds,
// this will slow down to TIME_SYNC_INTERVAL
// after first succesfull sync
// TODO move it to a new connection callback and use initTimeSync from
// ntp.hpp
conn->timeSyncTask.set(10 * TASK_SECOND, TASK_FOREVER, [conn, &mesh]() {
Log(logger::S_TIME, "timeSyncTask(): %u\n", conn->nodeId);
mesh.startTimeSync(conn);
});
mesh.mScheduler->addTask(conn->timeSyncTask);
if (conn->station)
// We are STA, request time immediately
conn->timeSyncTask.enable();
else
// We are the AP, give STA the change to initiate time sync
conn->timeSyncTask.enableDelayed();
conn->newConnection = false;
}
if (conn->updateSubs(newTree)) {
mesh.addTask([&mesh, nodeId = newTree.nodeId]() {
mesh.changedConnectionCallbacks.execute(nodeId);
});
} else {
conn->nodeSyncTask.delay();
mesh.stability += std::min(1000 - mesh.stability, (size_t)25);
}
}
template <class T, typename U>
callback::MeshPackageCallbackList<U> addPackageCallback(
callback::MeshPackageCallbackList<U>&& callbackList, T& mesh) {
// REQUEST type,
callbackList.onPackage(
protocol::NODE_SYNC_REQUEST,
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
uint32_t receivedAt) {
auto newTree = variant.to<protocol::NodeSyncRequest>();
handleNodeSync<T, U>(mesh, newTree, connection);
send<protocol::NodeSyncReply>(
connection->reply(std::move(mesh.asNodeTree())), connection, true);
return false;
});
// Reply type just handle it
callbackList.onPackage(
protocol::NODE_SYNC_REPLY,
[&mesh](protocol::Variant variant, std::shared_ptr<U> connection,
uint32_t receivedAt) {
auto newTree = variant.to<protocol::NodeSyncReply>();
handleNodeSync<T, U>(mesh, newTree, connection);
connection->timeOutTask.disable();
return false;
});
return callbackList;
}
} // namespace router
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,73 @@
#ifndef _PAINLESS_MESH_TCP_HPP_
#define _PAINLESS_MESH_TCP_HPP_
#include <list>
#include "Arduino.h"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
namespace painlessmesh {
namespace tcp {
inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
using namespace painlessmesh::logger;
Log(GENERAL, "encodeNodeId():\n");
uint32_t value = 0;
value |= hwaddr[2] << 24; // Big endian (aka "network order"):
value |= hwaddr[3] << 16;
value |= hwaddr[4] << 8;
value |= hwaddr[5];
return value;
}
template <class T, class M>
void initServer(AsyncServer &server, M &mesh) {
using namespace logger;
server.setNoDelay(true);
server.onClient(
[&mesh](void *arg, AsyncClient *client) {
if (mesh.semaphoreTake()) {
Log(CONNECTION, "New AP connection incoming\n");
auto conn = std::make_shared<T>(client, &mesh, false);
conn->initTasks();
conn->initTCPCallbacks();
mesh.subs.push_back(conn);
mesh.semaphoreGive();
}
},
NULL);
server.begin();
}
template <class T, class M>
void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh) {
using namespace logger;
client.onError([&mesh](void *, AsyncClient *client, int8_t err) {
if (mesh.semaphoreTake()) {
Log(CONNECTION, "tcp_err(): error trying to connect %d\n", err);
mesh.droppedConnectionCallbacks.execute(0, true);
mesh.semaphoreGive();
}
});
client.onConnect(
[&mesh](void *, AsyncClient *client) {
if (mesh.semaphoreTake()) {
Log(CONNECTION, "New STA connection incoming\n");
auto conn = std::make_shared<T>(client, &mesh, true);
conn->initTasks();
conn->initTCPCallbacks();
mesh.subs.push_back(conn);
mesh.semaphoreGive();
}
},
NULL);
client.connect(ip, port);
}
} // namespace tcp
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,202 @@
#ifndef _PAINLESS_MESH_PLUGIN_PERFORMANCE_HPP_
#define _PAINLESS_MESH_PLUGIN_PERFORMANCE_HPP_
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/plugin.hpp"
namespace painlessmesh {
namespace plugin {
/** Add performance tracking to the mesh
*
* Nodes will send out special packages to each other, allowing each node to
* keep track of different performance measures
*/
namespace performance {
/** Track mean and variance of the given value
*
* Older values are discounted, so that you keep a rolling average
*/
class Stats {
public:
void update(double v, double alpha = 0.1) {
if (!init) {
mu = v;
init = true;
return;
}
auto d = v - mu;
mu = mu + alpha * d;
var = (1 - alpha) * (var + alpha * pow(d, 2));
}
// Returns the mean and 95% interval based on 1.96*sd? or 2.96*sd
TSTRING toString() const {
#ifdef PAINLESSMESH_ENABLE_STD_STRING
std::stringstream ss;
ss << mu << "[" << mu - 1.96 * sqrt(var) << "," << mu + 1.96 * sqrt(var)
<< "]";
return ss.str();
#else
return TSTRING(mu) + TSTRING("[") + TSTRING(mu - 1.96 * sqrt(var)) +
TSTRING(",") + TSTRING(mu + 1.96 * sqrt(var)) + TSTRING("]");
#endif
}
protected:
double mu = 0;
double var = 0;
bool init = false;
};
class PerformancePackage : public plugin::BroadcastPackage {
public:
int id = 0; // Can see if we missed values
int time; // Get an idea of the delay, by comparing it with nodetime
int stability; // stability of the sending node
int freeMemory; // memory of the sending node
#ifdef ESP32
TSTRING hardware = "ESP32";
#else
TSTRING hardware = "ESP8266";
#endif
PerformancePackage() : plugin::BroadcastPackage(13) {}
PerformancePackage(JsonObject jsonObj) : plugin::BroadcastPackage(jsonObj) {
id = jsonObj["id"];
time = jsonObj["time"];
stability = jsonObj["stability"];
freeMemory = jsonObj["freeMemory"];
hardware = jsonObj["hardware"].as<TSTRING>();
}
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
jsonObj["type"] = type;
jsonObj["id"] = id;
jsonObj["time"] = time;
jsonObj["stability"] = stability;
jsonObj["freeMemory"] = freeMemory;
jsonObj["hardware"] = hardware;
return jsonObj;
}
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(4 + 5) + round(2 * (hardware.length()));
}
};
/// Numbers to track for each node we receive PerformancePackages from
class Track {
public:
uint32_t nodeId;
TSTRING hardware;
uint32_t hits = 0;
uint32_t misses = 0;
int lastId = 0;
Stats delay;
Stats stability;
Stats freeMemory;
uint32_t present = 0; // Every so often check if each node is absent or
// present in the layout
uint32_t absent = 0;
Track() {}
void addTo(JsonObject& jsonObj) const {
jsonObj["nodeId"] = nodeId;
jsonObj["hardware"] = hardware;
jsonObj["hits"] = hits;
jsonObj["misses"] = misses;
jsonObj["delay"] = delay.toString();
jsonObj["stability"] = stability.toString();
jsonObj["freeMemory"] = freeMemory.toString();
jsonObj["present"] = present;
jsonObj["absent"] = absent;
}
};
/// Holds resulst from all the nodes
class TrackMap : public protocol::PackageInterface,
public std::map<uint32_t, Track> {
public:
JsonObject addTo(JsonObject&& jsonObj) const {
jsonObj["event"] = "performance";
// Start array
auto jsonArr = jsonObj.createNestedArray("nodes");
// for each in map do
for (auto&& pair : (*this)) {
auto obj = jsonArr.createNestedObject();
pair.second.addTo(obj);
}
return jsonObj;
} // namespace performance
size_t jsonObjectSize() const {
return JSON_OBJECT_SIZE(2 + 15) + JSON_ARRAY_SIZE(this->size()) +
this->size()*(JSON_OBJECT_SIZE(9) + 4 * 100);
}
}; // namespace plugin
template <class T>
void begin(T& mesh, double frequency = 2) {
auto tracker = std::make_shared<TrackMap>();
auto sendPkg = std::make_shared<PerformancePackage>();
mesh.onPackage(sendPkg->type, [&mesh, tracker](protocol::Variant var) {
auto pkg = var.to<PerformancePackage>();
// if not in tracker, add it
if (!tracker->count(pkg.from)) {
tracker->operator[](pkg.from) = Track();
if (pkg.id > 0) tracker->operator[](pkg.from).lastId = pkg.id - 1;
}
// update all the values in the trackmap
tracker->operator[](pkg.from).nodeId = pkg.from;
tracker->operator[](pkg.from).hardware = pkg.hardware;
++tracker->operator[](pkg.from).hits;
if (pkg.id < tracker->operator[](pkg.from).lastId) // Node was reset?
tracker->operator[](pkg.from).lastId = pkg.id;
else {
tracker->operator[](pkg.from).misses +=
(pkg.id - tracker->operator[](pkg.from).lastId) - 1;
}
tracker->operator[](pkg.from).lastId = pkg.id;
tracker->operator[](pkg.from).delay.update(
((int)mesh.getNodeTime() - pkg.time) / 1000);
tracker->operator[](pkg.from).stability.update(pkg.stability);
tracker->operator[](pkg.from).freeMemory.update(pkg.freeMemory);
return false;
});
sendPkg->from = mesh.getNodeId();
mesh.addTask(frequency*TASK_SECOND, TASK_FOREVER, [sendPkg, &mesh]() {
++sendPkg->id;
sendPkg->time = mesh.getNodeTime();
sendPkg->stability = mesh.stability;
sendPkg->freeMemory = ESP.getFreeHeap();
mesh.sendPackage(sendPkg.get());
});
mesh.addTask(TASK_MINUTE, TASK_FOREVER, [tracker, &mesh]() {
for (auto&& pair : (*tracker)) {
if (mesh.isConnected(pair.first))
++pair.second.present;
else
++pair.second.absent;
}
protocol::Variant var(tracker.get());
TSTRING str;
var.printTo(str);
#ifdef PAINLESSMESH_ENABLE_STD_STRING
std::cout << str << std::endl;
#else
Serial.println(str);
#endif
});
}
} // namespace performance
} // namespace plugin
} // namespace painlessmesh
#endif

View File

@@ -0,0 +1,15 @@
/*
* https://github.com/arkhipenko/TaskScheduler/tree/master/examples/Scheduler_example16_Multitab
*/
// #define _TASK_TIMECRITICAL // Enable monitoring scheduling overruns
// #define _TASK_SLEEP_ON_IDLE_RUN // Enable 1 ms SLEEP_IDLE powerdowns between tasks if no callback methods were invoked during the pass
// #define _TASK_STATUS_REQUEST // Compile with support for StatusRequest functionality - triggering tasks on status change events in addition to time only
// #define _TASK_WDT_IDS // Compile with support for wdt control points and task ids
// #define _TASK_LTS_POINTER // Compile with support for local task storage pointer
#define _TASK_PRIORITY // Support for layered scheduling priority
// #define _TASK_MICRO_RES // Support for microsecond resolution
#define _TASK_STD_FUNCTION // Support for std::function (ESP8266 ONLY)
// #define _TASK_DEBUG // Make all methods and variables public for debug purposes
#include <TaskScheduler.h>

View File

@@ -0,0 +1,2 @@
#include "arduino/wifi.hpp"
painlessmesh::logger::LogClass Log;