diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b005a81..4d3bbe9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,9 +37,7 @@ firmware-build: - pio run -t clean - pio run --environment basic - pio run --environment mesh - - pio run --environment meshPixel - pio run --environment meshMqttBridge - - pio run --environment ota artifacts: paths: - .pioenvs/*/firmware.* diff --git a/platformio.ini b/platformio.ini index 5124119..7a26082 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,6 +22,8 @@ lib_deps = ESPAsyncTCP TaskScheduler SPIFFS + ESP8266mDNS + ArduinoOTA ;[env:build] ;src_filter = +<*> - @@ -68,17 +70,4 @@ monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} painlessMesh - PubSubClient - -[env:ota] -src_filter = +<*> +<*/plugins/*> - + -platform = espressif8266 -board = esp12e -upload_speed = ${common.upload_speed} -upload_flags = --auth=f4ncy -monitor_baud = ${common.monitor_baud} -framework = ${common.framework} -lib_deps = ${common.lib_deps} - ESP8266mDNS - painlessMesh - ArduinoOTA \ No newline at end of file + PubSubClient \ No newline at end of file diff --git a/src/Mediator.h b/src/Mediator.h new file mode 100644 index 0000000..88f6d68 --- /dev/null +++ b/src/Mediator.h @@ -0,0 +1,31 @@ +#ifndef __MEDIATOR__ +#define __MEDIATOR__ + +#include +#include +#include +#include +#include +#include + +using namespace std; + +typedef std::function subscriptionHandler_t; + +class Mediator { + public: + std::map> subscriptions; + void subscribe(String topic, subscriptionHandler_t handler) { + subscriptions[topic.c_str()].reserve(1); + subscriptions[topic.c_str()].push_back(handler); + } + void publish(String topic, String msg) { + if (subscriptions.find(topic.c_str()) != subscriptions.end()){ + for(subscriptionHandler_t h : subscriptions[topic.c_str()]){ + h(msg); + } + } + } +}; + +#endif \ No newline at end of file diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 2160e6f..f691e7f 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -32,7 +32,7 @@ Network* MeshNet::init(){ Network* MeshNet::connectStation(int doConnect) { if(doConnect){ Serial.println("connect station"); - mesh.stationManual(config.stationSSID, config.stationPassword); + mesh.stationManual(config.stationSSID, config.stationPassword, 5555, IPAddress(192,168,1,142)); mesh.setHostname(config.hostname.c_str()); } return this; diff --git a/src/Plugin.h b/src/Plugin.h index a8791ae..bce5744 100644 --- a/src/Plugin.h +++ b/src/Plugin.h @@ -3,14 +3,26 @@ #include #include -#include +#include +#include class Plugin { public: + Mediator* mediator; virtual void activate(Scheduler*, Network*); virtual void enable(){}; virtual void disable(){}; - virtual void onMessage(MeshMessage msg){}; + virtual void onMessage(SprocketMessage msg){}; + Plugin* mediate(Mediator* m) { + mediator = m; + return this; + } + void subscribe(String topic, subscriptionHandler_t handler){ + mediator->subscribe(topic, handler); + } + void publish(String topic, String str){ + mediator->publish(topic, str); + } }; #endif \ No newline at end of file diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index a19110c..cd1b84d 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -43,13 +43,15 @@ void Sprocket::loop(){ } void Sprocket::dispatch( uint32_t from, String &msg ) { - MeshMessage mMsg; - if(mMsg.fromJsonString(msg)){ + SprocketMessage mMsg; + mMsg.fromJsonString(msg); + if(mMsg.valid){ dispatchMessageToPlugins(mMsg); } } void Sprocket::addPlugin(Plugin* p){ + p->mediate(this); plugins.reserve(1); plugins.push_back(p); } @@ -60,10 +62,11 @@ void Sprocket::activatePlugins(Scheduler* scheduler, Network* network){ } } -void Sprocket::dispatchMessageToPlugins(MeshMessage msg){ - if(msg.type != MeshMessage::NONE){ // baaaa +// TODO remove plugin dispatching and onMessage in favor of mediator pub/sub +void Sprocket::dispatchMessageToPlugins(SprocketMessage msg){ + if(msg.type != SprocketMessage::NONE){ // baaaa for(Plugin* p : plugins){ - Serial.println("dispatch to plugins"); + //Serial.println("dispatch to plugins"); p->onMessage(msg); } } diff --git a/src/Sprocket.h b/src/Sprocket.h index 676facc..d13b4b7 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -18,7 +18,7 @@ using namespace std::placeholders; // FIXME move to some global fnc lib #define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0]) -class Sprocket { +class Sprocket : public Mediator { protected: Scheduler scheduler; public: @@ -37,7 +37,7 @@ class Sprocket { void addPlugin(Plugin* p); void activatePlugins(Scheduler* scheduler, Network* network); - void dispatchMessageToPlugins(MeshMessage msg); + void dispatchMessageToPlugins(SprocketMessage msg); }; #endif \ No newline at end of file diff --git a/src/base/MeshMessage.h b/src/base/MeshMessage.h deleted file mode 100644 index 8d5b080..0000000 --- a/src/base/MeshMessage.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef __MESH_MESSAGE__ -#define __MESH_MESSAGE__ - -#include -#include - -#define JSON_DOMAIN "domain" -#define JSON_FROM "from" -#define JSON_TO "target" -#define JSON_MSG "msg" -#define JSON_TYPE "type" - -struct MeshMessage { - String domain; - String to; - String from; - String msg; - enum MeshMessageType { NONE, SYSTEM, APP, OTA } type; - int valid = 0; - // ------------------------------------------------------------------------------------------ - //void init() { - // from = reinterpret_cast(ESP.getChipId()); - //} - int verifyJsonObject(JsonObject& json){ - return json.success(); - //&& json.containsKey(JSON_DOMAIN) - //&& json.containsKey(JSON_TO) - //&& json.containsKey(JSON_FROM) - //&& json.containsKey(JSON_TYPE) - // && json.containsKey(JSON_MSG); // msg is only tx'ed from mqtt - }; - String toJsonString(){ - //StaticJsonBuffer<200> jsonBuffer; - DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); - JsonObject& root = jsonBuffer.createObject(); - root[JSON_DOMAIN] = domain; - root[JSON_TO] = to; - root[JSON_FROM] = from; - root[JSON_MSG] = msg; - root[JSON_TYPE] = type; - String jsonString; - root.printTo(jsonString); - return jsonString; - } - String getAttrFromJson(JsonObject& json, const char* attr){ - if(json.containsKey(attr)){ - return json[attr]; - } - return ""; - } - int getIntAttrFromJson(JsonObject& json, const char* attr){ - if(json.containsKey(attr)){ - return json[attr]; - } - return 0; - } - // Map a json object to this struct. - int fromJsonObject(JsonObject& json){ - if(!verifyJsonObject(json)){ - Serial.println("ERROR: cannot parse MeshMessage JSON object"); - valid = 0; - return valid; - } - domain = getAttrFromJson(json, JSON_DOMAIN); - to = getAttrFromJson(json, JSON_TO); - from = getAttrFromJson(json, JSON_FROM); - msg = getAttrFromJson(json, JSON_MSG); - type = (MeshMessageType) getIntAttrFromJson(json, JSON_TYPE); - valid = 1; - return valid; - }; - // Parse a json string and map parsed object - int fromJsonString(String& str){ - //StaticJsonBuffer<200> jsonBuffer; - DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); - JsonObject& json = jsonBuffer.parseObject(str); - return fromJsonObject(json); - }; -}; - -#endif \ No newline at end of file diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index 908ac15..7d9212f 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include "config.h" #include "utils_print.h" @@ -18,6 +18,7 @@ class MeshSprocket : public Sprocket { public: MeshNet* net; + MeshSprocket(){}; MeshSprocket(SprocketConfig cfg) : Sprocket(cfg) { } @@ -30,13 +31,13 @@ class MeshSprocket : public Sprocket { return this; } using Sprocket::activate; - virtual void onMessage(uint32_t from, String &msg) { - Serial.printf("MeshSprocket onMessage: received from %u msg=%s\n", from, msg.c_str()); - }; - void dispatch( uint32_t from, String &msg ) { - Sprocket::dispatch(from, msg); - onMessage(from, msg); + SprocketMessage sMsg; + sMsg.fromJsonString(msg); + if(sMsg.valid){ + sMsg.from = from; + publish(sMsg.topic, sMsg.payload); + } } void loop() { diff --git a/src/base/SprocketMessage.h b/src/base/SprocketMessage.h new file mode 100644 index 0000000..e4ebeee --- /dev/null +++ b/src/base/SprocketMessage.h @@ -0,0 +1,59 @@ +#ifndef __MESH_MESSAGE__ +#define __MESH_MESSAGE__ + +#include +#include +#include + +#define JSON_DOMAIN "domain" +#define JSON_FROM "from" +#define JSON_TO "target" +#define JSON_PAYLOAD "payload" +#define JSON_TYPE "type" +#define JSON_TOPIC "topic" + +struct SprocketMessage : public JsonStruct { + String domain; + String to; + String from; + String payload; + String topic; + + // TODO do we even need that? + enum SprocketMessageType { NONE, SYSTEM, APP, OTA } type; + // ------------------------------------------------------------------------------------------ + int verifyJsonObject(JsonObject& json){ + return json.success() + //&& json.containsKey(JSON_DOMAIN) + //&& json.containsKey(JSON_TO) + //&& json.containsKey(JSON_FROM) + //&& json.containsKey(JSON_TYPE) + && json.containsKey(JSON_TOPIC) + && json.containsKey(JSON_PAYLOAD); + }; + void mapJsonObject(JsonObject& root){ + root[JSON_DOMAIN] = domain; + root[JSON_TO] = to; + root[JSON_FROM] = from; + root[JSON_PAYLOAD] = payload; + root[JSON_TOPIC] = topic; + root[JSON_TYPE] = type; + } + // Map a json object to this struct. + void fromJsonObject(JsonObject& json){ + if(!verifyJsonObject(json)){ + Serial.println("ERROR: cannot parse SprocketMessage JSON object"); + valid = 0; + return; + } + domain = getAttrFromJson(json, JSON_DOMAIN); + to = getAttrFromJson(json, JSON_TO); + from = getAttrFromJson(json, JSON_FROM); + payload = getAttrFromJson(json, JSON_PAYLOAD); + topic = getAttrFromJson(json, JSON_TOPIC); + type = (SprocketMessageType) getIntAttrFromJson(json, JSON_TYPE); + valid = 1; + }; +}; + +#endif \ No newline at end of file diff --git a/src/examples/mesh/MeshApp.h b/src/examples/mesh/MeshApp.h index 77cb146..d7cbec7 100644 --- a/src/examples/mesh/MeshApp.h +++ b/src/examples/mesh/MeshApp.h @@ -9,6 +9,7 @@ #include #include #include +#include "Mediator.h" using namespace std; using namespace std::placeholders; @@ -18,12 +19,13 @@ AsyncWebServer WEBSERVER(80); class MeshApp : public MeshSprocket { public: Task heartbeatTask; - + MeshApp(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : MeshSprocket(cfg) { addPlugin(new OtaTcpPlugin(otaCfg)); addPlugin(new WebServerPlugin(webCfg, &WEBSERVER)); addPlugin(new WebConfigPlugin(&WEBSERVER)); addPlugin(new MeshManPlugin(&WEBSERVER)); + subscribe("mesh/heartbeat", bind(&MeshApp::messageHandler, this, _1)); } Sprocket* activate(Scheduler* scheduler, Network* network) { @@ -37,18 +39,25 @@ class MeshApp : public MeshSprocket { return this; } using MeshSprocket::activate; + void messageHandler(String msg){ + Serial.println(String("MeshApp: ") + msg); + } + void heartbeat(MeshNet* network){ - MeshMessage msg; // = { "wirelos", "broadcast", "local", "alive", 0, }; + SprocketMessage msg; // = { "wirelos", "broadcast", "local", "alive", 0, }; msg.domain = "wirelos"; msg.to = "broadcast"; - msg.msg = "alive"; - msg.type = MeshMessage::APP; + msg.payload = "alive"; + msg.topic = "mesh/heartbeat"; + msg.type = SprocketMessage::APP; String msgStr = msg.toJsonString(); - network->mesh.sendBroadcast(msgStr/* , true */); - } - void onMessage( uint32_t from, String &msg ) { - Serial.printf("MeshApp onMessage: received from %u msg=%s\n", from, msg.c_str()); + network->mesh.sendBroadcast(msgStr, true); + //String mMsg = String("hoi"); + //publish("mediatorMsg", "hi mediator"); } + //void onMessage( uint32_t from, String &msg ) { + // Serial.printf("MeshApp onMessage: received from %u msg=%s\n", from, msg.c_str()); + //} }; #endif \ No newline at end of file diff --git a/src/examples/ota/SprocketOTA.cpp b/src/examples/ota/SprocketOTA.cpp deleted file mode 100644 index 8d110ab..0000000 --- a/src/examples/ota/SprocketOTA.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __MESH_APP__ -#define __MESH_APP__ - -#define DEBUG_ESP_OTA -#include -#include -#include "config.h" - -using namespace std; -using namespace std::placeholders; - -// MeshSprocket base class integrates OTA plugin by default -class SprocketOTA : public MeshSprocket { - public: - SprocketOTA(SprocketConfig cfg, OtaConfig otaCfg) : MeshSprocket(cfg, otaCfg) {} - - Sprocket* activate(Scheduler* scheduler, Network* network) { - // call parent method that enables dispatching and plugins - MeshSprocket::activate(scheduler, network); - return this; - } using MeshSprocket::activate; - - void onMessage( uint32_t from, String &msg ) { - Serial.printf("SprocketOTA onMessage: received from %u msg=%s\n", from, msg.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/examples/ota/config.h b/src/examples/ota/config.h deleted file mode 100644 index 86185cf..0000000 --- a/src/examples/ota/config.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef __OTA_NODE__CONFIG__ -#define __OTA_NODE__CONFIG__ - -// Scheduler config -#define _TASK_SLEEP_ON_IDLE_RUN -#define _TASK_STD_FUNCTION - -// Chip config -#define SERIAL_BAUD_RATE 115200 -#define STARTUP_DELAY 3000 - -// Mesh config -#define STATION_MODE 1 // must be 1 => connected to a normal network to receive update -#define WIFI_CHANNEL 11 -#define MESH_PORT 5555 -#define MESH_PREFIX "whateverYouLike" -#define MESH_PASSWORD "somethingSneaky" -#define STATION_SSID "Th1ngs4P" -#define STATION_PASSWORD "th3r31sn0sp00n" -#define HOSTNAME "ota-node" -#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION - -// OTA config -#define OTA_PORT 8266 -#define OTA_PASSWORD "f4ncy" - -#endif \ No newline at end of file diff --git a/src/examples/ota/main.cpp b/src/examples/ota/main.cpp deleted file mode 100644 index 222a804..0000000 --- a/src/examples/ota/main.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include "config.h" -#include "MeshNet.h" -#include "SprocketOTA.cpp" - -MeshNet net({ - STATION_MODE, WIFI_CHANNEL, - MESH_PORT, MESH_PREFIX, MESH_PASSWORD, - STATION_SSID, STATION_PASSWORD, HOSTNAME, - MESH_DEBUG_TYPES -}); - -SprocketOTA sprocket( - { STARTUP_DELAY, SERIAL_BAUD_RATE }, - {OTA_PORT, OTA_PASSWORD} -); - -void setup() { - sprocket.join(net); -} - -void loop() { - sprocket.loop(); - yield(); -} \ No newline at end of file diff --git a/src/plugins/MeshManPlugin.cpp b/src/plugins/MeshManPlugin.cpp index 8b7b1f3..324907f 100644 --- a/src/plugins/MeshManPlugin.cpp +++ b/src/plugins/MeshManPlugin.cpp @@ -27,6 +27,11 @@ class MeshManPlugin : public Plugin { server->on("/mesh", HTTP_POST, std::bind(&MeshManPlugin::sendMsg, this, std::placeholders::_1)); server->on("/mesh/nodeId", HTTP_GET, std::bind(&MeshManPlugin::getNodeId, this, std::placeholders::_1)); server->on("/mesh/broadcast", HTTP_POST, std::bind(&MeshManPlugin::broadcast, this, std::placeholders::_1)); + + subscribe("mesh/heartbeat", std::bind(&MeshManPlugin::gotHeartbeat, this, std::placeholders::_1)); + } + void gotHeartbeat(String msg){ + Serial.println(String("MeshManPlugin / Heartbeat: ") + msg); } void getMeshConnections(AsyncWebServerRequest *request) { request->send(200, "text/plain", net->mesh.subConnectionJson()); diff --git a/src/plugins/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp index affde58..aa49999 100644 --- a/src/plugins/OtaTcpPlugin.cpp +++ b/src/plugins/OtaTcpPlugin.cpp @@ -33,8 +33,8 @@ class OtaTcpPlugin : public Plugin { ArduinoOTA.begin(); otaTask.enable(); } - void onMessage(MeshMessage msg) { - if(msg.type == MeshMessage::OTA){ + void onMessage(SprocketMessage msg) { + if(msg.type == SprocketMessage::OTA){ Serial.println("OTA msg received"); WiFi.disconnect(); net->mesh.stop();