Commit 5a00c5e8 authored by BlackEdder's avatar BlackEdder
Browse files

Refactor to fully separate arduino wifi code from other code

parent af9c838a
......@@ -12,8 +12,5 @@ foreach(TESTFILE ${TESTFILES})
target_include_directories(${NAME} PUBLIC test/include/ test/ArduinoJson/src/ src/ test/TaskScheduler/src)
endforeach()
add_executable(catch_integration test/catch/integration.cpp test/catch/fake_serial.cpp src/painlessMesh.cpp src/painlessMeshConnection.cpp src/painlessMeshSync.cpp src/scheduler.cpp)
add_executable(catch_integration test/catch/integration.cpp test/catch/fake_serial.cpp src/painlessMeshConnection.cpp src/scheduler.cpp)
target_include_directories(catch_integration PUBLIC test/include/ test/boostMesh/ test/ArduinoJson/src/ test/TaskScheduler/src/ src/)
add_executable(boostMesh test/boostMesh/main.cpp test/catch/fake_serial.cpp src/painlessMesh.cpp src/painlessMeshConnection.cpp src/painlessMeshSync.cpp src/scheduler.cpp)
target_include_directories(boostMesh PUBLIC test/include/ test/boostMesh/ test/ArduinoJson/src/ test/TaskScheduler/src/ src/)
......@@ -84,7 +84,6 @@ void setup() {
}
void loop() {
userScheduler.execute(); // it will run mesh scheduler as well
mesh.update();
digitalWrite(LED, !onFlag);
}
......
#ifndef _PAINLESS_MESH_ARDUINO_WIFI_HPP_
#define _PAINLESS_MESH_ARDUINO_WIFI_HPP_
#include "Arduino.h"
#include "painlessmesh/configuration.hpp"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/router.hpp"
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
#include "painlessMeshSTA.h"
#include "painlessMeshConnection.h"
#include "painlessmesh/logger.hpp"
#include "painlessmesh/mesh.hpp"
#include "painlessmesh/router.hpp"
#include "painlessmesh/tcp.hpp"
extern painlessmesh::logger::LogClass Log;
namespace painlessmesh {
namespace wifi {
class Mesh {
class Mesh : public painlessmesh::Mesh<MeshConnection> {
public:
virtual void init(uint32_t nodeId, uint16_t port) = 0;
void init(Scheduler *scheduler, uint32_t id, uint16_t port) {
painlessmesh::Mesh<MeshConnection>::init(scheduler, id, port);
}
void init(uint32_t id, uint16_t port) {
painlessmesh::Mesh<MeshConnection>::init(id, port);
}
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) {
......@@ -37,12 +45,12 @@ class Mesh {
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) {
......@@ -52,6 +60,9 @@ class Mesh {
this->init(nodeId, port);
tcpServerInit();
eventHandleInit();
_apIp = IPAddress(0, 0, 0, 0);
if (connectMode & WIFI_AP) {
......@@ -70,26 +81,241 @@ class Mesh {
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();
}
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 (_station_got_ip && 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();
eventSTAAuthChangeHandler = WiFiEventHandler();
eventSTAGotIPHandler = WiFiEventHandler();
eventSoftAPConnectedHandler = WiFiEventHandler();
eventSoftAPDisconnectedHandler = 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;
virtual void setScheduler(Scheduler *scheduler) = 0;
virtual void initStation() = 0;
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);
}
uint32_t encodeNodeId(const uint8_t *hwaddr) {
using namespace painlessmesh::logger;
Log(GENERAL, "encodeNodeId():\n");
uint32_t value = 0;
void apInit(uint32_t nodeId);
uint32_t encodeNodeId(const uint8_t *hwaddr);
value |= hwaddr[2] << 24; // Big endian (aka "network order"):
value |= hwaddr[3] << 16;
value |= hwaddr[4] << 8;
value |= hwaddr[5];
return value;
}
void eventHandleInit() {
using namespace logger;
#ifdef ESP32
eventScanDoneHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
Log(CONNECTION, "eventScanDoneHandler: SYSTEM_EVENT_SCAN_DONE\n");
this->stationScan.task.setCallback(
[this]() { this->stationScan.scanComplete(); });
this->stationScan.task.forceNextIteration();
},
WiFiEvent_t::SYSTEM_EVENT_SCAN_DONE);
eventSTAStartHandler = WiFi.onEvent(
[](WiFiEvent_t event, WiFiEventInfo_t info) {
Log(CONNECTION, "eventSTAStartHandler: SYSTEM_EVENT_STA_START\n");
},
WiFiEvent_t::SYSTEM_EVENT_STA_START);
eventSTADisconnectedHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
this->_station_got_ip = false;
Log(CONNECTION,
"eventSTADisconnectedHandler: SYSTEM_EVENT_STA_DISCONNECTED\n");
WiFi.disconnect();
// Search for APs and connect to the best one
this->stationScan.connectToAP();
},
WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
eventSTAGotIPHandler = WiFi.onEvent(
[this](WiFiEvent_t event, WiFiEventInfo_t info) {
this->_station_got_ip = true;
Log(CONNECTION, "eventSTAGotIPHandler: SYSTEM_EVENT_STA_GOT_IP\n");
this->tcpConnect(); // Connect to TCP port
},
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) {
this->_station_got_ip = false;
// Log(CONNECTION, "Event: Station Mode
// Disconnected from %s\n", event.ssid.c_str());
Log(CONNECTION, "Event: Station Mode Disconnected\n");
WiFi.disconnect();
this->stationScan
.connectToAP(); // Search for APs and connect to the best one
});
eventSTAAuthChangeHandler = WiFi.onStationModeAuthModeChanged(
[&](const WiFiEventStationModeAuthModeChanged &event) {
Log(CONNECTION, "Event: Station Mode Auth Mode Change\n");
});
eventSTAGotIPHandler =
WiFi.onStationModeGotIP([&](const WiFiEventStationModeGotIP &event) {
this->_station_got_ip = true;
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
});
eventSoftAPConnectedHandler = WiFi.onSoftAPModeStationConnected(
[&](const WiFiEventSoftAPModeStationConnected &event) {
Log(CONNECTION, "Event: %lu Connected to AP Mode Station\n",
this->encodeNodeId(event.mac));
});
eventSoftAPDisconnectedHandler = WiFi.onSoftAPModeStationDisconnected(
[&](const WiFiEventSoftAPModeStationDisconnected &event) {
Log(CONNECTION, "Event: %lu Disconnected from AP Mode Station\n",
this->encodeNodeId(event.mac));
});
#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 eventSTAAuthChangeHandler;
WiFiEventHandler eventSTAGotIPHandler;
WiFiEventHandler eventSoftAPConnectedHandler;
WiFiEventHandler eventSoftAPDisconnectedHandler;
#endif // ESP8266
AsyncServer *_tcpListener;
bool _station_got_ip = false;
friend class StationScan;
};
} // namespace wifi
}; // namespace painlessmesh
#endif
#endif
......
#include "painlessMesh.h"
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
// TODO: Is this really needed here?
#include "lwip/init.h"
#endif
LogClass Log;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
ICACHE_FLASH_ATTR painlessMesh::painlessMesh() {}
#pragma GCC diagnostic pop
void painlessMesh::init(uint32_t id, uint16_t port) {
nodeId = id;
_meshPort = port;
#ifdef ESP32
xSemaphore = xSemaphoreCreateMutex();
#endif
// establish AP tcpServers
tcpServerInit();
eventHandleInit();
_scheduler.enableAll();
// Add package handlers
callbackList =
painlessmesh::ntp::addPackageCallback(std::move(callbackList), (*this));
callbackList = painlessmesh::router::addPackageCallback(
std::move(callbackList), (*this));
}
void painlessMesh::init(Scheduler *scheduler, uint32_t id, uint16_t port) {
this->setScheduler(scheduler);
this->init(id, port);
}
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
void painlessMesh::initStation() {
stationScan.init(this, _meshSSID, _meshPassword, _meshPort);
_scheduler.addTask(stationScan.task);
stationScan.task.enable();
}
#endif
void ICACHE_FLASH_ATTR painlessMesh::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();
eventSTAAuthChangeHandler = WiFiEventHandler();
eventSTAGotIPHandler = WiFiEventHandler();
eventSoftAPConnectedHandler = WiFiEventHandler();
eventSoftAPDisconnectedHandler = WiFiEventHandler();
#endif // ESP32
// Close all connections
while (subs.size() > 0) {
auto connection = subs.begin();
(*connection)->close();
}
// Stop scanning task
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
stationScan.task.setCallback(NULL);
_scheduler.deleteTask(stationScan.task);
#endif
// Note that this results in the droppedConnections not to be signalled
// We might want to change this later
newConnectionTask.setCallback(NULL);
_scheduler.deleteTask(newConnectionTask);
droppedConnectionTask.setCallback(NULL);
_scheduler.deleteTask(droppedConnectionTask);
// Shutdown wifi hardware
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
}
//***********************************************************************
// do nothing if user have other Scheduler, they have to run their scheduler in
// loop not this library
void ICACHE_FLASH_ATTR painlessMesh::update(void) {
if (semaphoreTake()) {
_scheduler.execute();
semaphoreGive();
}
return;
}
bool ICACHE_FLASH_ATTR painlessMesh::semaphoreTake(void) {
#ifdef ESP32
return xSemaphoreTake(xSemaphore, (TickType_t)10) == pdTRUE;
#else
return true;
#endif
}
void ICACHE_FLASH_ATTR painlessMesh::semaphoreGive(void) {
#ifdef ESP32
xSemaphoreGive(xSemaphore);
#endif
}
//***********************************************************************
bool ICACHE_FLASH_ATTR painlessMesh::sendSingle(uint32_t destId, TSTRING msg) {
Log(COMMUNICATION, "sendSingle(): dest=%u msg=%s\n", destId, msg.c_str());
auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
return painlessmesh::router::send<MeshConnection>(single, (*this));
}
//***********************************************************************
bool ICACHE_FLASH_ATTR painlessMesh::sendBroadcast(TSTRING msg,
bool includeSelf) {
using namespace painlessmesh;
Log(COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str());
auto pkg = painlessmesh::protocol::Broadcast(this->nodeId, 0, msg);
auto success =
router::broadcast<protocol::Broadcast, MeshConnection>(pkg, (*this), 0);
if (success && includeSelf) {
auto variant = protocol::Variant(pkg);
callbackList.execute(pkg.type, pkg, NULL, 0);
}
if (success > 0) return true;
return false;
}
bool ICACHE_FLASH_ATTR painlessMesh::startDelayMeas(uint32_t nodeID) {
using namespace painlessmesh;
Log(S_TIME, "startDelayMeas(): NodeId %u\n", nodeID);
auto conn = painlessmesh::router::findRoute<MeshConnection>((*this), nodeID);
if (!conn) return false;
return router::send<protocol::TimeDelay, MeshConnection>(
protocol::TimeDelay(this->nodeId, nodeID, getNodeTime()), conn);
}
void ICACHE_FLASH_ATTR painlessMesh::setDebugMsgTypes(uint16_t newTypes) {
Log.setLogLevel(newTypes);
}
void ICACHE_FLASH_ATTR painlessMesh::tcpServerInit() {
Log(GENERAL, "tcpServerInit():\n");
_tcpListener = new AsyncServer(_meshPort);
painlessmesh::tcp::initServer<MeshConnection, painlessMesh>((*_tcpListener),
(*this));
Log(STARTUP, "AP tcp server established on port %d\n", _meshPort);
return;
}
......@@ -18,14 +18,17 @@ using namespace std;
#include <ESPAsyncTCP.h>
#endif // ESP32
#include "arduino/wifi.hpp"
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
#include "painlessMeshConnection.h"
#include "painlessMeshSTA.h"
#include "arduino/wifi.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"
......@@ -40,237 +43,9 @@ using namespace painlessmesh::logger;
50 // MAX number of unsent messages in queue. Newer messages are discarded
#define MAX_CONSECUTIVE_SEND 5 // Max message burst
template <typename T>
using SimpleList = std::list<T>; // backward compatibility
using ConnectionList = std::list<std::shared_ptr<MeshConnection>>;
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;
class painlessMesh
: public painlessmesh::ntp::MeshTime,
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
public painlessmesh::wifi::Mesh,
#endif
public painlessmesh::plugin::PackageHandler<MeshConnection> {
public:
void init(uint32_t nodeId, uint16_t port);
void init(Scheduler *scheduler, uint32_t nodeId, uint16_t port);
// TODO: move these to wifi::Mesh
#ifdef PAINLESSMESH_ENABLE_ARDUINO_WIFI
void initStation();
// These should be removable when things compile correctly?
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) {
using namespace painlessmesh;
wifi::Mesh::init(ssid, password, baseScheduler, port, connectMode, channel,
hidden, maxconn);
}
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 painlessmesh;
wifi::Mesh::init(ssid, password, port, connectMode, channel, hidden,
maxconn);
}
#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) { 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 root; };
// in painlessMeshDebug.cpp
void setDebugMsgTypes(uint16_t types);
// in painlessMesh.cpp
painlessMesh();
/**
* Disconnect and stop this node
*/
void stop();
void update(void);
bool sendSingle(uint32_t destId, TSTRING msg);
bool sendBroadcast(TSTRING msg, bool includeSelf = false);
bool startDelayMeas(uint32_t nodeId);
// in painlessMeshConnection.cpp
void onReceive(receivedCallback_t onReceive);
void onNewConnection(newConnectionCallback_t onNewConnection);
void onDroppedConnection(droppedConnectionCallback_t onDroppedConnection);
void onChangedConnections(changedConnectionsCallback_t onChangedConnections);
void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t onTimeAdjusted);
void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived);
bool isConnected(uint32_t nodeId) {
return painlessmesh::router::findRoute<MeshConnection>((*this), nodeId) !=
NULL;
}
std::list<uint32_t> getNodeList(bool includeSelf = false);
/**
* Return a json representation of the current mesh layout
*/
inline TSTRING subConnectionJson(bool pretty = false) {
return this->asNodeTree().toString(pretty);
}