commit accd942c3433ec3ad2d5a3452bffedad4e70de60 Author: Patrick Balsiger Date: Thu Nov 15 11:36:19 2018 +0100 basic mqtt plugin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e2a754 --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +# platformio / IDE +.pioenvs +.piolibdeps +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json + +# build stuff +/data/ +dist/ + +# sprocket stuff +/config/ +/firmware +.clang_complete +.gcc-flags.json +.pioenvs +.piolibdeps diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..52f6745 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,38 @@ +image: python:2.7-stretch + +# This folder is cached between builds +# http://docs.gitlab.com/ce/ci/yaml/README.html#cache +cache: + paths: + - firmware + +stages: + - test + - build + +before_script: + - "pip install -U platformio" + +#firmware-test: +# stage: test +# script: "pio ci --board=esp12e" +# variables: {PLATFORMIO_CI_SRC: "test.cpp"} + +firmware-build: + stage: build + image: python:2.7-stretch + script: + - pio run -t clean + - pio run + artifacts: + paths: + - .pioenvs/*/firmware.* + - .pioenvs/*/spiffs.bin +#pack: +# stage: deploy +# script: +# - yarn install +# - yarn pack +# artifacts: +# paths: +# - ./*.tgz diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9443843 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < http://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < http://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < http://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choice one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to by used as a library with examples +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8281e64 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..13f18c7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "terminal.integrated.env.linux": { + "PATH": "/home/master/.platformio/penv/bin:/home/master/.platformio/penv:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl", + "PLATFORMIO_CALLER": "vscode" + } +} \ No newline at end of file diff --git a/lib/readme.txt b/lib/readme.txt new file mode 100644 index 0000000..131f1bf --- /dev/null +++ b/lib/readme.txt @@ -0,0 +1,41 @@ + +This directory is intended for the project specific (private) libraries. +PlatformIO will compile them to static libraries and link to executable file. + +The source code of each library should be placed in separate directory, like +"lib/private_lib/[here are source files]". + +For example, see how can be organized `Foo` and `Bar` libraries: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- readme.txt --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Then in `src/main.c` you should use: + +#include +#include + +// rest H/C/CPP code + +PlatformIO will find your libraries automatically, configure preprocessor's +include paths and build them. + +More information about PlatformIO Library Dependency Finder +- http://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..8384dc6 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,30 @@ +[platformio] +env_default = basic + +[common] +framework = arduino +platform = espressif8266 +board = esp12e +upload_speed = 921600 +monitor_baud = 115200 +lib_deps = + Hash + TaskScheduler + SPIFFS + ArduinoJson + ArduinoOTA + ESPAsyncTCP + ESP8266mDNS + ESP Async WebServer + painlessMesh + https://gitlab.com/wirelos/sprocket-lib.git#develop + +[env:basic] +src_filter = +<*> - + +platform = ${common.platform} +board = ${common.board} +upload_speed = ${common.upload_speed} +monitor_baud = ${common.monitor_baud} +framework = ${common.framework} +lib_deps = ${common.lib_deps} + PubSubClient \ No newline at end of file diff --git a/src/MqttPlugin.cpp b/src/MqttPlugin.cpp new file mode 100644 index 0000000..09fc177 --- /dev/null +++ b/src/MqttPlugin.cpp @@ -0,0 +1,150 @@ +#ifndef __MQTT_PLUGIN__ +#define __MQTT_PLUGIN__ + +#define _TASK_SLEEP_ON_IDLE_RUN +#define _TASK_STD_FUNCTION + +#include +#include +#include + +using namespace std; +using namespace std::placeholders; + +struct MqttConfig +{ + const char *clientName; + const char *brokerHost; + int brokerPort; + const char *inTopicRoot; + const char *outTopicRoot; +}; + +class MqttPlugin : public Plugin +{ + public: + PubSubClient *client; + WiFiClient wifiClient; + Task connectTask; + Task processTask; + MqttConfig mqttConfig; + vector topics; + + MqttPlugin(MqttConfig cfg) + { + mqttConfig = cfg; + //subscribe("mqtt/out", bind(&MqttPlugin::mqttPublish, this, _1)); + } + + void activate(Scheduler *scheduler) + { + client = new PubSubClient(mqttConfig.brokerHost, mqttConfig.brokerPort, bind(&MqttPlugin::downstreamHandler, this, _1, _2, _3), wifiClient); + enableConnectTask(scheduler); + enableProcessTask(scheduler); + Serial.println("MQTT plugin activated"); // FIXME use PRINT_MSG + } + + private: + void enableConnectTask(Scheduler *scheduler) + { + connectTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MqttPlugin::connect, this)); + scheduler->addTask(connectTask); + connectTask.enable(); + } + + void enableProcessTask(Scheduler *scheduler) + { + processTask.set(TASK_MILLISECOND * 5, TASK_FOREVER, bind(&MqttPlugin::process, this)); + scheduler->addTask(processTask); + processTask.enable(); + } + + void process() + { + client->loop(); + } + + void connect() + { + if (!client->connected()) + { + Serial.println("MQTT try connect"); + if (client->connect(mqttConfig.clientName)) + { + Serial.println("MQTT connected"); + upstreamHandler("lobby", "join"); + // overkill to subscribe to all...? + for (auto const &localSub : mediator->subscriptions) + { + subscribe(localSub.first.c_str(), bind(&MqttPlugin::upstreamHandler, this, localSub.first.c_str(), _1)); + client->subscribe( + (String(mqttConfig.inTopicRoot) + String(localSub.first.c_str())) + .c_str()); + } + } + } + else + { + publish("local/topic", "Free heap: " + String(ESP.getFreeHeap())); + } + } + + void mqttPublish(String msg) + { + client->publish(mqttConfig.outTopicRoot, msg.c_str()); + } + + void upstreamHandler(String topic, String msg) + { + Serial.println(msg); + // publish message on remote queue + // TODO rootTopic + topic? + client->publish((String(mqttConfig.outTopicRoot) + topic).c_str(), msg.c_str()); + } + + void downstreamHandler(char *topic, uint8_t *payload, unsigned int length) + { + char *cleanPayload = (char *)malloc(length + 1); + payload[length] = '\0'; + memcpy(cleanPayload, payload, length + 1); + String msg = String(cleanPayload); + free(cleanPayload); + + Serial.println("MQTT topic " + String(topic)); + Serial.println("MQTT msg " + msg); + + int topicRootLength = String(mqttConfig.inTopicRoot).length(); + String localTopic = String(topic).substring(topicRootLength); + Serial.println("Local topic:" + localTopic); + + // publish message to local subscribers on device + publish(localTopic, msg); + } + /* void mqttCallback(char* topic, uint8_t* payload, unsigned int length) { + char* cleanPayload = (char*)malloc(length+1); + payload[length] = '\0'; + memcpy(cleanPayload, payload, length+1); + String msg = String(cleanPayload); + free(cleanPayload); + + int topicRootLength = String(mqttConfig.topicRoot).length(); + String targetStr = String(topic).substring(topicRootLength + 4); + + if(targetStr == "gateway"){ + if(msg == "getNodes") { + client->publish(MQTT_TOPIC_FROM_GATEWAY, net->mesh.subConnectionJson().c_str()); + } + } else if(targetStr == "broadcast") { + net->mesh.sendBroadcast(msg); + } else { + uint32_t target = strtoul(targetStr.c_str(), NULL, 10); + if(net->mesh.isConnected(target)){ + net->mesh.sendSingle(target, msg); + } else { + client->publish(MQTT_TOPIC_FROM_GATEWAY, "Client not connected!"); + } + } + } */ +}; + +#endif \ No newline at end of file diff --git a/src/examples/basic/config.h b/src/examples/basic/config.h new file mode 100644 index 0000000..c7bfad0 --- /dev/null +++ b/src/examples/basic/config.h @@ -0,0 +1,36 @@ +#ifndef __DEVICE_CONFIG__ +#define __DEVICE_CONFIG__ + +// Scheduler config +#define _TASK_SLEEP_ON_IDLE_RUN +#define _TASK_STD_FUNCTION +#define _TASK_PRIORITY + +// Chip config +#define SPROCKET_TYPE "SPROCKET" +#define SERIAL_BAUD_RATE 115200 +#define STARTUP_DELAY 1000 + +// network config +#define SPROCKET_MODE 1 +#define WIFI_CHANNEL 11 +#define MESH_PORT 5555 +#define AP_SSID "sprocket" +#define AP_PASSWORD "th3r31sn0sp00n" +#define MESH_PREFIX "sprocket-mesh" +#define MESH_PASSWORD "th3r31sn0sp00n" +#define STATION_SSID "MyAP" +#define STATION_PASSWORD "th3r31sn0sp00n" +#define HOSTNAME "sprocket" +#define CONNECT_TIMEOUT 10000 +#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION +//#define MESH_DEBUG_TYPES ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE + +// WebServer +#define WEB_CONTEXT_PATH "/" +#define WEB_DOC_ROOT "/www" +#define WEB_DEFAULT_FILE "index.html" +#define WEB_PORT 80 + + +#endif \ No newline at end of file diff --git a/src/examples/basic/main.cpp b/src/examples/basic/main.cpp new file mode 100644 index 0000000..e71c210 --- /dev/null +++ b/src/examples/basic/main.cpp @@ -0,0 +1,36 @@ +#include "config.h" +#include "WiFiNet.h" +#include "Sprocket.h" +#include "MqttPlugin.cpp" + +WiFiNet *network; +Sprocket *sprocket; + +void setup() +{ + sprocket = new Sprocket( + {STARTUP_DELAY, SERIAL_BAUD_RATE}); + sprocket->subscribe("local/topic", [](String msg){ + Serial.println("Received: " + msg); + }); + sprocket->addPlugin( + new MqttPlugin({"sprocket", "192.168.1.2", 1883, "wirelos/mqttSprocket-in/", "wirelos/mqttSprocket-out/"})); + + network = new WiFiNet( + SPROCKET_MODE, + STATION_SSID, + STATION_PASSWORD, + AP_SSID, + AP_PASSWORD, + HOSTNAME, + CONNECT_TIMEOUT); + network->connect(); + + sprocket->activate(); +} + +void loop() +{ + sprocket->loop(); + yield(); +} \ No newline at end of file