merge develop into main

This commit is contained in:
2025-08-14 19:57:36 +02:00
49 changed files with 1251 additions and 1065 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,8 @@
.pio
.pioenvs
.piolibdeps
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/launch.json
tools/node_modules
data/config.json

View File

@@ -1,15 +1,14 @@
image: python:3.12.10
stages:
- build
cache:
paths:
- .piolibdeps
before_script:
- "pip install -U platformio"
build-examples:
examples:
stage: build
image: registry.gitlab.com/wirelos/contraption-pipeline/platformio:v1
image: python:3.12.10
script:
- platformio lib --global install painlessMesh ArduinoJson TaskScheduler PubSubClient ESPAsyncTCP AsyncTCP "ESP Async WebServer"
- platformio ci --lib="." --board=esp12e src/examples/basic/
- platformio ci --lib="." --board=esp12e src/examples/mesh/
- platformio ci --lib="." --board=esp12e src/examples/mqttBridge/
- pio run --target clean
- pio run --environment basic

View File

@@ -36,6 +36,7 @@
"type_traits": "cpp",
"tuple": "cpp",
"typeinfo": "cpp",
"utility": "cpp"
"utility": "cpp",
"bitset": "cpp"
}
}

View File

@@ -1 +1,20 @@
# Sprocket-Core
# Sprocket-Core
A lightweight Arduino framework for event driven programming.
## Concepts
... topic based event channel / pubsub-pattern
... plugin system
## Sprocket Lifecycle
TODO
# Useful commands
```sh
# erase flash
esptool --port /dev/ttyUSB0 erase_flash
# OTA
~/.platformio/packages/tool-espotapy/espota.py -i <espIP> -p 8266 -a <authPW> -f .pioenvs/ota/firmware.bin
```

13
data/config.json Normal file
View File

@@ -0,0 +1,13 @@
{
"stationMode": 0,
"channel": 11,
"meshPort": 5555,
"meshSSID": "whateverYouLike",
"meshPassword": "somethingSneaky",
"apSSID": "MyAP",
"apPassword": "myApPwd",
"stationSSID": "tErAx1d",
"stationPassword": "ramalamadingdong",
"hostname": "dbuggy",
"connectTimeout": 10000
}

11
data/example.config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"stationMode": 0,
"channel": 11,
"meshPort": 5555,
"meshSSID": "WibblyWobbly",
"meshPassword": "th3r31sn0sp00n",
"stationSSID": "MyAP",
"stationPassword": "myApPassword",
"hostname": "mesh-node",
"connectTimeout": 10000
}

22
data/www/index.html Normal file
View File

@@ -0,0 +1,22 @@
<html>
<head>
<script src="jquery-3.3.1.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>Sprocket Config</h1>
<button class="js-restart">Restart</button>
<br>
<br>
<form action="/config" method="post">
<textarea name="config" class="js-config" rows="20" cols="50"></textarea>
<br>
<input type="submit" value="Submit">
</form>
</body>
</html>

2
data/www/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

25
data/www/script.js Normal file
View File

@@ -0,0 +1,25 @@
// mesh topology:
const exampleMesh = [{
"nodeId": 757307929, "subs": [
{ "nodeId": 2138241514, "subs": [] },
{ "nodeId": 757307466, "subs": [] }
]
}, {
"nodeId": 757308244, "subs": [
{ "nodeId": 3154639577, "subs": [] }
]
}, {
"nodeId": 3954439712, "subs": []
}];
$(() => {
// load config
$.get("/config.json", (data) => {
$('.js-config').val(JSON.stringify(data, null, 4));
});
// add handlers
$('.js-restart').click(() => {
$.post('/restart');
alert('restarting...');
});
});

39
include/readme.txt Normal file
View File

@@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View File

@@ -17,9 +17,13 @@
"platforms": "espressif8266",
"examples": "examples/*",
"export": {
"include":
[
"lib"
],
"exclude":
[
"firmware/"
"src/examples/"
]
}
}

View File

@@ -8,81 +8,35 @@
; Please visit documentation for the other options and examples
; http://docs.platformio.org/page/projectconf.html
[platformio]
env_default = illucat
;[platformio]
;env_default = basic
[common]
framework = arduino
platform = espressif8266
platform = platformio/espressif8266@2.6.3
board = esp12e
upload_speed = 921600
monitor_baud = 115200
lib_deps =
Hash
ESP Async WebServer
ESPAsyncTCP
TaskScheduler
painlessMesh
[env:build]
src_filter = +<*> -<examples/>
#platform = https://github.com/platformio/platform-espressif8266.git#feature/stage
#platform = https://github.com/platformio/platform-espressif8266.git
#platform = espressif8266@~1.6.0
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
arkhipenko/TaskScheduler@^3.8.5
ArduinoJson@5.13.4
#build_flags = -DLED_PIN=2 -g
;upload_port = /dev/ttyUSB0
;upload_port = 192.168.1.168
[env:basic]
src_filter = +<*> -<examples/> -<firmware/> +<examples/basic/>
platform = ${common.platform}
board = ${common.board}
src_filter = +<*> -<examples/> +<examples/basic/>
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
[env:mesh]
src_filter = +<*> -<examples/> -<firmware/> +<examples/mesh/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
[env:meshMqttBridge]
src_filter = +<*> -<examples/> -<firmware/> +<examples/mqttBridge/>
platform = espressif8266
board = esp12e
[env:basic_esp32]
platform = espressif32
board = esp32dev
framework = ${common.framework}
build_flags = -std=c++14
src_filter = +<*> -<examples/> +<examples/basic/>
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
PubSubClient
[env:illucat]
src_filter = +<*> -<examples/> -<firmware/> +<examples/illucat/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}
Adafruit NeoPixel
[env:button]
src_filter = +<*> -<examples/> -<firmware/> +<examples/button/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}

