初始化提交
This commit is contained in:
327
arduino-cli/libraries/painlessMesh-master/src/arduino/wifi.hpp
Normal file
327
arduino-cli/libraries/painlessMesh-master/src/arduino/wifi.hpp
Normal 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 doesn’t 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 doesn’t 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
|
||||
|
||||
271
arduino-cli/libraries/painlessMesh-master/src/boost/asynctcp.hpp
Normal file
271
arduino-cli/libraries/painlessMesh-master/src/boost/asynctcp.hpp
Normal 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
|
||||
63
arduino-cli/libraries/painlessMesh-master/src/painlessMesh.h
Normal file
63
arduino-cli/libraries/painlessMesh-master/src/painlessMesh.h
Normal 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_
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
15
arduino-cli/libraries/painlessMesh-master/src/scheduler.cpp
Normal file
15
arduino-cli/libraries/painlessMesh-master/src/scheduler.cpp
Normal 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>
|
||||
2
arduino-cli/libraries/painlessMesh-master/src/wifi.cpp
Normal file
2
arduino-cli/libraries/painlessMesh-master/src/wifi.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "arduino/wifi.hpp"
|
||||
painlessmesh::logger::LogClass Log;
|
||||
Reference in New Issue
Block a user