31
src/EventChannel.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef __SPROCKET_EVENT_CHANNEL__
#define __SPROCKET_EVENT_CHANNEL__
#include <Arduino.h>
#include <ArduinoJson.h>
#include <list>
#include <functional>
#include <map>
#include <vector>
using namespace std;
typedef std::function<void(String msg)> subscriptionHandler_t;
class EventChannel {
public:
std::map<std::string, vector<subscriptionHandler_t>> 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

127
src/JsonStruct.h Normal file
View File

@@ -0,0 +1,127 @@
#ifndef __JSON_STRUCT__
#define __JSON_STRUCT__
#include <ArduinoJson.h>
#include <FS.h>
#ifdef ESP32
#include "SPIFFS.h"
#endif
struct JsonStruct
{
int valid = 0;
// ------------------------------------------------------------------------------------------
virtual void mapJsonObject(JsonObject &json);
virtual void fromJsonObject(JsonObject &json);
virtual int verifyJsonObject(JsonObject &json)
{
return json.success();
};
String toJsonString()
{
DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300));
JsonObject &root = jsonBuffer.createObject();
mapJsonObject(root);
String jsonString;
root.printTo(jsonString);
return jsonString;
}
char* toCharArray()
{
DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300));
JsonObject &root = jsonBuffer.createObject();
mapJsonObject(root);
size_t jsonSize = root.measureLength() + 1;
char* jsonChar = new char[jsonSize];
root.printTo(jsonChar, jsonSize);
return jsonChar;
}
String getAttrFromJson(JsonObject &json, const char *attr, String defautValue = "")
{
if (json.containsKey(String(attr)))
{
const char *value = json[attr];
return String(value);
}
return defautValue;
}
const char *getAttr(JsonObject &json, const char *attr, const char *defautValue = "")
{
if (json.containsKey(String(attr)))
{
const char *value = json[attr];
return value;
}
return defautValue;
}
String getStrAttrFromJson(JsonObject &json, const char *attr, String defautValue = "")
{
if (json.containsKey(String(attr)))
{
const char *value = json[attr];
return String(value);
}
return defautValue;
}
int getIntAttrFromJson(JsonObject &json, const char *attr, int defautValue = 0)
{
if (json.containsKey(attr))
{
return json[attr];
}
return defautValue;
}
// Map a json object to this struct.
// Parse a json string and map parsed object
void fromJsonString(String &str)
{
DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300));
JsonObject &json = jsonBuffer.parseObject(str);
valid = verifyJsonObject(json);
if (valid)
{
fromJsonObject(json);
}
};
void fromFile(const char *path)
{
File configFile = SPIFFS.open(path, "r");
String cfgFileStr = configFile.readString();
DynamicJsonBuffer jsonBuffer;
JsonObject &json = jsonBuffer.parseObject(cfgFileStr);
valid = verifyJsonObject(json);
if (configFile)
{
fromJsonObject(json);
}
if (!valid)
{
Serial.println("ERROR: read failed for " + String(path));
}
configFile.close();
}
void saveFile(const char *path)
{
File configFile = SPIFFS.open(path, "w");
DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300));
JsonObject &json = jsonBuffer.createObject();
valid = configFile && verifyJsonObject(json);
if (valid)
{
mapJsonObject(json);
json.printTo(configFile);
}
}
};
#endif

View File

@@ -1,53 +0,0 @@
#include "MeshNet.h"
MeshNet::MeshNet(MeshConfig cfg) : Network() {
config = cfg;
}
Network* MeshNet::init(){
Serial.println("init mesh");
//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
mesh.setDebugMsgTypes( config.debugTypes );
mesh.init( config.meshSSID, config.meshPassword, scheduler, config.meshPort, WIFI_AP_STA, config.channel );
//mesh.onReceive(bind(&MeshNet::receivedCallback,this, _1, _2));
mesh.onNewConnection(bind(&MeshNet::newConnectionCallback, this, _1));
mesh.onChangedConnections(bind(&MeshNet::changedConnectionCallback, this));
mesh.onNodeTimeAdjusted(bind(&MeshNet::nodeTimeAdjustedCallback, this, _1));
if(config.stationMode){
Serial.println("connect station");
mesh.stationManual(config.stationSSID, config.stationPassword);
mesh.setHostname(config.hostname);
}
return this;
}
void MeshNet::sendTo(uint32_t target, String msg){
mesh.sendSingle(target, msg);
}
void MeshNet::broadcast(String msg){
mesh.sendBroadcast(msg);
}
void MeshNet::update(){
// only needed when no scheduler was passed to mesh.init
mesh.update();
}
void MeshNet::receivedCallback( uint32_t from, String &msg ) {
Serial.printf("--> Received from %u msg=%s\n", from, msg.c_str());
}
void MeshNet::newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> New Connection, nodeId = %u\n", nodeId);
}
void MeshNet::changedConnectionCallback() {
Serial.printf("--> Changed connections %s\n",mesh.subConnectionJson().c_str());
}
void MeshNet::nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("--> Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

View File

@@ -1,40 +0,0 @@
#ifndef __MESHNET_H__
#define __MESHNET_H__
#include <painlessMesh.h>
#include <WiFiClient.h>
#include "Network.h"
using namespace std;
using namespace std::placeholders;
struct MeshConfig {
int stationMode;
int channel;
int meshPort;
const char* meshSSID;
const char* meshPassword;
const char* stationSSID;
const char* stationPassword;
const char* hostname;
uint16_t debugTypes;
};
class MeshNet : public Network {
public:
painlessMesh mesh;
MeshConfig config;
MeshNet(MeshConfig cfg);
Network* init();
void broadcast(String msg);
void sendTo(uint32_t target, String msg);
void update(); // only needed when no scheduler was passed to mesh.init
void receivedCallback( uint32_t from, String &msg );
void newConnectionCallback(uint32_t nodeId);
void changedConnectionCallback();
void nodeTimeAdjustedCallback(int32_t offset);
};
#endif

View File

@@ -3,19 +3,23 @@
#include <Arduino.h>
#include <TaskSchedulerDeclarations.h>
#include <functional>
typedef std::function<void(uint32_t from, String &msg)> msgReceived_cb;
class Network {
public:
uint32_t id = 0;
Network(){}
Scheduler* scheduler;
virtual Network* init() { return this; };
virtual Network* init(Scheduler* s) { scheduler = s; return init(); };
virtual Network* connect() { return this; };
virtual int connect() { return 0; };
virtual int connectStation() { return 0; };
virtual int isConnected(){ return 0; };
virtual void update() {};
virtual void broadcast(String msg){
Serial.println("no-broadcast");
};
virtual void broadcast(String msg, bool self = false){};
virtual void sendTo(uint32_t target, String msg) {};
virtual void onReceive(std::function<void(uint32_t from, String &msg)>) {};
Network* setScheduler(Scheduler* s) {
scheduler = s;
return this;

32
src/Plugin.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef __SPROCKET_PLUGIN__
#define __SPROCKET_PLUGIN__
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
#include <TaskSchedulerDeclarations.h>
#include <Network.h>
#include <SprocketMessage.h>
#include <EventChannel.h>
class Plugin {
public:
EventChannel* eventChannel;
virtual void activate(Scheduler*);
virtual void enable(){}
virtual void disable(){}
virtual void onMessage(SprocketMessage msg){}
Plugin* mount(EventChannel* ec) {
eventChannel = ec;
return this;
}
void subscribe(String topic, subscriptionHandler_t handler){
eventChannel->subscribe(topic, handler);
}
void publish(String topic, String str){
eventChannel->publish(topic, str);
}
};
#endif

View File

@@ -13,25 +13,40 @@ Sprocket* Sprocket::init(SprocketConfig cfg){
delay(cfg.startupDelay);
Serial.begin(cfg.serialBaudRate);
SPIFFS.begin();
scheduler = new Scheduler();
return this;
}
Sprocket* Sprocket::activate() {
return activate(&scheduler);
}
Sprocket* Sprocket::join(Network& net){
Serial.println("join network");
net.init(&scheduler);
net.connect();
return activate(&scheduler, &net);
activatePlugins(scheduler);
return activate(scheduler);
}
Sprocket* Sprocket::addTask(Task& tsk){
scheduler.addTask(tsk);
scheduler->addTask(tsk);
tsk.enable();
return this;
}
void Sprocket::loop(){
scheduler.execute();
}
scheduler->execute();
}
void Sprocket::dispatch( uint32_t from, String &msg ) {
currentMessage.fromJsonString(msg);
if(currentMessage.valid){
currentMessage.from = from;
publish(currentMessage.topic, currentMessage.payload);
}
}
void Sprocket::addPlugin(Plugin* p){
p->mount(this);
plugins.reserve(1);
plugins.push_back(p);
}
void Sprocket::activatePlugins(Scheduler* scheduler){
for(Plugin* p : plugins){
p->activate(scheduler);
}
}

View File

@@ -1,30 +1,46 @@
#ifndef __SPROCKET_H__
#define __SPROCKET_H__
#include <TaskSchedulerDeclarations.h>
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
#include <Arduino.h>
#include <FS.h>
#include <TaskSchedulerDeclarations.h>
#include <vector>
#include "FS.h"
#ifdef ESP32
#include "SPIFFS.h"
#endif
#include "Network.h"
#include "SprocketConfig.h"
#include "Plugin.h"
struct SprocketConfig {
int startupDelay;
int serialBaudRate;
};
using namespace std;
using namespace std::placeholders;
class Sprocket {
class Sprocket : public EventChannel {
protected:
Scheduler scheduler;
// TODO move scheduler out of Sprocket
// => see difference between standalone and mesh sprochet usage of scheduler
Scheduler* scheduler;
Network network;
private:
SprocketMessage currentMessage;
public:
SprocketConfig config;
std::vector<Plugin*> plugins;
Sprocket();
Sprocket(SprocketConfig);
Sprocket* init(SprocketConfig);
Sprocket* join(Network&);
Sprocket* addTask(Task&);
virtual void loop();
virtual Sprocket* activate();
virtual Sprocket* activate(Scheduler*) { return this; }
virtual Sprocket* activate(Scheduler*, Network*) { return this; }
virtual Sprocket* activate(Scheduler*) { return this; };
virtual void dispatch( uint32_t from, String &msg );
void addPlugin(Plugin* p);
void activatePlugins(Scheduler* scheduler);
};
#endif

9
src/SprocketConfig.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef __SPROCKET_CONFIG__
#define __SPROCKET_CONFIG__
struct SprocketConfig {
int startupDelay;
int serialBaudRate;
};
#endif

63
src/SprocketMessage.h Normal file
View File

@@ -0,0 +1,63 @@
#ifndef __SPROCKET_MESSAGE__
#define __SPROCKET_MESSAGE__
#include <Arduino.h>
#include <ArduinoJson.h>
#include <JsonStruct.h>
#define JSON_DOMAIN "domain"
#define JSON_FROM "from"
#define JSON_TO "target"
#define JSON_PAYLOAD "payload"
#define JSON_TYPE "type"
#define JSON_TOPIC "topic"
#define JSON_BROADCAST "broadcast"
struct SprocketMessage : public JsonStruct {
String domain;
String to;
String from;
String payload;
int broadcast;
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;
root[JSON_BROADCAST] = broadcast;
}
// 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);
broadcast = getIntAttrFromJson(json, JSON_BROADCAST);
type = (SprocketMessageType) getIntAttrFromJson(json, JSON_TYPE);
valid = 1;
};
};
#endif

15
src/TaskScheduler.cpp Normal file
View File

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

View File

@@ -1,8 +1,6 @@
#ifndef __EXAMPLE_APP__
#define __EXAMPLE_APP__
//#include "Sprocket.h"
#include <ESPAsyncWebServer.h>
#include <Sprocket.h>
using namespace std;
@@ -12,23 +10,17 @@ class ExampleApp : public Sprocket {
public:
Task someTask;
ExampleApp(SprocketConfig cfg) : Sprocket(cfg) {
Serial.println("joo");
Serial.println("init");
}
Sprocket* activate(Scheduler* scheduler) {
Serial.println("activate");
someTask.set(TASK_SECOND, TASK_FOREVER, [this](){
someTask.set(TASK_SECOND, TASK_FOREVER, [](){
Serial.println("do stuff in task");
});
scheduler->addTask(someTask);
someTask.enable();
return this;
} using Sprocket::activate;
void server(AsyncWebServer* srv) {
srv->on("/ping", HTTP_POST, bind(&ExampleApp::handlePingRequest, this, _1));
}
void handlePingRequest(AsyncWebServerRequest *request) {
Serial.println("pinged");
request->send(200, "text/html", "pong");
}
};
#endif

View File

@@ -1,9 +1,10 @@
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define SERIAL_BAUD_RATE 115200
#define STARTUP_DELAY 3000
#include "ExampleApp.h"
#include "ExampleApp.cpp"
ExampleApp sprocket({ STARTUP_DELAY, SERIAL_BAUD_RATE });

View File

@@ -1,51 +0,0 @@
#ifndef __MESH_APP__
#define __MESH_APP__
#include <painlessMesh.h>
#include <Sprocket.h>
#include <MeshNet.h>
using namespace std;
using namespace std::placeholders;
class Button : public Sprocket {
public:
Task btnTask;
MeshNet* net;
int pin;
int warnlevel = 0;
int cycleEnd = 5;
Button(SprocketConfig cfg) : Sprocket(cfg) {}
Sprocket* activate(Scheduler* scheduler, Network* network) {
pin = D2;
pinMode(pin, INPUT_PULLUP);
net = static_cast<MeshNet*>(network);
net->mesh.onReceive(bind(&Button::receivedCallback,this, _1, _2));
btnTask.set(TASK_MILLISECOND * 250, TASK_FOREVER,
bind(&Button::readPin, this, net));
scheduler->addTask(btnTask);
btnTask.enable();
} using Sprocket::activate;
void readPin(MeshNet* network){
if(digitalRead(pin)){
Serial.println("warnlevel: " + String(warnlevel));
warnlevel++;
if(warnlevel == cycleEnd) warnlevel = 0;
network->broadcast(String(warnlevel));
}
}
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("button: Received from %u msg=%s\n", from, msg.c_str());
// respond in receive callback can cause an endless loop when all nodes run the same firmware
//String foo = String("cheerz back to ") + String(from);
//net->broadcast(foo);
}
void loop() {
net->update();
scheduler.execute();
}
};
#endif

View File

@@ -1,25 +0,0 @@
#ifndef __MESH_CONFIG__
#define __MESH_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 0
#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 "mesh-node"
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
#define BUTTON_PIN D2
#endif

View File

@@ -1,20 +0,0 @@
#include "config.h"
#include "MeshNet.h"
#include "Button.h"
MeshNet net({
STATION_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
Button sprocket({ STARTUP_DELAY, SERIAL_BAUD_RATE });
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

View File

@@ -1,72 +0,0 @@
#ifndef __MESH_APP__
#define __MESH_APP__
#include <painlessMesh.h>
#include <Sprocket.h>
#include <MeshNet.h>
#include "NeoPattern.h"
using namespace std;
using namespace std::placeholders;
#define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0])
enum PIXEL_MODES {BLACK = 0x000000, RED = 0xFF0000, GREEN = 0x00FF00, YELLOW = 0xffff00, BLUE = 0x0000FF, ORANGE = 0xffa500};
int CAT_MODES[] = {BLACK, RED, GREEN, YELLOW, BLUE, ORANGE};
struct NeoPixelConfig {
int pin;
int length;
int mode;
};
class Illucat : public Sprocket {
public:
MeshNet* net;
NeoPattern* pixels;
int currentMode;
Illucat(SprocketConfig cfg, NeoPixelConfig pixelCfg) : Sprocket(cfg) {
pixels = new NeoPattern(pixelCfg.length, pixelCfg.pin, NEO_GRB + NEO_KHZ800, [](int pixels){});
pixels->begin();
pixels->setBrightness(64);
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
net = static_cast<MeshNet*>(network);
net->mesh.onReceive(bind(&Illucat::messageReceived,this, _1, _2));
// TODO default rainbow task
} using Sprocket::activate;
void messageReceived( uint32_t from, String &msg ) {
Serial.printf("illucat: received from %u msg=%s\n", from, msg.c_str());
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(msg);
if (!root.success()) {
Serial.println("parseObject() failed");
return;
}
changeMode(root["severity"]);
/* if(root["action"] == "pressed") {
}
if(root["action"] == "dial") {
changeMode("0");
} */
}
void changeMode(const char *mode){
currentMode = atoi(mode);
Serial.println(currentMode);
pixels->ColorSet(CAT_MODES[currentMode]);
pixels->show();
}
void setHexColor(const char *hex){
int r, g, b;
sscanf(hex, "%02x%02x%02x", &r, &g, &b);
pixels->ColorSet(pixels->Color(r,g,b));
}
void loop() {
net->update();
scheduler.execute();
}
};
#endif

View File

@@ -1,304 +0,0 @@
#ifndef __NeoPattern_H_INCLUDED__
#define __NeoPattern_H_INCLUDED__
#include <Adafruit_NeoPixel.h>
/**
* NeoPattern by Bill Earl
* https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
*/
// Pattern types supported:
enum pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum direction { FORWARD, REVERSE };
// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPattern : public Adafruit_NeoPixel
{
public:
// Member Variables:
pattern ActivePattern; // which pattern is running
direction Direction; // direction to run the pattern
unsigned long Interval; // milliseconds between updates
unsigned long lastUpdate; // last update of position
uint32_t Color1, Color2; // What colors are in use
uint16_t TotalSteps; // total number of steps in the pattern
uint16_t Index; // current step within the pattern
uint16_t completed = 0;
void (*OnComplete)(int); // Callback on completion of pattern
// Constructor - calls base-class constructor to initialize strip
NeoPattern(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)(int))
:Adafruit_NeoPixel(pixels, pin, type)
{
OnComplete = callback;
}
// Update the pattern
void Update()
{
if((millis() - lastUpdate) > Interval) // time to update
{
lastUpdate = millis();
switch(ActivePattern)
{
case RAINBOW_CYCLE:
RainbowCycleUpdate();
break;
case THEATER_CHASE:
TheaterChaseUpdate();
break;
case COLOR_WIPE:
ColorWipeUpdate();
break;
case SCANNER:
ScannerUpdate();
break;
case FADE:
FadeUpdate();
break;
default:
break;
}
}
}
// Increment the Index and reset at the end
void Increment()
{
completed = 0;
if (Direction == FORWARD)
{
Index++;
if (Index >= TotalSteps)
{
Index = 0;
if (OnComplete != NULL)
{
completed = 1;
OnComplete(numPixels()); // call the comlpetion callback
}
}
}
else // Direction == REVERSE
{
--Index;
if (Index <= 0)
{
Index = TotalSteps-1;
if (OnComplete != NULL)
{
completed = 1;
OnComplete(numPixels()); // call the comlpetion callback
}
}
}
}
// Reverse pattern direction
void Reverse()
{
if (Direction == FORWARD)
{
Direction = REVERSE;
Index = TotalSteps-1;
}
else
{
Direction = FORWARD;
Index = 0;
}
}
// Initialize for a RainbowCycle
void RainbowCycle(uint8_t interval, direction dir = FORWARD)
{
ActivePattern = RAINBOW_CYCLE;
Interval = interval;
TotalSteps = 255;
Index = 0;
Direction = dir;
}
// Update the Rainbow Cycle Pattern
void RainbowCycleUpdate()
{
for(int i=0; i< numPixels(); i++)
{
setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
}
show();
Increment();
}
// Initialize for a Theater Chase
void TheaterChase(uint32_t color1, uint32_t color2, uint16_t interval, direction dir = FORWARD)
{
ActivePattern = THEATER_CHASE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Theater Chase Pattern
void TheaterChaseUpdate()
{
for(int i=0; i< numPixels(); i++)
{
if ((i + Index) % 3 == 0)
{
setPixelColor(i, Color1);
}
else
{
setPixelColor(i, Color2);
}
}
show();
Increment();
}
// Initialize for a ColorWipe
void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = COLOR_WIPE;
Interval = interval;
TotalSteps = numPixels();
Color1 = color;
Index = 0;
Direction = dir;
}
// Update the Color Wipe Pattern
void ColorWipeUpdate()
{
setPixelColor(Index, Color1);
show();
Increment();
}
// Initialize for a SCANNNER
void Scanner(uint32_t color1, uint8_t interval)
{
ActivePattern = SCANNER;
Interval = interval;
TotalSteps = (numPixels() - 1) * 2;
Color1 = color1;
Index = 0;
}
// Update the Scanner Pattern
void ScannerUpdate()
{
for (int i = 0; i < numPixels(); i++)
{
if (i == Index) // Scan Pixel to the right
{
setPixelColor(i, Color1);
}
else if (i == TotalSteps - Index) // Scan Pixel to the left
{
setPixelColor(i, Color1);
}
else // Fading tail
{
setPixelColor(i, DimColor(getPixelColor(i)));
}
}
show();
Increment();
}
// Initialize for a Fade
void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
{
ActivePattern = FADE;
Interval = interval;
TotalSteps = steps;
Color1 = color1;
Color2 = color2;
Index = 0;
Direction = dir;
}
// Update the Fade Pattern
void FadeUpdate()
{
// Calculate linear interpolation between Color1 and Color2
// Optimise order of operations to minimize truncation error
uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
ColorSet(Color(red, green, blue));
show();
Increment();
}
// Calculate 50% dimmed version of a color (used by ScannerUpdate)
uint32_t DimColor(uint32_t color)
{
// Shift R, G and B components one bit to the right
uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
return dimColor;
}
// Set all pixels to a color (synchronously)
void ColorSet(uint32_t color)
{
for (int i = 0; i < numPixels(); i++)
{
setPixelColor(i, color);
}
show();
}
// Returns the Red component of a 32-bit color
uint8_t Red(uint32_t color)
{
return (color >> 16) & 0xFF;
}
// Returns the Green component of a 32-bit color
uint8_t Green(uint32_t color)
{
return (color >> 8) & 0xFF;
}
// Returns the Blue component of a 32-bit color
uint8_t Blue(uint32_t color)
{
return color & 0xFF;
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos)
{
WheelPos = 255 - WheelPos;
if(WheelPos < 85)
{
return Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else if(WheelPos < 170)
{
WheelPos -= 85;
return Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
else
{
WheelPos -= 170;
return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
};
#endif

View File

@@ -1,29 +0,0 @@
#ifndef __MESH_CONFIG__
#define __MESH_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 0
#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 "mesh-node"
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
// illucat
#define LED_STRIP_PIN D2
#define LED_STRIP_LENGTH 8
#define LED_STRIP_BRIGHTNESS 32
#endif

View File

@@ -1,21 +0,0 @@
#include "config.h"
#include "MeshNet.h"
#include "Illucat.h"
MeshNet net({
STATION_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
Illucat sprocket({ STARTUP_DELAY, SERIAL_BAUD_RATE },
{ LED_STRIP_PIN, LED_STRIP_LENGTH });
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

View File

@@ -1,43 +0,0 @@
#ifndef __MESH_APP__
#define __MESH_APP__
#include <painlessMesh.h>
#include <Sprocket.h>
#include <MeshNet.h>
using namespace std;
using namespace std::placeholders;
class MeshApp : public Sprocket {
public:
Task someTask;
MeshNet* net;
MeshApp(SprocketConfig cfg) : Sprocket(cfg) {}
Sprocket* activate(Scheduler* scheduler, Network* network) {
net = static_cast<MeshNet*>(network);
net->mesh.onReceive(bind(&MeshApp::receivedCallback,this, _1, _2));
// add a task that sends stuff to the mesh
someTask.set(TASK_SECOND * 5, TASK_FOREVER,
bind(&MeshApp::heartbeat, this, net));
scheduler->addTask(someTask);
someTask.enable();
} using Sprocket::activate;
void heartbeat(MeshNet* network){
String msg = "{ \"payload \": 1 }";
network->broadcast(msg);
}
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
// respond in receive callback can cause an endless loop when all nodes run the same firmware
//String foo = String("cheerz back to ") + String(from);
//net->broadcast(foo);
}
void loop() {
net->update();
scheduler.execute();
}
};
#endif

View File

@@ -1,23 +0,0 @@
#ifndef __MESH_CONFIG__
#define __MESH_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 0
#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 "mesh-node"
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
#endif

View File

@@ -1,20 +0,0 @@
#include "config.h"
#include "MeshNet.h"
#include "MeshApp.h"
MeshNet net({
STATION_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
MeshApp sprocket({ STARTUP_DELAY, SERIAL_BAUD_RATE });
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

View File

@@ -1,112 +0,0 @@
#ifndef __MESH_MQTT_BRIDGE_APP__
#define __MESH_MQTT_BRIDGE_APP__
#include <WiFiClient.h>
#include <painlessMesh.h>
#include <PubSubClient.h>
#include "Sprocket.h"
#include "MeshNet.h"
#define MQTT_TOPIC_FROM "mesh/from/"
#define MQTT_TOPIC_FROM_GATEWAY "mesh/from/gateway"
#define MQTT_TOPIC_TO_ALL "mesh/to/#"
using namespace std;
using namespace std::placeholders;
struct MqttConfig {
const char* clientName;
const char* brokerHost;
int brokerPort;
const char* topicRoot;
};
class MqttMeshBridge : public Sprocket {
public:
MeshNet* net;
PubSubClient* client;
WiFiClient wifiClient;
Task connectTask;
Task processTask;
MqttConfig mqttConfig;
MqttMeshBridge(SprocketConfig sprktCfg, MqttConfig cfg) : Sprocket(sprktCfg) {
mqttConfig = cfg;
}
Sprocket* activate(Scheduler* scheduler){
enableConnectTask(scheduler);
enableProcessTask(scheduler);
return this;
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
Serial.println("activate MQTT bridge");
net = static_cast<MeshNet*>(network);
net->mesh.onReceive(bind(&MqttMeshBridge::receivedCallback,this, _1, _2));
client = new PubSubClient(mqttConfig.brokerHost, mqttConfig.brokerPort, bind(&MqttMeshBridge::mqttCallback, this, _1, _2, _3), wifiClient);
return activate(scheduler);
}
private:
void enableConnectTask(Scheduler* scheduler) {
connectTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MqttMeshBridge::connect, this));
scheduler->addTask(connectTask);
connectTask.enable();
}
void enableProcessTask(Scheduler* scheduler) {
processTask.set(TASK_MILLISECOND * 5, TASK_FOREVER, bind(&MqttMeshBridge::process, this));
scheduler->addTask(processTask);
processTask.enable();
}
void process(){
client->loop();
}
void connect() {
if (!client->connected()) {
if (client->connect(mqttConfig.clientName)) {
Serial.println("MQTT connected");
client->publish(MQTT_TOPIC_FROM_GATEWAY,"Ready!");
client->subscribe(MQTT_TOPIC_TO_ALL);
}
}
}
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
String topic = MQTT_TOPIC_FROM + String(from);
client->publish(topic.c_str(), msg.c_str());
}
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

View File

@@ -1,24 +0,0 @@
#include "config.h"
#include "MeshNet.h"
#include "MqttMeshBridge.h"
MeshNet net({
STATION_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
MqttMeshBridge sprocket(
{ STARTUP_DELAY, SERIAL_BAUD_RATE },
{ MQTT_CLIENT_NAME, MQTT_BROKER, MQTT_PORT, MQTT_TOPIC_ROOT }
);
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

View File

@@ -1,29 +0,0 @@
#ifndef __BRIDGE_CONFIG__
#define __BRIDGE_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
#define WIFI_CHANNEL 11
#define MESH_PORT 5555
#define MESH_PREFIX "wirelos_contraption"
#define MESH_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "Th1ngs4P"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "sprocket"
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
// Bridge config
#define MQTT_CLIENT_NAME HOSTNAME
#define MQTT_BROKER "citadel.lan"
#define MQTT_PORT 1883
#define MQTT_TOPIC_ROOT "mesh"
#endif

View File

@@ -1,38 +0,0 @@
#ifndef __BASE_SPROCKET__
#define __BASE_SPROCKET__
#include <ESPAsyncWebServer.h>
#include <Sprocket.h>
using namespace std;
using namespace std::placeholders;
// TODO remove someTask and replace with OTA stuff
class BaseSprocket : public Sprocket {
public:
Task someTask;
MeshNet* net;
BaseSprocket(SprocketConfig cfg) : Sprocket(cfg) {
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
net = static_cast<MeshNet*>(network);
net->mesh.onReceive(bind(&BaseSprocket::receivedCallback,this, _1, _2));
// add a task that sends stuff to the mesh
someTask.set(TASK_SECOND * 5, TASK_FOREVER,
bind(&BaseSprocket::heartbeat, this, net));
scheduler->addTask(someTask);
someTask.enable();
} using Sprocket::activate;
void heartbeat(MeshNet* network){
String msg = "{ \"alive \": 1 }";
network->broadcast(msg);
}
virtual void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("RECV %u = %s\n", from, msg.c_str());
}
};
#endif

View File

@@ -1,28 +0,0 @@
#ifndef __BRIDGE_CONFIG__
#define __BRIDGE_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 0 // 1 = connect to AP using STATION params
#define WIFI_CHANNEL 11
#define MESH_PORT 5555
#define MESH_PREFIX "wirelos_contraption"
#define MESH_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "Th1ngs4P"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "sprocket"
#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION
#define MQTT_CLIENT_NAME HOSTNAME
#define MQTT_BROKER "citadel.lan"
#define MQTT_PORT 1883
#define MQTT_TOPIC_ROOT "mesh"
#endif

View File

@@ -1,23 +0,0 @@
#include "config.h"
#include "MeshNet.h"
#include "BaseSprocket.h"
MeshNet net({
STATION_MODE, WIFI_CHANNEL,
MESH_PORT, MESH_PREFIX, MESH_PASSWORD,
STATION_SSID, STATION_PASSWORD, HOSTNAME,
MESH_DEBUG_TYPES
});
BaseSprocket sprocket(
{ STARTUP_DELAY, SERIAL_BAUD_RATE }
);
void setup() {
sprocket.join(net);
}
void loop() {
sprocket.loop();
yield();
}

View File

@@ -0,0 +1,28 @@
#ifndef __NETWORK_PLUGIN__
#define __NETWORK_PLUGIN__
#include "TaskSchedulerDeclarations.h"
#include "Plugin.h"
#include "Network.h"
class NetworkPlugin : public Plugin
{
protected:
Network *network;
public:
NetworkPlugin() {}
NetworkPlugin(Network *net)
{
network = net;
}
void activate(Scheduler *userScheduler)
{
Serial.println("join network");
network->init(userScheduler);
network->connect();
}
};
#endif

11
src/utils/misc.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef __MISC_UTILS__
#define __MISC_UTILS__
#define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0])
static long mapValueToRange(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
#endif

28
src/utils/print.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include "utils/print.h"
int FORMAT_BUFFER_SIZE(const char *format, ...)
{
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args);
va_end(args);
return result + 1; // safe byte for \0
}
void PRINT_MSG(Print &out, const char *prefix, const char *format, ...)
{
if (SPROCKET_PRINT)
{
out.print(String(prefix) + String("\t| "));
char formatString[128], *ptr;
strncpy_P(formatString, format, sizeof(formatString)); // copy in from program mem
// null terminate - leave last char since we might need it in worst case for result's \0
formatString[sizeof(formatString) - 2] = '\0';
ptr = &formatString[strlen(formatString) + 1]; // our result buffer...
va_list args;
va_start(args, format);
vsnprintf(ptr, sizeof(formatString) - 1 - strlen(formatString), formatString, args);
va_end(args);
formatString[sizeof(formatString) - 1] = '\0';
out.println(ptr);
}
}

15
src/utils/print.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef __SPROCKET_UTILS__
#define __SPROCKET_UTILS__
#include <Arduino.h>
#ifndef SPROCKET_PRINT
#define SPROCKET_PRINT 1
#endif
// TODO move to sprocket
int FORMAT_BUFFER_SIZE(const char *format, ...);
void PRINT_MSG(Print &out, const char *prefix, const char *format, ...);
#endif

116
tools/ota.js Normal file
View File

@@ -0,0 +1,116 @@
// Mesh msg type = 7
// OTA paket types: START = 0, DATA = 1, END = 3
const fs = require('fs');
const crypto = require('crypto');
const mqtt = require('mqtt');
let filePath = '../.pioenvs/mesh/firmware.bin';
let broker = 'mqtt://192.168.1.2:1883';
const readFile = filePath => new Promise((resolve, reject) => {
fs.readFile(filePath, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
const checksumFile = (hashName, path) => new Promise((resolve, reject) => {
let hash = crypto.createHash(hashName);
let stream = fs.createReadStream(path);
stream.on('error', err => reject(err));
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => resolve(hash.digest('hex')));
});
const upload = async path => {
let content = await readFile(path);
let md5 = await checksumFile('MD5', path);
let b64Content = content.toString('base64');
console.log(`MD5 Hash: ${md5}`);
//console.log(b64Content);
//mqttClient(md5, b64Content);
};
const initializeUpdateMessage = (fromNode, toNode, md5Hash) => {
return {
dest: toNode,
from: fromNode,
type: 7,
msg: {
type: 0,
md5: md5Hash
}
};
};
const firmwareUpdateMessage = (fromNode, toNode, firmware) => {
return {
dest: toNode,
from: fromNode,
type: 7,
msg: {
type: 1,
data: firmware,
length: firmware.length
}
};
};
const mqttClient = (md5, data) => {
var client = mqtt.connect(broker);
let target = 3895627464;
// button thingy=> dest: "757307466",
let initMsg = {
dest: target,
from: 1,
type: 7,
msg: {
type: 0,
md5: md5
}
};
let dataMsg = {
dest: target,
from: 1,
type: 7,
msg: {
type: 1,
data: data,
length: data.length
}
};
let sm = {
OTA_INIT: {
FAILED: console.log,
DONE: OTA_DATA.WRITE
},
OTA_DATA: {
WRITE: console.log,
DONE: OTA_FIN.RESTART,
FAILED: console.log
},
OTA_FIN: {
RESTART: console.log
}
};
client.on('connect', function () {
client.subscribe('/up/wirelos/gateway');
client.publish('/down/wirelos', JSON.stringify(initMsg));
})
client.on('message', function (topic, message) {
// message is Buffer
console.log(message.toString());
let obj = JSON.parse(message.toString());
sm[obj.type][obj.state](obj);
//if(JSON.parse(message.toString()).type == 'OTA_INIT'){
// client.publish('/down/wirelos', JSON.stringify(dataMsg));
//}
client.end();
})
return client;
};
upload(filePath);

488
tools/package-lock.json generated Normal file
View File

@@ -0,0 +1,488 @@
{
"name": "tools",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"bl": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": {
"readable-stream": "2.3.6",
"safe-buffer": "5.1.2"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
"integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ=="
},
"callback-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz",
"integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=",
"requires": {
"inherits": "2.0.3",
"readable-stream": "2.3.6"
}
},
"commist": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-1.0.0.tgz",
"integrity": "sha1-wMNSUBz29S6RJOPvicmAbiAi6+8=",
"requires": {
"leven": "1.0.2",
"minimist": "1.2.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "1.1.0",
"inherits": "2.0.3",
"readable-stream": "2.3.6",
"typedarray": "0.0.6"
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"duplexify": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
"integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
"requires": {
"end-of-stream": "1.4.1",
"inherits": "2.0.3",
"readable-stream": "2.3.6",
"stream-shift": "1.0.0"
}
},
"end-of-stream": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"requires": {
"once": "1.4.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"requires": {
"is-glob": "3.1.0",
"path-dirname": "1.0.2"
}
},
"glob-stream": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz",
"integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=",
"requires": {
"extend": "3.0.2",
"glob": "7.1.2",
"glob-parent": "3.1.0",
"is-negated-glob": "1.0.0",
"ordered-read-streams": "1.0.1",
"pumpify": "1.5.1",
"readable-stream": "2.3.6",
"remove-trailing-separator": "1.1.0",
"to-absolute-glob": "2.0.2",
"unique-stream": "2.2.1"
}
},
"help-me": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz",
"integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=",
"requires": {
"callback-stream": "1.1.0",
"glob-stream": "6.1.0",
"through2": "2.0.3",
"xtend": "4.0.1"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"is-absolute": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz",
"integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==",
"requires": {
"is-relative": "1.0.0",
"is-windows": "1.0.2"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"requires": {
"is-extglob": "2.1.1"
}
},
"is-negated-glob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI="
},
"is-relative": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
"integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==",
"requires": {
"is-unc-path": "1.0.0"
}
},
"is-unc-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
"integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==",
"requires": {
"unc-path-regex": "0.1.2"
}
},
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"json-stable-stringify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
"integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
"requires": {
"jsonify": "0.0.0"
}
},
"jsonify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
},
"leven": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz",
"integrity": "sha1-kUS27ryl8dBoAWnxpncNzqYLdcM="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mqtt": {
"version": "2.18.3",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.18.3.tgz",
"integrity": "sha512-BXCUugFgA6FOWJGxhvUWtVLOdt6hYTmiMGPksEyKuuF1FQ0ji7UJBJ/0kVRMUtUWCAtPGnt4mZZZgJpzNLcuQg==",
"requires": {
"commist": "1.0.0",
"concat-stream": "1.6.2",
"end-of-stream": "1.4.1",
"help-me": "1.1.0",
"inherits": "2.0.3",
"minimist": "1.2.0",
"mqtt-packet": "5.6.0",
"pump": "3.0.0",
"readable-stream": "2.3.6",
"reinterval": "1.1.0",
"split2": "2.2.0",
"websocket-stream": "5.1.2",
"xtend": "4.0.1"
}
},
"mqtt-packet": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-5.6.0.tgz",
"integrity": "sha512-QECe2ivqcR1LRsPobRsjenEKAC3i1a5gmm+jNKJLrsiq9PaSQ18LlKFuxvhGxWkvGEPadWv6rKd31O4ICqS1Xw==",
"requires": {
"bl": "1.2.2",
"inherits": "2.0.3",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.2"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"ordered-read-streams": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz",
"integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=",
"requires": {
"readable-stream": "2.3.6"
}
},
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
"integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "1.4.1",
"once": "1.4.0"
}
},
"pumpify": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
"integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
"requires": {
"duplexify": "3.6.0",
"inherits": "2.0.3",
"pump": "2.0.1"
},
"dependencies": {
"pump": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": {
"end-of-stream": "1.4.1",
"once": "1.4.0"
}
}
}
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.2",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc="
},
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"split2": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
"integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"requires": {
"through2": "2.0.3"
}
},
"stream-shift": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"through2": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
"integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
"requires": {
"readable-stream": "2.3.6",
"xtend": "4.0.1"
}
},
"through2-filter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz",
"integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=",
"requires": {
"through2": "2.0.3",
"xtend": "4.0.1"
}
},
"to-absolute-glob": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
"integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=",
"requires": {
"is-absolute": "1.0.0",
"is-negated-glob": "1.0.0"
}
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
},
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo="
},
"unique-stream": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz",
"integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=",
"requires": {
"json-stable-stringify": "1.0.1",
"through2-filter": "2.0.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"websocket-stream": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.1.2.tgz",
"integrity": "sha512-lchLOk435iDWs0jNuL+hiU14i3ERSrMA0IKSiJh7z6X/i4XNsutBZrtqu2CPOZuA4G/zabiqVAos0vW+S7GEVw==",
"requires": {
"duplexify": "3.6.0",
"inherits": "2.0.3",
"readable-stream": "2.3.6",
"safe-buffer": "5.1.2",
"ws": "3.3.3",
"xtend": "4.0.1"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz",
"integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==",
"requires": {
"async-limiter": "1.0.0",
"safe-buffer": "5.1.2",
"ultron": "1.1.1"
}
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}
}
}

13
tools/package.json Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "tools",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"mqtt": "^2.18.3"
}
}

45
tools/tcp_example.js Normal file
View File

@@ -0,0 +1,45 @@
/*
In the node.js intro tutorial (http://nodejs.org/), they show a basic tcp
server, but for some reason omit a client connecting to it. I added an
example at the bottom.
Save the following server in example.js:
*/
var net = require('net');
var server = net.createServer(function(socket) {
socket.write('Echo server\r\n');
socket.pipe(socket);
});
server.listen(1337, '127.0.0.1');
/*
And connect with a tcp client from the command line using netcat, the *nix
utility for reading and writing across tcp/udp network connections. I've only
used it for debugging myself.
$ netcat 127.0.0.1 1337
You should see:
> Echo server
*/
/* Or use this example tcp client written in node.js. (Originated with
example code from
http://www.hacksparrow.com/tcp-socket-programming-in-node-js.html.) */
var net = require('net');
var client = new net.Socket();
client.connect(1337, '127.0.0.1', function() {
console.log('Connected');
client.write('Hello, server! Love, Client.');
});
client.on('data', function(data) {
console.log('Received: ' + data);
client.destroy(); // kill client after server's response
});
client.on('close', function() {
console.log('Connection closed');
});