From 63c65dfbc7ef0903b0a154065593cc114413e8b2 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Mon, 2 Jul 2018 11:49:18 +0200 Subject: [PATCH 01/88] some readme text --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18d535e..63ef55f 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# Sprocket-Core \ No newline at end of file +# Sprocket-Core +Library to build a mesh network of single purpose nodes. + +## WTF is a sprocket? +A sprocket is a device that has a single purpose, for example a PIR sensor node that notifies other nodes if there is motion or an LED node that lights up when a message is received. \ No newline at end of file From 22225842ae455e902d36d77ef140185aeb235b78 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 11 Jul 2018 14:32:43 +0200 Subject: [PATCH 02/88] cleanup examples --- platformio.ini | 31 +-- src/examples/button/Button.h | 47 ----- src/examples/button/config.h | 25 --- src/examples/button/main.cpp | 20 -- src/examples/illucat/Illucat.h | 45 ----- src/examples/illucat/NeoPattern.h | 304 ------------------------------ src/examples/illucat/config.h | 29 --- src/examples/illucat/main.cpp | 21 --- src/examples/mesh/config.h | 2 +- src/examples/mqttBridge/config.h | 8 +- src/firmware/BaseSprocket.h | 38 ---- src/firmware/config.h | 28 --- src/firmware/main.cpp | 23 --- 13 files changed, 11 insertions(+), 610 deletions(-) delete mode 100644 src/examples/button/Button.h delete mode 100644 src/examples/button/config.h delete mode 100644 src/examples/button/main.cpp delete mode 100644 src/examples/illucat/Illucat.h delete mode 100644 src/examples/illucat/NeoPattern.h delete mode 100644 src/examples/illucat/config.h delete mode 100644 src/examples/illucat/main.cpp delete mode 100644 src/firmware/BaseSprocket.h delete mode 100644 src/firmware/config.h delete mode 100644 src/firmware/main.cpp diff --git a/platformio.ini b/platformio.ini index 972848a..6421b5d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; http://docs.platformio.org/page/projectconf.html [platformio] -env_default = button +env_default = mesh [common] framework = arduino @@ -35,13 +35,13 @@ upload_speed = ${common.upload_speed} monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} - + #build_flags = -DLED_PIN=2 -g ;upload_port = /dev/ttyUSB0 ;upload_port = 192.168.1.168 [env:basic] -src_filter = +<*> - - + +src_filter = +<*> - + platform = ${common.platform} board = ${common.board} upload_speed = ${common.upload_speed} @@ -50,7 +50,7 @@ framework = ${common.framework} lib_deps = ${common.lib_deps} [env:mesh] -src_filter = +<*> - - + +src_filter = +<*> - + platform = ${common.platform} board = ${common.board} upload_speed = ${common.upload_speed} @@ -59,30 +59,11 @@ framework = ${common.framework} lib_deps = ${common.lib_deps} [env:meshMqttBridge] -src_filter = +<*> - - + +src_filter = +<*> - + platform = espressif8266 board = esp12e upload_speed = ${common.upload_speed} monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} - PubSubClient - -[env:illucat] -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} - Adafruit NeoPixel - -[env:button] -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} \ No newline at end of file + PubSubClient \ No newline at end of file diff --git a/src/examples/button/Button.h b/src/examples/button/Button.h deleted file mode 100644 index f26d90a..0000000 --- a/src/examples/button/Button.h +++ /dev/null @@ -1,47 +0,0 @@ - #ifndef __MESH_APP__ -#define __MESH_APP__ - -#include -#include -#include - -using namespace std; -using namespace std::placeholders; - -class Button : public Sprocket { - public: - Task btnTask; - MeshNet* net; - int pin; - Button(SprocketConfig cfg) : Sprocket(cfg) {} - Sprocket* activate(Scheduler* scheduler, Network* network) { - pin = D2; - pinMode(pin, INPUT_PULLUP); - net = static_cast(network); - net->mesh.onReceive(bind(&Button::receivedCallback,this, _1, _2)); - btnTask.set(TASK_MILLISECOND * 500, TASK_FOREVER, - bind(&Button::readPin, this, net)); - scheduler->addTask(btnTask); - btnTask.enable(); - } using Sprocket::activate; - - void readPin(MeshNet* network){ - if(digitalRead(pin)){ - Serial.println("btn pressed"); - network->broadcast("EE1B2E"); - } - } - - 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 \ No newline at end of file diff --git a/src/examples/button/config.h b/src/examples/button/config.h deleted file mode 100644 index c27cb09..0000000 --- a/src/examples/button/config.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/examples/button/main.cpp b/src/examples/button/main.cpp deleted file mode 100644 index 061f47f..0000000 --- a/src/examples/button/main.cpp +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/src/examples/illucat/Illucat.h b/src/examples/illucat/Illucat.h deleted file mode 100644 index 0991f6c..0000000 --- a/src/examples/illucat/Illucat.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef __MESH_APP__ -#define __MESH_APP__ - -#include -#include -#include -#include "NeoPattern.h" - -using namespace std; -using namespace std::placeholders; - -struct NeoPixelConfig { - int pin; - int length; -}; - -class Illucat : public Sprocket { - public: - MeshNet* net; - NeoPattern* pixels; - Illucat(SprocketConfig cfg, NeoPixelConfig pixelCfg) : Sprocket(cfg) { - pixels = new NeoPattern(pixelCfg.length, pixelCfg.pin, NEO_GRB + NEO_KHZ800, [](int pixels){}); - } - Sprocket* activate(Scheduler* scheduler, Network* network) { - net = static_cast(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()); - setHexColor(msg.c_str()); - } - 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 \ No newline at end of file diff --git a/src/examples/illucat/NeoPattern.h b/src/examples/illucat/NeoPattern.h deleted file mode 100644 index f2b1a58..0000000 --- a/src/examples/illucat/NeoPattern.h +++ /dev/null @@ -1,304 +0,0 @@ -#ifndef __NeoPattern_H_INCLUDED__ -#define __NeoPattern_H_INCLUDED__ - -#include - -/** - * 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 \ No newline at end of file diff --git a/src/examples/illucat/config.h b/src/examples/illucat/config.h deleted file mode 100644 index f099c01..0000000 --- a/src/examples/illucat/config.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/examples/illucat/main.cpp b/src/examples/illucat/main.cpp deleted file mode 100644 index 9c12dbf..0000000 --- a/src/examples/illucat/main.cpp +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index a9cd7eb..743cae6 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -13,7 +13,7 @@ #define STATION_MODE 0 #define WIFI_CHANNEL 11 #define MESH_PORT 5555 -#define MESH_PREFIX "whateverYouLike" +#define MESH_PREFIX "WirelosContraption" #define MESH_PASSWORD "somethingSneaky" #define STATION_SSID "Th1ngs4P" #define STATION_PASSWORD "th3r31sn0sp00n" diff --git a/src/examples/mqttBridge/config.h b/src/examples/mqttBridge/config.h index 67b0c67..f60e49e 100644 --- a/src/examples/mqttBridge/config.h +++ b/src/examples/mqttBridge/config.h @@ -13,16 +13,16 @@ #define STATION_MODE 1 #define WIFI_CHANNEL 11 #define MESH_PORT 5555 -#define MESH_PREFIX "wirelos_contraption" -#define MESH_PASSWORD "th3r31sn0sp00n" +#define MESH_PREFIX "WirelosContraption" +#define MESH_PASSWORD "somethingSneaky" #define STATION_SSID "Th1ngs4P" #define STATION_PASSWORD "th3r31sn0sp00n" -#define HOSTNAME "sprocket" +#define HOSTNAME "mqtt-bridge" #define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION // Bridge config #define MQTT_CLIENT_NAME HOSTNAME -#define MQTT_BROKER "citadel.lan" +#define MQTT_BROKER "iot.eclipse.org" #define MQTT_PORT 1883 #define MQTT_TOPIC_ROOT "mesh" diff --git a/src/firmware/BaseSprocket.h b/src/firmware/BaseSprocket.h deleted file mode 100644 index 9cb0398..0000000 --- a/src/firmware/BaseSprocket.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef __BASE_SPROCKET__ -#define __BASE_SPROCKET__ - -#include -#include - -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(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 \ No newline at end of file diff --git a/src/firmware/config.h b/src/firmware/config.h deleted file mode 100644 index 0a19cd1..0000000 --- a/src/firmware/config.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/firmware/main.cpp b/src/firmware/main.cpp deleted file mode 100644 index ab78d91..0000000 --- a/src/firmware/main.cpp +++ /dev/null @@ -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(); -} \ No newline at end of file From acc284dd0b915171c7aac8f2ef60f54d4ccf3f07 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 11 Jul 2018 14:35:20 +0200 Subject: [PATCH 03/88] ci --- .gitlab-ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c8ed165..231cec5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,14 +1,15 @@ stages: - build -cache: - paths: - - .piolibdeps +#cache: +# paths: +# - .piolibdeps build-examples: stage: build image: registry.gitlab.com/wirelos/contraption-pipeline/platformio:v1 script: + - pio upgrade - 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/ From 45a186085277ce335a87d1c717053d486b1fb614 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 11 Jul 2018 19:44:52 +0200 Subject: [PATCH 04/88] ci --- .gitlab-ci.yml | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 231cec5..d69347e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,16 +1,42 @@ -stages: - - build - +#stages: +# - build +# #cache: # paths: # - .piolibdeps +# +#build-examples: +# stage: build +# image: registry.gitlab.com/wirelos/contraption-pipeline/platformio:v1 +# script: +# - pio upgrade +# - 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/ -build-examples: +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-build: stage: build - image: registry.gitlab.com/wirelos/contraption-pipeline/platformio:v1 + image: python:2.7-stretch script: - - pio upgrade - - 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/ \ No newline at end of file + - pio run -t clean + - pio run + artifacts: + paths: + - .pioenvs/*/firmware.* + - .pioenvs/*/spiffs.bin From 5f9de83c5a9ed566a90b6ffcc78eb9b8d75c3196 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 12 Jul 2018 01:42:43 +0200 Subject: [PATCH 05/88] layer basic com --- .gitlab-ci.yml | 4 +++- .vscode/settings.json | 3 ++- src/MeshNet.cpp | 5 ++--- src/MeshNet.h | 6 +++--- src/Network.h | 8 +++++--- src/examples/mesh/MeshApp.h | 6 +++--- src/examples/mesh/config.h | 4 ++-- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d69347e..4d3bbe9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,7 +35,9 @@ firmware-build: image: python:2.7-stretch script: - pio run -t clean - - pio run + - pio run --environment basic + - pio run --environment mesh + - pio run --environment meshMqttBridge artifacts: paths: - .pioenvs/*/firmware.* diff --git a/.vscode/settings.json b/.vscode/settings.json index 30a0ad1..ba52e2e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ "type_traits": "cpp", "tuple": "cpp", "typeinfo": "cpp", - "utility": "cpp" + "utility": "cpp", + "bitset": "cpp" } } \ No newline at end of file diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 92ebfd6..f7a6a5b 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -11,7 +11,6 @@ Network* MeshNet::init(){ 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)); @@ -36,8 +35,8 @@ 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::onReceive( std::function cb) { + mesh.onReceive(cb); } void MeshNet::newConnectionCallback(uint32_t nodeId) { diff --git a/src/MeshNet.h b/src/MeshNet.h index 1275655..1c59eb7 100644 --- a/src/MeshNet.h +++ b/src/MeshNet.h @@ -28,13 +28,13 @@ class MeshNet : public Network { 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); + void broadcast(String msg); + void sendTo(uint32_t target, String msg); + void onReceive(std::function); }; #endif \ No newline at end of file diff --git a/src/Network.h b/src/Network.h index e6af301..e9d9632 100644 --- a/src/Network.h +++ b/src/Network.h @@ -4,6 +4,8 @@ #include #include +typedef std::function msgReceived_cb; + class Network { public: uint32_t id = 0; @@ -13,9 +15,9 @@ class Network { virtual Network* init(Scheduler* s) { scheduler = s; return init(); }; virtual Network* connect() { return this; }; virtual void update() {}; - virtual void broadcast(String msg){ - Serial.println("no-broadcast"); - }; + virtual void broadcast(String msg){}; + virtual void sendTo(uint32_t target, String msg) {}; + virtual void onReceive(std::function); Network* setScheduler(Scheduler* s) { scheduler = s; return this; diff --git a/src/examples/mesh/MeshApp.h b/src/examples/mesh/MeshApp.h index 8c8d28c..15df943 100644 --- a/src/examples/mesh/MeshApp.h +++ b/src/examples/mesh/MeshApp.h @@ -15,7 +15,7 @@ class MeshApp : public Sprocket { MeshApp(SprocketConfig cfg) : Sprocket(cfg) {} Sprocket* activate(Scheduler* scheduler, Network* network) { net = static_cast(network); - net->mesh.onReceive(bind(&MeshApp::receivedCallback,this, _1, _2)); + net->onReceive(bind(&MeshApp::onReceive,this, _1, _2)); // add a task that sends stuff to the mesh someTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MeshApp::heartbeat, this, net)); @@ -28,8 +28,8 @@ class MeshApp : public Sprocket { network->broadcast(msg); } - void receivedCallback( uint32_t from, String &msg ) { - Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str()); + void onReceive( uint32_t from, String &msg ) { + Serial.printf("MeshApp: 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); diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index 743cae6..391f13b 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -13,11 +13,11 @@ #define STATION_MODE 0 #define WIFI_CHANNEL 11 #define MESH_PORT 5555 -#define MESH_PREFIX "WirelosContraption" +#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 MESH_DEBUG_TYPES ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE #endif \ No newline at end of file From b4ebd347eb8b3df8483d1ecfb1f5033a987567d5 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 16:16:44 +0200 Subject: [PATCH 06/88] introduce dispatch method to simplify things --- README.md | 8 +++++++- library.json | 2 +- src/Sprocket.cpp | 1 + src/Sprocket.h | 4 ++++ src/examples/mesh/MeshApp.h | 4 ++-- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 63ef55f..f2ca9a0 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,10 @@ Library to build a mesh network of single purpose nodes. ## WTF is a sprocket? -A sprocket is a device that has a single purpose, for example a PIR sensor node that notifies other nodes if there is motion or an LED node that lights up when a message is received. \ No newline at end of file +A sprocket is a device that has a single purpose, for example a PIR sensor node that notifies other nodes if there is motion or an LED node that lights up when a message is received. + +# Useful commands +```sh +# erase flash + esptool --port /dev/ttyUSB0 erase_flash +``` \ No newline at end of file diff --git a/library.json b/library.json index 1ec316d..706a6f7 100644 --- a/library.json +++ b/library.json @@ -1,4 +1,4 @@ -{ +k{ "name": "sprocket-core", "keywords": "esp8266, sprocket, stack", "description": "Core stack for Sprockets", diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index 2a70fb6..3ec142f 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -22,6 +22,7 @@ Sprocket* Sprocket::activate() { Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); net.init(&scheduler); + net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); net.connect(); return activate(&scheduler, &net); } diff --git a/src/Sprocket.h b/src/Sprocket.h index 6d485b8..fbd3c34 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -6,6 +6,9 @@ #include #include "Network.h" +using namespace std; +using namespace std::placeholders; + struct SprocketConfig { int startupDelay; int serialBaudRate; @@ -25,6 +28,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } + virtual void dispatch(uint32_t from, String &msg) {}; }; #endif \ No newline at end of file diff --git a/src/examples/mesh/MeshApp.h b/src/examples/mesh/MeshApp.h index 15df943..5c7b0cc 100644 --- a/src/examples/mesh/MeshApp.h +++ b/src/examples/mesh/MeshApp.h @@ -15,7 +15,7 @@ class MeshApp : public Sprocket { MeshApp(SprocketConfig cfg) : Sprocket(cfg) {} Sprocket* activate(Scheduler* scheduler, Network* network) { net = static_cast(network); - net->onReceive(bind(&MeshApp::onReceive,this, _1, _2)); + //net->onReceive(bind(&MeshApp::onReceive,this, _1, _2)); // add a task that sends stuff to the mesh someTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MeshApp::heartbeat, this, net)); @@ -28,7 +28,7 @@ class MeshApp : public Sprocket { network->broadcast(msg); } - void onReceive( uint32_t from, String &msg ) { + void dispatch( uint32_t from, String &msg ) { Serial.printf("MeshApp: 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); From d2c0998b8f27ec1a17a92eb2b5caa0bdca459227 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 18:29:16 +0200 Subject: [PATCH 07/88] add network as member of Sprocket --- src/MeshNet.cpp | 2 ++ src/Sprocket.cpp | 1 + src/Sprocket.h | 1 + 3 files changed, 4 insertions(+) diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index f7a6a5b..b4f4269 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -35,6 +35,8 @@ void MeshNet::update(){ // only needed when no scheduler was passed to mesh.init mesh.update(); } + +// example, not used, to be removed void MeshNet::onReceive( std::function cb) { mesh.onReceive(cb); } diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index 3ec142f..ea1e7f5 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -21,6 +21,7 @@ Sprocket* Sprocket::activate() { Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); + network = &net; net.init(&scheduler); net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); net.connect(); diff --git a/src/Sprocket.h b/src/Sprocket.h index fbd3c34..7d3f79f 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -17,6 +17,7 @@ struct SprocketConfig { class Sprocket { protected: Scheduler scheduler; + Network* network; public: SprocketConfig config; Sprocket(); From 9acd1cb6a8f10d482040b4d5797feba2772860a6 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 18:31:33 +0200 Subject: [PATCH 08/88] add network update to loop --- src/Sprocket.cpp | 2 ++ src/Sprocket.h | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index ea1e7f5..561d7ad 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -21,6 +21,7 @@ Sprocket* Sprocket::activate() { Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); + hasNetwork = 1; network = &net; net.init(&scheduler); net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); @@ -36,4 +37,5 @@ Sprocket* Sprocket::addTask(Task& tsk){ void Sprocket::loop(){ scheduler.execute(); + if(hasNetwork) network->update(); } \ No newline at end of file diff --git a/src/Sprocket.h b/src/Sprocket.h index 7d3f79f..21a777a 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -18,6 +18,7 @@ class Sprocket { protected: Scheduler scheduler; Network* network; + int hasNetwork = 0; public: SprocketConfig config; Sprocket(); From b4094e4fdd557316fe0a58e5b72ed87a3766df92 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 18:41:20 +0200 Subject: [PATCH 09/88] fix library.json --- library.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.json b/library.json index 706a6f7..1ec316d 100644 --- a/library.json +++ b/library.json @@ -1,4 +1,4 @@ -k{ +{ "name": "sprocket-core", "keywords": "esp8266, sprocket, stack", "description": "Core stack for Sprockets", From fe8066af714458325e1f9a27fb4601a3c5c126a7 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 19:04:34 +0200 Subject: [PATCH 10/88] remove network from sprocket --- src/Sprocket.cpp | 3 --- src/Sprocket.h | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index 561d7ad..3ec142f 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -21,8 +21,6 @@ Sprocket* Sprocket::activate() { Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); - hasNetwork = 1; - network = &net; net.init(&scheduler); net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); net.connect(); @@ -37,5 +35,4 @@ Sprocket* Sprocket::addTask(Task& tsk){ void Sprocket::loop(){ scheduler.execute(); - if(hasNetwork) network->update(); } \ No newline at end of file diff --git a/src/Sprocket.h b/src/Sprocket.h index 21a777a..2be7ce9 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -17,8 +17,6 @@ struct SprocketConfig { class Sprocket { protected: Scheduler scheduler; - Network* network; - int hasNetwork = 0; public: SprocketConfig config; Sprocket(); @@ -30,7 +28,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } - virtual void dispatch(uint32_t from, String &msg) {}; + virtual void dispatch(uint32_t from, String &msg) {}; }; #endif \ No newline at end of file From 8eeb0da4fd77fd375bf32131ea76e17b99ee0018 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 19:22:15 +0200 Subject: [PATCH 11/88] don't bind dispatch when joining network --- src/Sprocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index 3ec142f..aa3751b 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -22,7 +22,7 @@ Sprocket* Sprocket::activate() { Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); net.init(&scheduler); - net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); + //net.onReceive(bind(&Sprocket::dispatch,this, _1, _2)); net.connect(); return activate(&scheduler, &net); } From c5396b5bc0bb172f61b33a69beebfce8eb13335d Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 13 Jul 2018 19:46:46 +0200 Subject: [PATCH 12/88] comment out dispatch thingy --- src/Sprocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sprocket.h b/src/Sprocket.h index 2be7ce9..7133c62 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -28,7 +28,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } - virtual void dispatch(uint32_t from, String &msg) {}; + //virtual void dispatch(uint32_t from, String &msg) {}; }; #endif \ No newline at end of file From 4ec14819026a162fd99f0e26c2480c151269f55e Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 19 Jul 2018 17:27:29 +0200 Subject: [PATCH 13/88] cleanup basic example --- platformio.ini | 1 - src/Sprocket.h | 6 +++++- .../basic/{ExampleApp.h => ExampleApp.cpp} | 14 +++----------- src/examples/basic/{basic.cpp => main.cpp} | 3 ++- 4 files changed, 10 insertions(+), 14 deletions(-) rename src/examples/basic/{ExampleApp.h => ExampleApp.cpp} (54%) rename src/examples/basic/{basic.cpp => main.cpp} (90%) diff --git a/platformio.ini b/platformio.ini index 6421b5d..750657e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,7 +19,6 @@ upload_speed = 921600 monitor_baud = 115200 lib_deps = Hash - ESP Async WebServer ESPAsyncTCP TaskScheduler painlessMesh diff --git a/src/Sprocket.h b/src/Sprocket.h index 7133c62..aa59182 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -9,6 +9,9 @@ using namespace std; using namespace std::placeholders; +// FIXME move to some global fnc lib +#define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0]) + struct SprocketConfig { int startupDelay; int serialBaudRate; @@ -28,7 +31,8 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } - //virtual void dispatch(uint32_t from, String &msg) {}; + // TODO bind onMessage to network->onReceive + //virtual void onMessage(uint32_t from, String &msg) {}; }; #endif \ No newline at end of file diff --git a/src/examples/basic/ExampleApp.h b/src/examples/basic/ExampleApp.cpp similarity index 54% rename from src/examples/basic/ExampleApp.h rename to src/examples/basic/ExampleApp.cpp index 7a21f8d..9fa7180 100644 --- a/src/examples/basic/ExampleApp.h +++ b/src/examples/basic/ExampleApp.cpp @@ -1,8 +1,6 @@ #ifndef __EXAMPLE_APP__ #define __EXAMPLE_APP__ -//#include "Sprocket.h" -#include #include 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 \ No newline at end of file diff --git a/src/examples/basic/basic.cpp b/src/examples/basic/main.cpp similarity index 90% rename from src/examples/basic/basic.cpp rename to src/examples/basic/main.cpp index 5eac4d8..1ba6d64 100644 --- a/src/examples/basic/basic.cpp +++ b/src/examples/basic/main.cpp @@ -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 }); From c86f96996de3800b5fd75d19d48c534f24b5dcc3 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 19 Jul 2018 17:30:05 +0200 Subject: [PATCH 14/88] cleanup mesh example --- src/examples/mesh/{MeshApp.h => MeshApp.cpp} | 5 +++-- src/examples/mesh/{mesh.cpp => main.cpp} | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/examples/mesh/{MeshApp.h => MeshApp.cpp} (91%) rename src/examples/mesh/{mesh.cpp => main.cpp} (93%) diff --git a/src/examples/mesh/MeshApp.h b/src/examples/mesh/MeshApp.cpp similarity index 91% rename from src/examples/mesh/MeshApp.h rename to src/examples/mesh/MeshApp.cpp index 5c7b0cc..b41f39f 100644 --- a/src/examples/mesh/MeshApp.h +++ b/src/examples/mesh/MeshApp.cpp @@ -1,4 +1,4 @@ - #ifndef __MESH_APP__ +#ifndef __MESH_APP__ #define __MESH_APP__ #include @@ -15,12 +15,13 @@ class MeshApp : public Sprocket { MeshApp(SprocketConfig cfg) : Sprocket(cfg) {} Sprocket* activate(Scheduler* scheduler, Network* network) { net = static_cast(network); - //net->onReceive(bind(&MeshApp::onReceive,this, _1, _2)); + net->onReceive(bind(&MeshApp::dispatch,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(); + return this; } using Sprocket::activate; void heartbeat(MeshNet* network){ diff --git a/src/examples/mesh/mesh.cpp b/src/examples/mesh/main.cpp similarity index 93% rename from src/examples/mesh/mesh.cpp rename to src/examples/mesh/main.cpp index 655fbbd..bd04b7c 100644 --- a/src/examples/mesh/mesh.cpp +++ b/src/examples/mesh/main.cpp @@ -1,6 +1,6 @@ #include "config.h" #include "MeshNet.h" -#include "MeshApp.h" +#include "MeshApp.cpp" MeshNet net({ STATION_MODE, WIFI_CHANNEL, From d7a67560eab37b54411cd63df338bde3249a1ab3 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 19 Jul 2018 17:34:44 +0200 Subject: [PATCH 15/88] cleanup mqtt bridge example --- .../MqttMeshBridge.h => meshMqttBridge/MqttMeshBridge.cpp} | 0 src/examples/{mqttBridge => meshMqttBridge}/config.h | 0 src/examples/{mqttBridge/bridge.cpp => meshMqttBridge/main.cpp} | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/examples/{mqttBridge/MqttMeshBridge.h => meshMqttBridge/MqttMeshBridge.cpp} (100%) rename src/examples/{mqttBridge => meshMqttBridge}/config.h (100%) rename src/examples/{mqttBridge/bridge.cpp => meshMqttBridge/main.cpp} (93%) diff --git a/src/examples/mqttBridge/MqttMeshBridge.h b/src/examples/meshMqttBridge/MqttMeshBridge.cpp similarity index 100% rename from src/examples/mqttBridge/MqttMeshBridge.h rename to src/examples/meshMqttBridge/MqttMeshBridge.cpp diff --git a/src/examples/mqttBridge/config.h b/src/examples/meshMqttBridge/config.h similarity index 100% rename from src/examples/mqttBridge/config.h rename to src/examples/meshMqttBridge/config.h diff --git a/src/examples/mqttBridge/bridge.cpp b/src/examples/meshMqttBridge/main.cpp similarity index 93% rename from src/examples/mqttBridge/bridge.cpp rename to src/examples/meshMqttBridge/main.cpp index ed1776a..46f0fce 100644 --- a/src/examples/mqttBridge/bridge.cpp +++ b/src/examples/meshMqttBridge/main.cpp @@ -1,6 +1,6 @@ #include "config.h" #include "MeshNet.h" -#include "MqttMeshBridge.h" +#include "MqttMeshBridge.cpp" MeshNet net({ STATION_MODE, WIFI_CHANNEL, From 64958720f80b47b55313e58c5fd278ad110a6198 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Thu, 19 Jul 2018 17:34:57 +0200 Subject: [PATCH 16/88] cleanup pio ini --- platformio.ini | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/platformio.ini b/platformio.ini index 750657e..9d3dfcb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,8 +8,8 @@ ; Please visit documentation for the other options and examples ; http://docs.platformio.org/page/projectconf.html -[platformio] -env_default = mesh +;[platformio] +;env_default = mesh [common] framework = arduino @@ -21,21 +21,20 @@ lib_deps = Hash ESPAsyncTCP TaskScheduler - painlessMesh -[env:build] -src_filter = +<*> - -#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} +;[env:build] +;src_filter = +<*> - +;#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} -#build_flags = -DLED_PIN=2 -g +;build_flags = -DLED_PIN=2 -g ;upload_port = /dev/ttyUSB0 ;upload_port = 192.168.1.168 @@ -56,13 +55,15 @@ upload_speed = ${common.upload_speed} monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} + painlessMesh [env:meshMqttBridge] -src_filter = +<*> - + +src_filter = +<*> - + platform = espressif8266 board = esp12e upload_speed = ${common.upload_speed} monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} + painlessMesh PubSubClient \ No newline at end of file From dbb4ce9de7ea3c7fdb46862d333f8641de3a1fb2 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 3 Aug 2018 11:01:42 +0000 Subject: [PATCH 17/88] Resolve "OTA" --- .gitignore | 3 +- README.md | 6 +- platformio.ini | 23 +- src/MeshNet.cpp | 7 +- src/MeshNet.h | 15 +- src/Network.h | 2 + src/OtaTcpPlugin.cpp | 74 ++++ src/Plugin.h | 15 + src/Sprocket.h | 6 +- src/examples/mesh/config.h | 3 +- src/examples/meshMqttBridge/config.h | 4 +- src/examples/ota/SprocketOTA.cpp | 40 +++ src/examples/ota/config.h | 27 ++ src/examples/ota/main.cpp | 25 ++ tools/ota.js | 116 +++++++ tools/package-lock.json | 488 +++++++++++++++++++++++++++ tools/package.json | 13 + tools/tcp_example.js | 45 +++ 18 files changed, 895 insertions(+), 17 deletions(-) create mode 100644 src/OtaTcpPlugin.cpp create mode 100644 src/Plugin.h create mode 100644 src/examples/ota/SprocketOTA.cpp create mode 100644 src/examples/ota/config.h create mode 100644 src/examples/ota/main.cpp create mode 100644 tools/ota.js create mode 100644 tools/package-lock.json create mode 100644 tools/package.json create mode 100644 tools/tcp_example.js diff --git a/.gitignore b/.gitignore index a368c67..251e8a9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .piolibdeps .vscode/.browse.c_cpp.db* .vscode/c_cpp_properties.json -.vscode/launch.json \ No newline at end of file +.vscode/launch.json +tools/node_modules \ No newline at end of file diff --git a/README.md b/README.md index f2ca9a0..bd7a948 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,9 @@ A sprocket is a device that has a single purpose, for example a PIR sensor node # Useful commands ```sh # erase flash - esptool --port /dev/ttyUSB0 erase_flash +esptool --port /dev/ttyUSB0 erase_flash + +# OTA +~/.platformio/packages/tool-espotapy/espota.py -i -p 8266 -a -f .pioenvs/ota/firmware.bin + ``` \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 9d3dfcb..96adc9e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,8 +8,8 @@ ; Please visit documentation for the other options and examples ; http://docs.platformio.org/page/projectconf.html -;[platformio] -;env_default = mesh +[platformio] +env_default = mesh [common] framework = arduino @@ -21,19 +21,16 @@ lib_deps = Hash ESPAsyncTCP TaskScheduler + SPIFFS ;[env:build] ;src_filter = +<*> - -;#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} - ;build_flags = -DLED_PIN=2 -g ;upload_port = /dev/ttyUSB0 ;upload_port = 192.168.1.168 @@ -66,4 +63,16 @@ monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} painlessMesh - PubSubClient \ No newline at end of file + 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} + https://gitlab.com/wirelos/painlessMesh.git#feature/ota + ArduinoOTA \ No newline at end of file diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index b4f4269..5da7068 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -15,15 +15,18 @@ Network* MeshNet::init(){ mesh.onChangedConnections(bind(&MeshNet::changedConnectionCallback, this)); mesh.onNodeTimeAdjusted(bind(&MeshNet::nodeTimeAdjustedCallback, this, _1)); + connectStation(); + + return this; +} +Network* MeshNet::connectStation() { 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); } diff --git a/src/MeshNet.h b/src/MeshNet.h index 1c59eb7..0110575 100644 --- a/src/MeshNet.h +++ b/src/MeshNet.h @@ -1,6 +1,12 @@ #ifndef __MESHNET_H__ #define __MESHNET_H__ +#ifdef ESP32 +#include +#elif defined(ESP8266) +#include +#endif // ESP32 + #include #include #include "Network.h" @@ -8,6 +14,7 @@ using namespace std; using namespace std::placeholders; +// FIXME non-mesh config should have it's own struct struct MeshConfig { int stationMode; int channel; @@ -27,14 +34,18 @@ class MeshNet : public Network { MeshNet(MeshConfig cfg); Network* init(); - - void update(); // only needed when no scheduler was passed to mesh.init + Network* connectStation(); + + void update(); void newConnectionCallback(uint32_t nodeId); void changedConnectionCallback(); void nodeTimeAdjustedCallback(int32_t offset); void broadcast(String msg); void sendTo(uint32_t target, String msg); void onReceive(std::function); + int isConnected(){ + return WiFi.status() == WL_CONNECTED; + } }; #endif \ No newline at end of file diff --git a/src/Network.h b/src/Network.h index e9d9632..93a65ea 100644 --- a/src/Network.h +++ b/src/Network.h @@ -14,6 +14,8 @@ class Network { virtual Network* init() { return this; }; virtual Network* init(Scheduler* s) { scheduler = s; return init(); }; virtual Network* connect() { return this; }; + virtual Network* connectStation() { return this; }; + virtual int isConnected(){ return 0; }; virtual void update() {}; virtual void broadcast(String msg){}; virtual void sendTo(uint32_t target, String msg) {}; diff --git a/src/OtaTcpPlugin.cpp b/src/OtaTcpPlugin.cpp new file mode 100644 index 0000000..4e72ae2 --- /dev/null +++ b/src/OtaTcpPlugin.cpp @@ -0,0 +1,74 @@ +#ifndef __OTA_CLASSIC_H__ +#define __OTA_CLASSIC_H__ + +#include "TaskSchedulerDeclarations.h" +#include "ArduinoOTA.h" +#include "MeshNet.h" +#include "Plugin.h" + +using namespace std; +using namespace std::placeholders; + +struct OtaConfig { + int port; + const char* password; +}; + +// TODO rename to OtaTcpPlugin +class OtaTcpPlugin : public Plugin { + private: + OtaConfig config; + Task otaTask; + public: + OtaTcpPlugin(OtaConfig cfg){ + config = cfg; + } + void enable(Scheduler* userScheduler, Network* network){ + scheduler = userScheduler; + + // connect to network + if(!network->isConnected()){ + // TODO when config is refactored, cast is not necessary anymore + static_cast(network)->config.stationMode = 1; + network->connectStation(); + // TODO set service message to mesh to announce station IP + } + // setup task + otaTask.set(TASK_MILLISECOND * 100, TASK_FOREVER, [](){ + ArduinoOTA.handle(); + }); + scheduler->addTask(otaTask); + + // configure OTA + ArduinoOTA.setPort(config.port); + //ArduinoOTA.setHostname(HOSTNAME); + if(strlen(config.password) > 0){ + ArduinoOTA.setPassword(config.password); + } + // setup callbacks + ArduinoOTA.onStart([]() { + Serial.println("OTA: Start"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("OTA: End"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("OTA: Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("OTA: Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("OTA: Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("OTA: Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("OTA: Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("OTA: Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("OTA: End Failed"); + }); + ArduinoOTA.begin(); + otaTask.enable(); + } + void disable(){ + otaTask.disable(); + } +}; + +#endif \ No newline at end of file diff --git a/src/Plugin.h b/src/Plugin.h new file mode 100644 index 0000000..c331e60 --- /dev/null +++ b/src/Plugin.h @@ -0,0 +1,15 @@ +#ifndef __SPROCKET_PLUGIN__ +#define __SPROCKET_PLUGIN__ + +#include +#include + +class Plugin { + protected: + Scheduler* scheduler; + public: + virtual void enable(Scheduler*, Network*); + virtual void disable(); +}; + +#endif \ No newline at end of file diff --git a/src/Sprocket.h b/src/Sprocket.h index aa59182..cf20edd 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -3,7 +3,10 @@ #include #include -#include +#include "FS.h" +#ifdef ESP32 +#include "SPIFFS.h" +#endif #include "Network.h" using namespace std; @@ -31,6 +34,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } + virtual Sprocket* enable() { return this; }; // TODO bind onMessage to network->onReceive //virtual void onMessage(uint32_t from, String &msg) {}; }; diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index 391f13b..6b44a23 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -18,6 +18,7 @@ #define STATION_SSID "Th1ngs4P" #define STATION_PASSWORD "th3r31sn0sp00n" #define HOSTNAME "mesh-node" -#define MESH_DEBUG_TYPES ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE +#define MESH_DEBUG_TYPES ERROR | CONNECTION | COMMUNICATION +//ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE #endif \ No newline at end of file diff --git a/src/examples/meshMqttBridge/config.h b/src/examples/meshMqttBridge/config.h index f60e49e..5a10475 100644 --- a/src/examples/meshMqttBridge/config.h +++ b/src/examples/meshMqttBridge/config.h @@ -13,7 +13,7 @@ #define STATION_MODE 1 #define WIFI_CHANNEL 11 #define MESH_PORT 5555 -#define MESH_PREFIX "WirelosContraption" +#define MESH_PREFIX "whateverYouLike" #define MESH_PASSWORD "somethingSneaky" #define STATION_SSID "Th1ngs4P" #define STATION_PASSWORD "th3r31sn0sp00n" @@ -22,7 +22,7 @@ // Bridge config #define MQTT_CLIENT_NAME HOSTNAME -#define MQTT_BROKER "iot.eclipse.org" +#define MQTT_BROKER "citadel.lan" #define MQTT_PORT 1883 #define MQTT_TOPIC_ROOT "mesh" diff --git a/src/examples/ota/SprocketOTA.cpp b/src/examples/ota/SprocketOTA.cpp new file mode 100644 index 0000000..ebe4776 --- /dev/null +++ b/src/examples/ota/SprocketOTA.cpp @@ -0,0 +1,40 @@ +#ifndef __MESH_APP__ +#define __MESH_APP__ + +#define DEBUG_ESP_OTA +#include +#include +#include +#include +#include "config.h" + +using namespace std; +using namespace std::placeholders; + +class SprocketOTA : public Sprocket { + public: + MeshNet* net; + OtaTcpPlugin* ota; + + SprocketOTA(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { + ota = new OtaTcpPlugin(otaCfg); + } + + Sprocket* activate(Scheduler* scheduler, Network* network) { + net = static_cast(network); + net->onReceive(bind(&SprocketOTA::dispatch,this, _1, _2)); + ota->enable(scheduler, network); + return this; + } using Sprocket::activate; + + void dispatch( uint32_t from, String &msg ) { + Serial.printf("Sprocket: received from %u msg=%s\n", from, msg.c_str()); + } + + void loop() { + net->update(); + scheduler.execute(); + } +}; + +#endif \ No newline at end of file diff --git a/src/examples/ota/config.h b/src/examples/ota/config.h new file mode 100644 index 0000000..86185cf --- /dev/null +++ b/src/examples/ota/config.h @@ -0,0 +1,27 @@ +#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 new file mode 100644 index 0000000..222a804 --- /dev/null +++ b/src/examples/ota/main.cpp @@ -0,0 +1,25 @@ +#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/tools/ota.js b/tools/ota.js new file mode 100644 index 0000000..dd20f5f --- /dev/null +++ b/tools/ota.js @@ -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); \ No newline at end of file diff --git a/tools/package-lock.json b/tools/package-lock.json new file mode 100644 index 0000000..0c2ecd0 --- /dev/null +++ b/tools/package-lock.json @@ -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=" + } + } +} diff --git a/tools/package.json b/tools/package.json new file mode 100644 index 0000000..b0a549e --- /dev/null +++ b/tools/package.json @@ -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" + } +} diff --git a/tools/tcp_example.js b/tools/tcp_example.js new file mode 100644 index 0000000..18f0ff9 --- /dev/null +++ b/tools/tcp_example.js @@ -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'); +}); From 32975862d8d4ca91c16d286733e832a9d0802a14 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 3 Aug 2018 16:02:51 +0200 Subject: [PATCH 18/88] rename OTA plugin --- src/OtaTcpPlugin.cpp | 1 - src/Sprocket.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/OtaTcpPlugin.cpp b/src/OtaTcpPlugin.cpp index 4e72ae2..6ff0a1b 100644 --- a/src/OtaTcpPlugin.cpp +++ b/src/OtaTcpPlugin.cpp @@ -14,7 +14,6 @@ struct OtaConfig { const char* password; }; -// TODO rename to OtaTcpPlugin class OtaTcpPlugin : public Plugin { private: OtaConfig config; diff --git a/src/Sprocket.h b/src/Sprocket.h index cf20edd..de48cf2 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -34,7 +34,6 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } - virtual Sprocket* enable() { return this; }; // TODO bind onMessage to network->onReceive //virtual void onMessage(uint32_t from, String &msg) {}; }; From a94aaa68047d36392e3218ea969c315ad4831cc6 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 3 Aug 2018 21:39:01 +0200 Subject: [PATCH 19/88] add OTA example to CI build --- .gitlab-ci.yml | 1 + platformio.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d3bbe9..5ea9ae4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,6 +38,7 @@ firmware-build: - pio run --environment basic - pio run --environment mesh - pio run --environment meshMqttBridge + - pio run --environment ota artifacts: paths: - .pioenvs/*/firmware.* diff --git a/platformio.ini b/platformio.ini index 96adc9e..6bb5b8c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,5 +74,5 @@ upload_flags = --auth=f4ncy monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} - https://gitlab.com/wirelos/painlessMesh.git#feature/ota + painlessMesh ArduinoOTA \ No newline at end of file From c127346394cec1a8c26d786e83a1f2c649c52b77 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 17 Aug 2018 14:29:18 +0200 Subject: [PATCH 20/88] add base MeshSprocket with OTA enabled --- platformio.ini | 3 +- src/Sprocket.h | 3 +- src/base/MeshSprocket.h | 46 ++++++++++++++++++++++++++++++ src/examples/mesh/MeshApp.cpp | 41 ++++++++++++-------------- src/examples/mesh/config.h | 6 +++- src/examples/mesh/main.cpp | 5 +++- src/examples/ota/SprocketOTA.cpp | 30 ++++++------------- src/{ => plugins}/OtaTcpPlugin.cpp | 0 8 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 src/base/MeshSprocket.h rename src/{ => plugins}/OtaTcpPlugin.cpp (100%) diff --git a/platformio.ini b/platformio.ini index 6bb5b8c..11ad2ab 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ lib_deps = ESPAsyncTCP TaskScheduler SPIFFS - + ;[env:build] ;src_filter = +<*> - ;platform = ${common.platform} @@ -74,5 +74,6 @@ 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 diff --git a/src/Sprocket.h b/src/Sprocket.h index de48cf2..f9a2532 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -34,8 +34,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*) { return this; } - // TODO bind onMessage to network->onReceive - //virtual void onMessage(uint32_t from, String &msg) {}; + virtual void dispatch( uint32_t from, String &msg ) {} }; #endif \ No newline at end of file diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h new file mode 100644 index 0000000..085bd06 --- /dev/null +++ b/src/base/MeshSprocket.h @@ -0,0 +1,46 @@ +#ifndef __MESH_SPROCKET__ +#define __MESH_SPROCKET__ + +#define DEBUG_ESP_OTA +#include +#include +#include +#include +#include "config.h" + +using namespace std; +using namespace std::placeholders; + +class MeshSprocket : public Sprocket { + public: + MeshNet* net; + OtaTcpPlugin* ota; + + MeshSprocket(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { + ota = new OtaTcpPlugin(otaCfg); + } + + Sprocket* activate(Scheduler* scheduler, Network* network) { + net = static_cast(network); + net->onReceive(bind(&MeshSprocket::dispatch,this, _1, _2)); + // enable plugins + ota->enable(scheduler, network); + 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 ) { + // TODO handle OTA before passing to onMessage + onMessage(from, msg); + } + + void loop() { + net->update(); + scheduler.execute(); + } +}; + +#endif \ No newline at end of file diff --git a/src/examples/mesh/MeshApp.cpp b/src/examples/mesh/MeshApp.cpp index b41f39f..81150a6 100644 --- a/src/examples/mesh/MeshApp.cpp +++ b/src/examples/mesh/MeshApp.cpp @@ -2,42 +2,37 @@ #define __MESH_APP__ #include -#include +#include #include using namespace std; using namespace std::placeholders; -class MeshApp : public Sprocket { +class MeshApp : public MeshSprocket { public: - Task someTask; - MeshNet* net; - MeshApp(SprocketConfig cfg) : Sprocket(cfg) {} + Task heartbeatTask; + + MeshApp(SprocketConfig cfg, OtaConfig otaCfg) : MeshSprocket(cfg, otaCfg) { + + } + Sprocket* activate(Scheduler* scheduler, Network* network) { - net = static_cast(network); - net->onReceive(bind(&MeshApp::dispatch,this, _1, _2)); + // call parent method that enables dispatching and plugins + MeshSprocket::activate(scheduler, network); + // 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(); + heartbeatTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MeshApp::heartbeat, this, net)); + addTask(heartbeatTask); + return this; - } using Sprocket::activate; + } using MeshSprocket::activate; void heartbeat(MeshNet* network){ String msg = "{ \"payload \": 1 }"; - network->broadcast(msg); + network->mesh.sendBroadcast(msg, true); } - - void dispatch( uint32_t from, String &msg ) { - Serial.printf("MeshApp: 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(); + void onMessage( uint32_t from, String &msg ) { + Serial.printf("MeshApp onMessage: received from %u msg=%s\n", from, msg.c_str()); } }; diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index 6b44a23..ab23017 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -18,7 +18,11 @@ #define STATION_SSID "Th1ngs4P" #define STATION_PASSWORD "th3r31sn0sp00n" #define HOSTNAME "mesh-node" -#define MESH_DEBUG_TYPES ERROR | CONNECTION | COMMUNICATION +#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION //ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE +// OTA config +#define OTA_PORT 8266 +#define OTA_PASSWORD "" + #endif \ No newline at end of file diff --git a/src/examples/mesh/main.cpp b/src/examples/mesh/main.cpp index bd04b7c..4361b38 100644 --- a/src/examples/mesh/main.cpp +++ b/src/examples/mesh/main.cpp @@ -8,7 +8,10 @@ MeshNet net({ STATION_SSID, STATION_PASSWORD, HOSTNAME, MESH_DEBUG_TYPES }); -MeshApp sprocket({ STARTUP_DELAY, SERIAL_BAUD_RATE }); +MeshApp sprocket( + { STARTUP_DELAY, SERIAL_BAUD_RATE }, + { OTA_PORT, OTA_PASSWORD } +); void setup() { sprocket.join(net); diff --git a/src/examples/ota/SprocketOTA.cpp b/src/examples/ota/SprocketOTA.cpp index ebe4776..8d110ab 100644 --- a/src/examples/ota/SprocketOTA.cpp +++ b/src/examples/ota/SprocketOTA.cpp @@ -2,38 +2,26 @@ #define __MESH_APP__ #define DEBUG_ESP_OTA -#include -#include #include -#include +#include #include "config.h" using namespace std; using namespace std::placeholders; -class SprocketOTA : public Sprocket { +// MeshSprocket base class integrates OTA plugin by default +class SprocketOTA : public MeshSprocket { public: - MeshNet* net; - OtaTcpPlugin* ota; + SprocketOTA(SprocketConfig cfg, OtaConfig otaCfg) : MeshSprocket(cfg, otaCfg) {} - SprocketOTA(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { - ota = new OtaTcpPlugin(otaCfg); - } - Sprocket* activate(Scheduler* scheduler, Network* network) { - net = static_cast(network); - net->onReceive(bind(&SprocketOTA::dispatch,this, _1, _2)); - ota->enable(scheduler, network); + // call parent method that enables dispatching and plugins + MeshSprocket::activate(scheduler, network); return this; - } using Sprocket::activate; + } using MeshSprocket::activate; - void dispatch( uint32_t from, String &msg ) { - Serial.printf("Sprocket: received from %u msg=%s\n", from, msg.c_str()); - } - - void loop() { - net->update(); - scheduler.execute(); + void onMessage( uint32_t from, String &msg ) { + Serial.printf("SprocketOTA onMessage: received from %u msg=%s\n", from, msg.c_str()); } }; diff --git a/src/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp similarity index 100% rename from src/OtaTcpPlugin.cpp rename to src/plugins/OtaTcpPlugin.cpp From 77f1b278f4a045e9d33eaf8a4aa597081ed66002 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Sun, 19 Aug 2018 16:10:56 +0200 Subject: [PATCH 21/88] refactoring --- platformio.ini | 2 + src/Plugin.h | 5 ++- src/base/MeshMessage.h | 77 +++++++++++++++++++++++++++++++++++ src/base/MeshSprocket.h | 35 ++++++++++++++-- src/examples/mesh/MeshApp.cpp | 9 +++- src/examples/mesh/config.h | 8 ++-- src/plugins/OtaTcpPlugin.cpp | 33 +++++++++------ 7 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 src/base/MeshMessage.h diff --git a/platformio.ini b/platformio.ini index 11ad2ab..2ebb608 100644 --- a/platformio.ini +++ b/platformio.ini @@ -53,6 +53,8 @@ monitor_baud = ${common.monitor_baud} framework = ${common.framework} lib_deps = ${common.lib_deps} painlessMesh + ESP8266mDNS + ArduinoOTA [env:meshMqttBridge] src_filter = +<*> - + diff --git a/src/Plugin.h b/src/Plugin.h index c331e60..c046e3e 100644 --- a/src/Plugin.h +++ b/src/Plugin.h @@ -3,13 +3,16 @@ #include #include +#include class Plugin { protected: Scheduler* scheduler; public: - virtual void enable(Scheduler*, Network*); + virtual void setup(Scheduler*, Network*); + virtual void enable(); virtual void disable(); + virtual void onMessage(MeshMessage msg); }; #endif \ No newline at end of file diff --git a/src/base/MeshMessage.h b/src/base/MeshMessage.h new file mode 100644 index 0000000..6eb8240 --- /dev/null +++ b/src/base/MeshMessage.h @@ -0,0 +1,77 @@ +#ifndef __MESH_MESSAGE__ +#define __MESH_MESSAGE__ + +#include +#include + +#define JSON_DOMAIN "domain" +#define JSON_FROM "from" +#define JSON_TO "target" +#define JSON_MSG "msg" + +struct MeshMessage { + String domain; + String to; + String from; + String msg; + enum MeshMessageType { NONE, SYSTEM, 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_MSG); + }; + 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; + 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. + void fromJsonObject(JsonObject& json){ + if(!verifyJsonObject(json)){ + Serial.println("ERROR: cannot parse MeshMessage JSON object"); + valid = 0; + //return; + } + domain = getAttrFromJson(json, JSON_DOMAIN); + to = getAttrFromJson(json, JSON_TO); + from = getAttrFromJson(json, JSON_FROM); + msg = getAttrFromJson(json, JSON_MSG); + type = (MeshMessageType) getIntAttrFromJson(json, "type"); + valid = 1; + }; + // Parse a json string and map parsed object + void fromJsonString(String& str){ + //StaticJsonBuffer<200> jsonBuffer; + DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); + JsonObject& json = jsonBuffer.parseObject(str); + fromJsonObject(json); + }; +}; + +#endif \ No newline at end of file diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index 085bd06..9bed0ab 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -2,9 +2,11 @@ #define __MESH_SPROCKET__ #define DEBUG_ESP_OTA +#include #include #include #include +#include #include #include "config.h" @@ -14,17 +16,37 @@ using namespace std::placeholders; class MeshSprocket : public Sprocket { public: MeshNet* net; - OtaTcpPlugin* ota; + std::vector plugins; MeshSprocket(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { - ota = new OtaTcpPlugin(otaCfg); + addPlugin(new OtaTcpPlugin(otaCfg)); } + void addPlugin(Plugin* p){ + plugins.reserve(1); + plugins.push_back(p); + } + + void setupPlugins(Scheduler* scheduler, Network* network){ + for(Plugin* p : plugins){ + p->setup(scheduler, network); + } + } + + void dispatchMessageToPlugins(MeshMessage msg){ + for(Plugin* p : plugins){ + if(msg.type != MeshMessage::NONE){ + Serial.println("dispatch to plugins"); + p->onMessage(msg); + } + } + } + Sprocket* activate(Scheduler* scheduler, Network* network) { net = static_cast(network); net->onReceive(bind(&MeshSprocket::dispatch,this, _1, _2)); - // enable plugins - ota->enable(scheduler, network); + // setup plugins + setupPlugins(scheduler, network); return this; } using Sprocket::activate; @@ -34,6 +56,11 @@ class MeshSprocket : public Sprocket { void dispatch( uint32_t from, String &msg ) { // TODO handle OTA before passing to onMessage + MeshMessage mMsg; + mMsg.fromJsonString(msg); + if(mMsg.valid){ + dispatchMessageToPlugins(mMsg); + } onMessage(from, msg); } diff --git a/src/examples/mesh/MeshApp.cpp b/src/examples/mesh/MeshApp.cpp index 81150a6..cb44b72 100644 --- a/src/examples/mesh/MeshApp.cpp +++ b/src/examples/mesh/MeshApp.cpp @@ -28,8 +28,13 @@ class MeshApp : public MeshSprocket { } using MeshSprocket::activate; void heartbeat(MeshNet* network){ - String msg = "{ \"payload \": 1 }"; - network->mesh.sendBroadcast(msg, true); + MeshMessage msg; // = { "wirelos", "broadcast", "local", "alive", 0, }; + msg.domain = "wirelos"; + msg.to = "broadcast"; + msg.msg = "alive"; + String msgStr = msg.toJsonString(); + //String msg = "{ \"domain\": \"wirelos\",\"from\": \"0\",\"target\": \"broadcast\", \"msg \": \"alive\" }"; + 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()); diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index ab23017..f958da0 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -10,15 +10,15 @@ #define STARTUP_DELAY 3000 // Mesh config -#define STATION_MODE 0 +#define STATION_MODE 1 #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 STATION_SSID "tErAx1d" +#define STATION_PASSWORD "ramalamadingdong" #define HOSTNAME "mesh-node" -#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION +#define MESH_DEBUG_TYPES ERROR | STARTUP //ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE // OTA config diff --git a/src/plugins/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp index 6ff0a1b..4f2d73f 100644 --- a/src/plugins/OtaTcpPlugin.cpp +++ b/src/plugins/OtaTcpPlugin.cpp @@ -18,21 +18,31 @@ class OtaTcpPlugin : public Plugin { private: OtaConfig config; Task otaTask; + MeshNet* network; public: OtaTcpPlugin(OtaConfig cfg){ config = cfg; + } - void enable(Scheduler* userScheduler, Network* network){ - scheduler = userScheduler; - - // connect to network - if(!network->isConnected()){ - // TODO when config is refactored, cast is not necessary anymore - static_cast(network)->config.stationMode = 1; - network->connectStation(); - // TODO set service message to mesh to announce station IP - } + void connectUpdateNetwork(Network* network) { + // if(!network->isConnected()){ + // static_cast(network)->config.stationMode = 1; + // } + network->connectStation(); + } + void enable() { + ArduinoOTA.begin(); + otaTask.enable(); + } + void onMessage(MeshMessage msg) { + //enable(); + Serial.println("OTA msg received"); + } + void setup(Scheduler* userScheduler, Network* network){ + // connect done in network class + //connectUpdateNetwork(network); // setup task + scheduler = userScheduler; otaTask.set(TASK_MILLISECOND * 100, TASK_FOREVER, [](){ ArduinoOTA.handle(); }); @@ -62,8 +72,7 @@ class OtaTcpPlugin : public Plugin { else if (error == OTA_RECEIVE_ERROR) Serial.println("OTA: Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("OTA: End Failed"); }); - ArduinoOTA.begin(); - otaTask.enable(); + enable(); } void disable(){ otaTask.disable(); From 426d495fd97dd8f06edefffa88833ef95ebebc62 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 22 Aug 2018 11:09:42 +0200 Subject: [PATCH 22/88] get OTA enabling working --- platformio.ini | 1 + src/MeshNet.cpp | 9 +++++---- src/MeshNet.h | 2 +- src/base/MeshMessage.h | 26 +++++++++++++++----------- src/base/MeshSprocket.h | 8 +++----- src/examples/mesh/MeshApp.cpp | 4 ++-- src/examples/mesh/README.md | 7 +++++++ src/examples/mesh/config.h | 2 +- src/examples/mesh/main.cpp | 2 +- src/plugins/OtaTcpPlugin.cpp | 23 +++++++++++++---------- 10 files changed, 49 insertions(+), 35 deletions(-) create mode 100644 src/examples/mesh/README.md diff --git a/platformio.ini b/platformio.ini index 2ebb608..c8231b5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,6 +55,7 @@ lib_deps = ${common.lib_deps} painlessMesh ESP8266mDNS ArduinoOTA +upload_port = 192.168.1.247 [env:meshMqttBridge] src_filter = +<*> - + diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 5da7068..2c35f0c 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -15,13 +15,14 @@ Network* MeshNet::init(){ mesh.onChangedConnections(bind(&MeshNet::changedConnectionCallback, this)); mesh.onNodeTimeAdjusted(bind(&MeshNet::nodeTimeAdjustedCallback, this, _1)); - connectStation(); + connectStation(config.stationMode); return this; } -Network* MeshNet::connectStation() { - if(config.stationMode){ - Serial.println("connect station"); +Network* MeshNet::connectStation(int doConnect) { + Serial.println("connect station?"); + if(doConnect){ + Serial.println("connect station!"); mesh.stationManual(config.stationSSID, config.stationPassword); mesh.setHostname(config.hostname); } diff --git a/src/MeshNet.h b/src/MeshNet.h index 0110575..97ce245 100644 --- a/src/MeshNet.h +++ b/src/MeshNet.h @@ -34,7 +34,7 @@ class MeshNet : public Network { MeshNet(MeshConfig cfg); Network* init(); - Network* connectStation(); + Network* connectStation(int); void update(); void newConnectionCallback(uint32_t nodeId); diff --git a/src/base/MeshMessage.h b/src/base/MeshMessage.h index 6eb8240..8d5b080 100644 --- a/src/base/MeshMessage.h +++ b/src/base/MeshMessage.h @@ -8,24 +8,26 @@ #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, OTA } type; + enum MeshMessageType { NONE, SYSTEM, APP, OTA } type; int valid = 0; // ------------------------------------------------------------------------------------------ - void init() { - //from = reinterpret_cast(ESP.getChipId()); - } + //void init() { + // from = reinterpret_cast(ESP.getChipId()); + //} int verifyJsonObject(JsonObject& json){ - return json.success() + return json.success(); //&& json.containsKey(JSON_DOMAIN) //&& json.containsKey(JSON_TO) //&& json.containsKey(JSON_FROM) - && json.containsKey(JSON_MSG); + //&& json.containsKey(JSON_TYPE) + // && json.containsKey(JSON_MSG); // msg is only tx'ed from mqtt }; String toJsonString(){ //StaticJsonBuffer<200> jsonBuffer; @@ -35,6 +37,7 @@ struct MeshMessage { root[JSON_TO] = to; root[JSON_FROM] = from; root[JSON_MSG] = msg; + root[JSON_TYPE] = type; String jsonString; root.printTo(jsonString); return jsonString; @@ -52,25 +55,26 @@ struct MeshMessage { return 0; } // Map a json object to this struct. - void fromJsonObject(JsonObject& json){ + int fromJsonObject(JsonObject& json){ if(!verifyJsonObject(json)){ Serial.println("ERROR: cannot parse MeshMessage JSON object"); valid = 0; - //return; + 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, "type"); + type = (MeshMessageType) getIntAttrFromJson(json, JSON_TYPE); valid = 1; + return valid; }; // Parse a json string and map parsed object - void fromJsonString(String& str){ + int fromJsonString(String& str){ //StaticJsonBuffer<200> jsonBuffer; DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); JsonObject& json = jsonBuffer.parseObject(str); - fromJsonObject(json); + return fromJsonObject(json); }; }; diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index 9bed0ab..790323d 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -34,8 +34,8 @@ class MeshSprocket : public Sprocket { } void dispatchMessageToPlugins(MeshMessage msg){ - for(Plugin* p : plugins){ - if(msg.type != MeshMessage::NONE){ + if(msg.type != MeshMessage::NONE){ // baaaa + for(Plugin* p : plugins){ Serial.println("dispatch to plugins"); p->onMessage(msg); } @@ -55,10 +55,8 @@ class MeshSprocket : public Sprocket { }; void dispatch( uint32_t from, String &msg ) { - // TODO handle OTA before passing to onMessage MeshMessage mMsg; - mMsg.fromJsonString(msg); - if(mMsg.valid){ + if(mMsg.fromJsonString(msg)){ dispatchMessageToPlugins(mMsg); } onMessage(from, msg); diff --git a/src/examples/mesh/MeshApp.cpp b/src/examples/mesh/MeshApp.cpp index cb44b72..e738a7f 100644 --- a/src/examples/mesh/MeshApp.cpp +++ b/src/examples/mesh/MeshApp.cpp @@ -32,9 +32,9 @@ class MeshApp : public MeshSprocket { msg.domain = "wirelos"; msg.to = "broadcast"; msg.msg = "alive"; + msg.type = MeshMessage::APP; String msgStr = msg.toJsonString(); - //String msg = "{ \"domain\": \"wirelos\",\"from\": \"0\",\"target\": \"broadcast\", \"msg \": \"alive\" }"; - network->mesh.sendBroadcast(msgStr, true); + 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()); diff --git a/src/examples/mesh/README.md b/src/examples/mesh/README.md new file mode 100644 index 0000000..53e7306 --- /dev/null +++ b/src/examples/mesh/README.md @@ -0,0 +1,7 @@ +# Mesh Sprocket Example + +## OTA +mosquitto_sub -h citadel.lan -p 1883 -v -t '#' + +Enable OTA: +mosquitto_pub -h citadel.lan -p 1883 -t '/down/wirelos/gateway' -m '{"target":"broadcast", "domain": "wirelos", "msg": {"target":"broadcast", "type": 3, msg: "OTA"} }' diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index f958da0..7f87652 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -10,7 +10,7 @@ #define STARTUP_DELAY 3000 // Mesh config -#define STATION_MODE 1 +#define SPROCKET_MODE 0 #define WIFI_CHANNEL 11 #define MESH_PORT 5555 #define MESH_PREFIX "whateverYouLike" diff --git a/src/examples/mesh/main.cpp b/src/examples/mesh/main.cpp index 4361b38..b8b8749 100644 --- a/src/examples/mesh/main.cpp +++ b/src/examples/mesh/main.cpp @@ -3,7 +3,7 @@ #include "MeshApp.cpp" MeshNet net({ - STATION_MODE, WIFI_CHANNEL, + SPROCKET_MODE, WIFI_CHANNEL, MESH_PORT, MESH_PREFIX, MESH_PASSWORD, STATION_SSID, STATION_PASSWORD, HOSTNAME, MESH_DEBUG_TYPES diff --git a/src/plugins/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp index 4f2d73f..25cc569 100644 --- a/src/plugins/OtaTcpPlugin.cpp +++ b/src/plugins/OtaTcpPlugin.cpp @@ -18,29 +18,33 @@ class OtaTcpPlugin : public Plugin { private: OtaConfig config; Task otaTask; - MeshNet* network; + MeshNet* net; public: OtaTcpPlugin(OtaConfig cfg){ config = cfg; } - void connectUpdateNetwork(Network* network) { - // if(!network->isConnected()){ - // static_cast(network)->config.stationMode = 1; - // } - network->connectStation(); + void connectUpdateNetwork() { + Serial.println("OTA connect to update-network"); + net->connectStation(1); } void enable() { + Serial.println("OTA enable"); ArduinoOTA.begin(); otaTask.enable(); } void onMessage(MeshMessage msg) { - //enable(); - Serial.println("OTA msg received"); + if(msg.type == MeshMessage::OTA){ + Serial.println("OTA msg received"); + connectUpdateNetwork(); + enable(); + //net->mesh.sendBroadcast("my ip:" + WiFi.localIP().toString(), true); + } } void setup(Scheduler* userScheduler, Network* network){ - // connect done in network class + // connect done in network class? //connectUpdateNetwork(network); + net = static_cast(network); // setup task scheduler = userScheduler; otaTask.set(TASK_MILLISECOND * 100, TASK_FOREVER, [](){ @@ -72,7 +76,6 @@ class OtaTcpPlugin : public Plugin { else if (error == OTA_RECEIVE_ERROR) Serial.println("OTA: Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("OTA: End Failed"); }); - enable(); } void disable(){ otaTask.disable(); From 5334d5843350fca4724bc7022cd07f933dfb24cf Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Fri, 24 Aug 2018 17:42:35 +0200 Subject: [PATCH 23/88] move plugins to sprocket --- platformio.ini | 2 +- src/JsonStruct.h | 43 ++++++++++++++++++++++ src/MeshConfig.h | 18 +++++++++ src/MeshNet.cpp | 3 +- src/MeshNet.h | 14 +------ src/Sprocket.cpp | 26 +++++++++++++ src/Sprocket.h | 15 +++++--- src/SprocketConfig.h | 9 +++++ src/base/MeshSprocket.h | 25 +------------ src/base/MeshSprocketConfig.h | 69 +++++++++++++++++++++++++++++++++++ src/plugins/OtaTcpPlugin.cpp | 3 ++ 11 files changed, 182 insertions(+), 45 deletions(-) create mode 100644 src/JsonStruct.h create mode 100644 src/MeshConfig.h create mode 100644 src/SprocketConfig.h create mode 100644 src/base/MeshSprocketConfig.h diff --git a/platformio.ini b/platformio.ini index c8231b5..78a249a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,7 +55,7 @@ lib_deps = ${common.lib_deps} painlessMesh ESP8266mDNS ArduinoOTA -upload_port = 192.168.1.247 +;upload_port = 192.168.1.247 [env:meshMqttBridge] src_filter = +<*> - + diff --git a/src/JsonStruct.h b/src/JsonStruct.h new file mode 100644 index 0000000..5a65912 --- /dev/null +++ b/src/JsonStruct.h @@ -0,0 +1,43 @@ +#ifndef __JSON_STRUCT__ +#define __JSON_STRUCT__ + +#include +#include + + +struct JsonStruct { + SprocketConfig soricketConfig; + MeshConfig meshConfig; + int valid = 0; + + // ------------------------------------------------------------------------------------------ + + virtual int verifyJsonObject(JsonObject& json){ + return json.success(); + //&& json.containsKey(JSON_DOMAIN) + }; + virtual String toJsonString(); + 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. + virtual int fromJsonObject(JsonObject& json); + // 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/MeshConfig.h b/src/MeshConfig.h new file mode 100644 index 0000000..1a1d6e0 --- /dev/null +++ b/src/MeshConfig.h @@ -0,0 +1,18 @@ +#ifndef __MESHCONFIG__ +#define __MESHCONFIG__ + +// FIXME non-mesh config should have it's own struct +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; +}; + + +#endif \ No newline at end of file diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 2c35f0c..7c9c30f 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -20,9 +20,8 @@ Network* MeshNet::init(){ return this; } Network* MeshNet::connectStation(int doConnect) { - Serial.println("connect station?"); if(doConnect){ - Serial.println("connect station!"); + Serial.println("connect station"); mesh.stationManual(config.stationSSID, config.stationPassword); mesh.setHostname(config.hostname); } diff --git a/src/MeshNet.h b/src/MeshNet.h index 97ce245..07d1e4f 100644 --- a/src/MeshNet.h +++ b/src/MeshNet.h @@ -10,23 +10,11 @@ #include #include #include "Network.h" +#include "MeshConfig.h" using namespace std; using namespace std::placeholders; -// FIXME non-mesh config should have it's own struct -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; diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index aa3751b..55eab57 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -18,6 +18,11 @@ Sprocket* Sprocket::init(SprocketConfig cfg){ Sprocket* Sprocket::activate() { return activate(&scheduler); } +Sprocket* Sprocket::activate(Scheduler* scheduler, Network* network) { + // setup plugins + setupPlugins(scheduler, network); + return this; +} Sprocket* Sprocket::join(Network& net){ Serial.println("join network"); @@ -35,4 +40,25 @@ Sprocket* Sprocket::addTask(Task& tsk){ void Sprocket::loop(){ scheduler.execute(); +} + + +void Sprocket::addPlugin(Plugin* p){ + plugins.reserve(1); + plugins.push_back(p); +} + +void Sprocket::setupPlugins(Scheduler* scheduler, Network* network){ + for(Plugin* p : plugins){ + p->setup(scheduler, network); + } +} + +void Sprocket::dispatchMessageToPlugins(MeshMessage msg){ + if(msg.type != MeshMessage::NONE){ // baaaa + for(Plugin* p : plugins){ + Serial.println("dispatch to plugins"); + p->onMessage(msg); + } + } } \ No newline at end of file diff --git a/src/Sprocket.h b/src/Sprocket.h index f9a2532..9d3d715 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -3,11 +3,14 @@ #include #include +#include #include "FS.h" #ifdef ESP32 #include "SPIFFS.h" #endif #include "Network.h" +#include "SprocketConfig.h" +#include "Plugin.h" using namespace std; using namespace std::placeholders; @@ -15,16 +18,12 @@ using namespace std::placeholders; // FIXME move to some global fnc lib #define ARRAY_LENGTH(array) sizeof(array)/sizeof(array[0]) -struct SprocketConfig { - int startupDelay; - int serialBaudRate; -}; - class Sprocket { protected: Scheduler scheduler; public: SprocketConfig config; + std::vector plugins; Sprocket(); Sprocket(SprocketConfig); Sprocket* init(SprocketConfig); @@ -33,8 +32,12 @@ class Sprocket { virtual void loop(); virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } - virtual Sprocket* activate(Scheduler*, Network*) { return this; } + virtual Sprocket* activate(Scheduler*, Network*); virtual void dispatch( uint32_t from, String &msg ) {} + + void addPlugin(Plugin* p); + void setupPlugins(Scheduler* scheduler, Network* network); + void dispatchMessageToPlugins(MeshMessage msg); }; #endif \ No newline at end of file diff --git a/src/SprocketConfig.h b/src/SprocketConfig.h new file mode 100644 index 0000000..645cf86 --- /dev/null +++ b/src/SprocketConfig.h @@ -0,0 +1,9 @@ +#ifndef __SPROCKET_CONFIG__ +#define __SPROCKET_CONFIG__ + +struct SprocketConfig { + int startupDelay; + int serialBaudRate; +}; + +#endif \ No newline at end of file diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index 790323d..5fa77bf 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "config.h" @@ -16,37 +17,15 @@ using namespace std::placeholders; class MeshSprocket : public Sprocket { public: MeshNet* net; - std::vector plugins; MeshSprocket(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { addPlugin(new OtaTcpPlugin(otaCfg)); } - - void addPlugin(Plugin* p){ - plugins.reserve(1); - plugins.push_back(p); - } - - void setupPlugins(Scheduler* scheduler, Network* network){ - for(Plugin* p : plugins){ - p->setup(scheduler, network); - } - } - - void dispatchMessageToPlugins(MeshMessage msg){ - if(msg.type != MeshMessage::NONE){ // baaaa - for(Plugin* p : plugins){ - Serial.println("dispatch to plugins"); - p->onMessage(msg); - } - } - } Sprocket* activate(Scheduler* scheduler, Network* network) { + Sprocket::activate(scheduler, network); net = static_cast(network); net->onReceive(bind(&MeshSprocket::dispatch,this, _1, _2)); - // setup plugins - setupPlugins(scheduler, network); return this; } using Sprocket::activate; diff --git a/src/base/MeshSprocketConfig.h b/src/base/MeshSprocketConfig.h new file mode 100644 index 0000000..c77d720 --- /dev/null +++ b/src/base/MeshSprocketConfig.h @@ -0,0 +1,69 @@ +#ifndef __MESH_SPROCKET_CONFIG__ +#define __MESH_SPROCKET_CONFIG__ + +#include +#include +#include +#include "MeshConfig.h" +#include "SprocketConfig.h" +#include "JsonStruct.h" + +#define JSON_stationMode "stationMode" +#define JSON_channel "channel" +#define JSON_meshPort "meshPort" +#define JSON_meshSSID "meshSSID" +#define JSON_meshPassword "meshPassword" +#define JSON_stationSSID "stationSSID" +#define JSON_stationPassword "stationPassword" +#define JSON_hostname "hostname" + +#define JSON_startupDelay "startupDelay" +#define JSON_serialBaudRate "serialBaudRate" + +struct MeshSprocketConfig : public JsonStruct { + SprocketConfig sprocket; + MeshConfig mesh; + int valid = 0; + + // ------------------------------------------------------------------------------------------ + String toJsonString(){ + //StaticJsonBuffer<200> jsonBuffer; + DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); + JsonObject& root = jsonBuffer.createObject(); + root[JSON_stationMode] = mesh.stationMode; + root[JSON_channel] = mesh.channel; + root[JSON_meshPort] = mesh.meshPort; + root[JSON_meshSSID] = mesh.meshSSID; + root[JSON_meshPassword] = mesh.meshPassword; + root[JSON_stationSSID] = mesh.stationSSID; + root[JSON_stationPassword] = mesh.stationPassword; + root[JSON_hostname] = mesh.hostname; + root[JSON_startupDelay] = sprocket.startupDelay; + root[JSON_serialBaudRate] = sprocket.serialBaudRate; + String jsonString; + root.printTo(jsonString); + return jsonString; + } + // Map a json object to this struct. + int fromJsonObject(JsonObject& json){ + if(!verifyJsonObject(json)){ + Serial.println("ERROR: cannot parse JSON object"); + valid = 0; + return valid; + } + mesh.stationMode = getIntAttrFromJson(json, JSON_stationMode); + mesh.channel = getIntAttrFromJson(json, JSON_channel); + mesh.meshPort = getIntAttrFromJson(json, JSON_meshPort); + mesh.meshSSID = getAttrFromJson(json, JSON_meshSSID).c_str(); + mesh.meshPassword = getAttrFromJson(json, JSON_meshPassword).c_str(); + mesh.stationSSID = getAttrFromJson(json, JSON_stationSSID).c_str(); + mesh.stationPassword = getAttrFromJson(json, JSON_stationPassword).c_str(); + mesh.hostname = getAttrFromJson(json, JSON_hostname).c_str(); + sprocket.startupDelay = getIntAttrFromJson(json, JSON_startupDelay); + sprocket.serialBaudRate = getIntAttrFromJson(json, JSON_serialBaudRate); + valid = 1; + return valid; + }; +}; + +#endif \ No newline at end of file diff --git a/src/plugins/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp index 25cc569..f0c9cd1 100644 --- a/src/plugins/OtaTcpPlugin.cpp +++ b/src/plugins/OtaTcpPlugin.cpp @@ -36,9 +36,12 @@ class OtaTcpPlugin : public Plugin { void onMessage(MeshMessage msg) { if(msg.type == MeshMessage::OTA){ Serial.println("OTA msg received"); + WiFi.disconnect(); connectUpdateNetwork(); enable(); //net->mesh.sendBroadcast("my ip:" + WiFi.localIP().toString(), true); + //net->mesh.sendBroadcast("gw ip:" + WiFi.gatewayIP().toString(), true); + WiFi.gatewayIP().printTo(Serial); } } void setup(Scheduler* userScheduler, Network* network){ From b67674672317a3b0c8a1a30953f452134f0cc4b7 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Sun, 26 Aug 2018 18:58:37 +0200 Subject: [PATCH 24/88] read config from json on SPIFFS --- data/config.json | 10 +++++ src/JsonStruct.h | 65 +++++++++++++++++++++++++++++---- src/MeshNet.cpp | 17 +++++++-- src/MeshNet.h | 6 +-- src/Network.h | 1 - src/Sprocket.cpp | 6 +++ src/Sprocket.h | 2 +- src/base/MeshSprocket.h | 5 +-- src/base/MeshSprocketConfig.h | 69 ++++++++++++++--------------------- src/examples/mesh/config.h | 6 +-- src/examples/mesh/main.cpp | 9 +++-- 11 files changed, 129 insertions(+), 67 deletions(-) create mode 100644 data/config.json diff --git a/data/config.json b/data/config.json new file mode 100644 index 0000000..0bb8956 --- /dev/null +++ b/data/config.json @@ -0,0 +1,10 @@ +{ + "stationMode": 1, + "channel": 11, + "meshPort": 5555, + "meshSSID": "MyMesh", + "meshPassword": "th3r31sn0sp00n", + "stationSSID": "MyAP", + "stationPassword": "myApPassword", + "hostname": "mesh-node" +} \ No newline at end of file diff --git a/src/JsonStruct.h b/src/JsonStruct.h index 5a65912..9924a08 100644 --- a/src/JsonStruct.h +++ b/src/JsonStruct.h @@ -12,16 +12,29 @@ struct JsonStruct { // ------------------------------------------------------------------------------------------ + virtual void mapJsonObject(JsonObject& json); + virtual void fromJsonObject(JsonObject& json); virtual int verifyJsonObject(JsonObject& json){ return json.success(); //&& json.containsKey(JSON_DOMAIN) }; - virtual String toJsonString(); + String toJsonString(){ + //StaticJsonBuffer<200> StringjsonBuffer; + DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); + JsonObject& root = jsonBuffer.createObject(); + mapJsonObject(root); + String jsonString; + root.printTo(jsonString); + return jsonString; + } + String getAttrFromJson(JsonObject& json, const char* attr){ - if(json.containsKey(attr)){ - return json[attr]; + if(json.containsKey(String(attr))){ + const char *value = json[attr]; + return String(value); + //return json[attr]; } - return ""; + return "no value"; } int getIntAttrFromJson(JsonObject& json, const char* attr){ if(json.containsKey(attr)){ @@ -30,14 +43,52 @@ struct JsonStruct { return 0; } // Map a json object to this struct. - virtual int fromJsonObject(JsonObject& json); // Parse a json string and map parsed object - int fromJsonString(String& str){ + void fromJsonString(String& str){ //StaticJsonBuffer<200> jsonBuffer; DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); JsonObject& json = jsonBuffer.parseObject(str); - return fromJsonObject(json); + valid = verifyJsonObject(json); + if(valid) { + fromJsonObject(json); + } }; + + void fromFile(const char* path) { + Serial.println("read json"); + File configFile = SPIFFS.open(path, "r"); + String cfgFileStr = configFile.readString(); + + // Allocate a buffer to store contents of the file. + //size_t size = configFile.size(); + //std::unique_ptr buf(new char[size]); + //configFile.readBytes(buf.get(), size); + //StaticJsonBuffer<1024> jsonBuffer; + //JsonObject& json = jsonBuffer.parseObject(buf.get()); + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(cfgFileStr); + + valid = verifyJsonObject(json); + + if(configFile) { + Serial.println("map json object"); + fromJsonObject(json); + } + if(!valid){ + Serial.println("read json failed"); + } + 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 \ No newline at end of file diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 7c9c30f..3152fda 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -1,12 +1,23 @@ #include "MeshNet.h" MeshNet::MeshNet(MeshConfig cfg) : Network() { - config = cfg; + config.stationMode = cfg.stationMode; + config.channel = cfg.channel; + config.meshPort = cfg.meshPort; + config.meshSSID = cfg.meshSSID; + config.meshPassword = cfg.meshPassword; + config.stationSSID = cfg.stationSSID; + config.stationPassword = cfg.stationPassword; + config.hostname = cfg.hostname; + config.debugTypes = cfg.debugTypes; } Network* MeshNet::init(){ - + Serial.println("init mesh"); + config.fromFile("/config.json"); + Serial.println(config.meshSSID); + //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 ); @@ -23,7 +34,7 @@ Network* MeshNet::connectStation(int doConnect) { if(doConnect){ Serial.println("connect station"); mesh.stationManual(config.stationSSID, config.stationPassword); - mesh.setHostname(config.hostname); + mesh.setHostname(config.hostname.c_str()); } return this; } diff --git a/src/MeshNet.h b/src/MeshNet.h index 07d1e4f..6996b1c 100644 --- a/src/MeshNet.h +++ b/src/MeshNet.h @@ -11,6 +11,7 @@ #include #include "Network.h" #include "MeshConfig.h" +#include "base/MeshSprocketConfig.h" using namespace std; using namespace std::placeholders; @@ -18,12 +19,11 @@ using namespace std::placeholders; class MeshNet : public Network { public: painlessMesh mesh; - MeshConfig config; - + MeshSprocketConfig config; MeshNet(MeshConfig cfg); Network* init(); Network* connectStation(int); - + void configure(MeshSprocketConfig cfg); void update(); void newConnectionCallback(uint32_t nodeId); void changedConnectionCallback(); diff --git a/src/Network.h b/src/Network.h index 93a65ea..d559959 100644 --- a/src/Network.h +++ b/src/Network.h @@ -9,7 +9,6 @@ typedef std::function 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(); }; diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index 55eab57..b720c18 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -42,6 +42,12 @@ void Sprocket::loop(){ scheduler.execute(); } +void Sprocket::dispatch( uint32_t from, String &msg ) { + MeshMessage mMsg; + if(mMsg.fromJsonString(msg)){ + dispatchMessageToPlugins(mMsg); + } +} void Sprocket::addPlugin(Plugin* p){ plugins.reserve(1); diff --git a/src/Sprocket.h b/src/Sprocket.h index 9d3d715..93b0f83 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -33,7 +33,7 @@ class Sprocket { virtual Sprocket* activate(); virtual Sprocket* activate(Scheduler*) { return this; } virtual Sprocket* activate(Scheduler*, Network*); - virtual void dispatch( uint32_t from, String &msg ) {} + virtual void dispatch( uint32_t from, String &msg ); void addPlugin(Plugin* p); void setupPlugins(Scheduler* scheduler, Network* network); diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index 5fa77bf..bc25570 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -34,10 +34,7 @@ class MeshSprocket : public Sprocket { }; void dispatch( uint32_t from, String &msg ) { - MeshMessage mMsg; - if(mMsg.fromJsonString(msg)){ - dispatchMessageToPlugins(mMsg); - } + Sprocket::dispatch(from, msg); onMessage(from, msg); } diff --git a/src/base/MeshSprocketConfig.h b/src/base/MeshSprocketConfig.h index c77d720..14f89fc 100644 --- a/src/base/MeshSprocketConfig.h +++ b/src/base/MeshSprocketConfig.h @@ -17,52 +17,39 @@ #define JSON_stationPassword "stationPassword" #define JSON_hostname "hostname" -#define JSON_startupDelay "startupDelay" -#define JSON_serialBaudRate "serialBaudRate" - struct MeshSprocketConfig : public JsonStruct { - SprocketConfig sprocket; - MeshConfig mesh; - int valid = 0; + int stationMode; + int channel; + int meshPort; + String meshSSID; + String meshPassword; + String stationSSID; + String stationPassword; + String hostname; + uint16_t debugTypes; // ------------------------------------------------------------------------------------------ - String toJsonString(){ - //StaticJsonBuffer<200> jsonBuffer; - DynamicJsonBuffer jsonBuffer(JSON_ARRAY_SIZE(300)); - JsonObject& root = jsonBuffer.createObject(); - root[JSON_stationMode] = mesh.stationMode; - root[JSON_channel] = mesh.channel; - root[JSON_meshPort] = mesh.meshPort; - root[JSON_meshSSID] = mesh.meshSSID; - root[JSON_meshPassword] = mesh.meshPassword; - root[JSON_stationSSID] = mesh.stationSSID; - root[JSON_stationPassword] = mesh.stationPassword; - root[JSON_hostname] = mesh.hostname; - root[JSON_startupDelay] = sprocket.startupDelay; - root[JSON_serialBaudRate] = sprocket.serialBaudRate; - String jsonString; - root.printTo(jsonString); - return jsonString; + void mapJsonObject(JsonObject& root) { + root[JSON_stationMode] = stationMode; + root[JSON_channel] = channel; + root[JSON_meshPort] = meshPort; + root[JSON_meshSSID] = meshSSID; + root[JSON_meshPassword] = meshPassword; + root[JSON_stationSSID] = stationSSID; + root[JSON_stationPassword] = stationPassword; + root[JSON_hostname] = hostname; } + // Map a json object to this struct. - int fromJsonObject(JsonObject& json){ - if(!verifyJsonObject(json)){ - Serial.println("ERROR: cannot parse JSON object"); - valid = 0; - return valid; - } - mesh.stationMode = getIntAttrFromJson(json, JSON_stationMode); - mesh.channel = getIntAttrFromJson(json, JSON_channel); - mesh.meshPort = getIntAttrFromJson(json, JSON_meshPort); - mesh.meshSSID = getAttrFromJson(json, JSON_meshSSID).c_str(); - mesh.meshPassword = getAttrFromJson(json, JSON_meshPassword).c_str(); - mesh.stationSSID = getAttrFromJson(json, JSON_stationSSID).c_str(); - mesh.stationPassword = getAttrFromJson(json, JSON_stationPassword).c_str(); - mesh.hostname = getAttrFromJson(json, JSON_hostname).c_str(); - sprocket.startupDelay = getIntAttrFromJson(json, JSON_startupDelay); - sprocket.serialBaudRate = getIntAttrFromJson(json, JSON_serialBaudRate); - valid = 1; - return valid; + void fromJsonObject(JsonObject& json) { + stationMode = getIntAttrFromJson(json, JSON_stationMode); + channel = getIntAttrFromJson(json, JSON_channel); + meshPort = getIntAttrFromJson(json, JSON_meshPort); + meshSSID = getAttrFromJson(json, JSON_meshSSID); + meshPassword = getAttrFromJson(json, JSON_meshPassword); + stationSSID = getAttrFromJson(json, JSON_stationSSID); + stationPassword = getAttrFromJson(json, JSON_stationPassword); + hostname = getAttrFromJson(json, JSON_hostname); }; }; diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index 7f87652..66a2092 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -15,10 +15,10 @@ #define MESH_PORT 5555 #define MESH_PREFIX "whateverYouLike" #define MESH_PASSWORD "somethingSneaky" -#define STATION_SSID "tErAx1d" -#define STATION_PASSWORD "ramalamadingdong" +#define STATION_SSID "Th1ngs4p" +#define STATION_PASSWORD "th3r31sn0sp00n" #define HOSTNAME "mesh-node" -#define MESH_DEBUG_TYPES ERROR | STARTUP +#define MESH_DEBUG_TYPES ERROR | STARTUP | CONNECTION //ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE // OTA config diff --git a/src/examples/mesh/main.cpp b/src/examples/mesh/main.cpp index b8b8749..e8dabc6 100644 --- a/src/examples/mesh/main.cpp +++ b/src/examples/mesh/main.cpp @@ -2,16 +2,17 @@ #include "MeshNet.h" #include "MeshApp.cpp" +MeshApp sprocket( + { STARTUP_DELAY, SERIAL_BAUD_RATE }, + { OTA_PORT, OTA_PASSWORD } +); + MeshNet net({ SPROCKET_MODE, WIFI_CHANNEL, MESH_PORT, MESH_PREFIX, MESH_PASSWORD, STATION_SSID, STATION_PASSWORD, HOSTNAME, MESH_DEBUG_TYPES }); -MeshApp sprocket( - { STARTUP_DELAY, SERIAL_BAUD_RATE }, - { OTA_PORT, OTA_PASSWORD } -); void setup() { sprocket.join(net); From 4c32af3e1b255c70bc4c251027dfef3005e13bad Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Sun, 26 Aug 2018 19:04:25 +0200 Subject: [PATCH 25/88] cleanup --- src/JsonStruct.h | 9 ++++----- src/MeshNet.cpp | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/JsonStruct.h b/src/JsonStruct.h index 9924a08..ae7856f 100644 --- a/src/JsonStruct.h +++ b/src/JsonStruct.h @@ -28,19 +28,18 @@ struct JsonStruct { return jsonString; } - String getAttrFromJson(JsonObject& json, const char* attr){ + String getAttrFromJson(JsonObject& json, const char* attr, String defautValue = ""){ if(json.containsKey(String(attr))){ const char *value = json[attr]; return String(value); - //return json[attr]; } - return "no value"; + return defautValue; } - int getIntAttrFromJson(JsonObject& json, const char* attr){ + int getIntAttrFromJson(JsonObject& json, const char* attr, int defautValue = 0){ if(json.containsKey(attr)){ return json[attr]; } - return 0; + return defautValue; } // Map a json object to this struct. // Parse a json string and map parsed object diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 3152fda..65a6566 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -16,7 +16,6 @@ Network* MeshNet::init(){ Serial.println("init mesh"); config.fromFile("/config.json"); - Serial.println(config.meshSSID); //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on mesh.setDebugMsgTypes( config.debugTypes ); From 52fdf0e5e0da3733d121ad60cf935aabf37d23e8 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 29 Aug 2018 09:03:38 +0200 Subject: [PATCH 26/88] move webserver stuff to plugin --- README.md | 12 +++-- data/www/index.html | 7 +++ platformio.ini | 4 +- src/MeshNet.cpp | 1 + src/Plugin.h | 8 +-- src/Sprocket.cpp | 6 +-- src/Sprocket.h | 2 +- src/base/MeshSprocket.h | 9 +++- src/examples/mesh/{MeshApp.cpp => MeshApp.h} | 7 +-- src/examples/mesh/config.h | 5 ++ src/examples/mesh/main.cpp | 12 ++--- src/plugins/OtaTcpPlugin.cpp | 4 +- src/plugins/WebConfigPlugin.cpp | 57 ++++++++++++++++++++ src/plugins/WebSO.h | 13 +++++ src/plugins/WebServerPlugin.cpp | 33 ++++++++++++ 15 files changed, 156 insertions(+), 24 deletions(-) create mode 100644 data/www/index.html rename src/examples/mesh/{MeshApp.cpp => MeshApp.h} (90%) create mode 100644 src/plugins/WebConfigPlugin.cpp create mode 100644 src/plugins/WebSO.h create mode 100644 src/plugins/WebServerPlugin.cpp diff --git a/README.md b/README.md index bd7a948..d34695f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -# Sprocket-Core -Library to build a mesh network of single purpose nodes. +# Sprocket-Lib +Library to build a mesh network of Sprockets. + +## Lifecycle +TODO docs +sprocket.init -> Serial.begin & SPIFFS.begin +sprocket.join(network) -> network.init & sprocket.activate -> plugins.activate +network->onReceive -> sprocket.dispatch -> plugins.onMessage &sprocket.onMessage -## WTF is a sprocket? -A sprocket is a device that has a single purpose, for example a PIR sensor node that notifies other nodes if there is motion or an LED node that lights up when a message is received. # Useful commands ```sh diff --git a/data/www/index.html b/data/www/index.html new file mode 100644 index 0000000..62b5718 --- /dev/null +++ b/data/www/index.html @@ -0,0 +1,7 @@ + + + + +

Sprocket

+ + \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 78a249a..01a3efc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,7 +55,9 @@ lib_deps = ${common.lib_deps} painlessMesh ESP8266mDNS ArduinoOTA -;upload_port = 192.168.1.247 + ESP Async WebServer + ESPAsyncTCP +upload_port = 192.168.1.247 [env:meshMqttBridge] src_filter = +<*> - + diff --git a/src/MeshNet.cpp b/src/MeshNet.cpp index 65a6566..2160e6f 100644 --- a/src/MeshNet.cpp +++ b/src/MeshNet.cpp @@ -43,6 +43,7 @@ void MeshNet::sendTo(uint32_t target, String msg){ void MeshNet::broadcast(String msg){ mesh.sendBroadcast(msg); + } void MeshNet::update(){ // only needed when no scheduler was passed to mesh.init diff --git a/src/Plugin.h b/src/Plugin.h index c046e3e..4a1bea7 100644 --- a/src/Plugin.h +++ b/src/Plugin.h @@ -9,10 +9,10 @@ class Plugin { protected: Scheduler* scheduler; public: - virtual void setup(Scheduler*, Network*); - virtual void enable(); - virtual void disable(); - virtual void onMessage(MeshMessage msg); + virtual void activate(Scheduler*, Network*); + virtual void enable(){}; + virtual void disable(){}; + virtual void onMessage(MeshMessage msg){}; }; #endif \ No newline at end of file diff --git a/src/Sprocket.cpp b/src/Sprocket.cpp index b720c18..a19110c 100644 --- a/src/Sprocket.cpp +++ b/src/Sprocket.cpp @@ -20,7 +20,7 @@ Sprocket* Sprocket::activate() { } Sprocket* Sprocket::activate(Scheduler* scheduler, Network* network) { // setup plugins - setupPlugins(scheduler, network); + activatePlugins(scheduler, network); return this; } @@ -54,9 +54,9 @@ void Sprocket::addPlugin(Plugin* p){ plugins.push_back(p); } -void Sprocket::setupPlugins(Scheduler* scheduler, Network* network){ +void Sprocket::activatePlugins(Scheduler* scheduler, Network* network){ for(Plugin* p : plugins){ - p->setup(scheduler, network); + p->activate(scheduler, network); } } diff --git a/src/Sprocket.h b/src/Sprocket.h index 93b0f83..676facc 100644 --- a/src/Sprocket.h +++ b/src/Sprocket.h @@ -36,7 +36,7 @@ class Sprocket { virtual void dispatch( uint32_t from, String &msg ); void addPlugin(Plugin* p); - void setupPlugins(Scheduler* scheduler, Network* network); + void activatePlugins(Scheduler* scheduler, Network* network); void dispatchMessageToPlugins(MeshMessage msg); }; diff --git a/src/base/MeshSprocket.h b/src/base/MeshSprocket.h index bc25570..7a55dbf 100644 --- a/src/base/MeshSprocket.h +++ b/src/base/MeshSprocket.h @@ -8,18 +8,25 @@ #include #include #include +#include #include +#include +#include #include "config.h" using namespace std; using namespace std::placeholders; +AsyncWebServer WEBSERVER(80); + class MeshSprocket : public Sprocket { public: MeshNet* net; - MeshSprocket(SprocketConfig cfg, OtaConfig otaCfg) : Sprocket(cfg) { + MeshSprocket(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : Sprocket(cfg) { addPlugin(new OtaTcpPlugin(otaCfg)); + addPlugin(new WebServerPlugin(webCfg, &WEBSERVER)); + addPlugin(new WebConfigPlugin(&WEBSERVER)); } Sprocket* activate(Scheduler* scheduler, Network* network) { diff --git a/src/examples/mesh/MeshApp.cpp b/src/examples/mesh/MeshApp.h similarity index 90% rename from src/examples/mesh/MeshApp.cpp rename to src/examples/mesh/MeshApp.h index e738a7f..dd1191b 100644 --- a/src/examples/mesh/MeshApp.cpp +++ b/src/examples/mesh/MeshApp.h @@ -8,12 +8,13 @@ using namespace std; using namespace std::placeholders; + class MeshApp : public MeshSprocket { public: Task heartbeatTask; - MeshApp(SprocketConfig cfg, OtaConfig otaCfg) : MeshSprocket(cfg, otaCfg) { - + MeshApp(SprocketConfig cfg, OtaConfig otaCfg, WebServerConfig webCfg) : MeshSprocket(cfg, otaCfg, webCfg) { + } Sprocket* activate(Scheduler* scheduler, Network* network) { @@ -23,7 +24,7 @@ class MeshApp : public MeshSprocket { // add a task that sends stuff to the mesh heartbeatTask.set(TASK_SECOND * 5, TASK_FOREVER, bind(&MeshApp::heartbeat, this, net)); addTask(heartbeatTask); - + return this; } using MeshSprocket::activate; diff --git a/src/examples/mesh/config.h b/src/examples/mesh/config.h index 66a2092..e1a48a5 100644 --- a/src/examples/mesh/config.h +++ b/src/examples/mesh/config.h @@ -25,4 +25,9 @@ #define OTA_PORT 8266 #define OTA_PASSWORD "" +// WebServer +#define WEB_CONTEXT_PATH "/" +#define WEB_DOC_ROOT "/www" +#define WEB_DEFAULT_FILE "index.html" + #endif \ No newline at end of file diff --git a/src/examples/mesh/main.cpp b/src/examples/mesh/main.cpp index e8dabc6..1b985dd 100644 --- a/src/examples/mesh/main.cpp +++ b/src/examples/mesh/main.cpp @@ -1,11 +1,6 @@ #include "config.h" #include "MeshNet.h" -#include "MeshApp.cpp" - -MeshApp sprocket( - { STARTUP_DELAY, SERIAL_BAUD_RATE }, - { OTA_PORT, OTA_PASSWORD } -); +#include "MeshApp.h" MeshNet net({ SPROCKET_MODE, WIFI_CHANNEL, @@ -13,6 +8,11 @@ MeshNet net({ STATION_SSID, STATION_PASSWORD, HOSTNAME, MESH_DEBUG_TYPES }); +MeshApp sprocket( + { STARTUP_DELAY, SERIAL_BAUD_RATE }, + { OTA_PORT, OTA_PASSWORD }, + { WEB_CONTEXT_PATH, WEB_DOC_ROOT, WEB_DEFAULT_FILE } +); void setup() { sprocket.join(net); diff --git a/src/plugins/OtaTcpPlugin.cpp b/src/plugins/OtaTcpPlugin.cpp index f0c9cd1..4bbcebd 100644 --- a/src/plugins/OtaTcpPlugin.cpp +++ b/src/plugins/OtaTcpPlugin.cpp @@ -44,7 +44,7 @@ class OtaTcpPlugin : public Plugin { WiFi.gatewayIP().printTo(Serial); } } - void setup(Scheduler* userScheduler, Network* network){ + void activate(Scheduler* userScheduler, Network* network){ // connect done in network class? //connectUpdateNetwork(network); net = static_cast(network); @@ -79,6 +79,8 @@ class OtaTcpPlugin : public Plugin { else if (error == OTA_RECEIVE_ERROR) Serial.println("OTA: Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("OTA: End Failed"); }); + + enable(); } void disable(){ otaTask.disable(); diff --git a/src/plugins/WebConfigPlugin.cpp b/src/plugins/WebConfigPlugin.cpp new file mode 100644 index 0000000..fc04f95 --- /dev/null +++ b/src/plugins/WebConfigPlugin.cpp @@ -0,0 +1,57 @@ +#ifndef __WEB_CONFIG_PLUGIN_H__ +#define __WEB_CONFIG_PLUGIN_H__ + +#include "TaskSchedulerDeclarations.h" +#include "ArduinoOTA.h" +#include "MeshNet.h" +#include "Plugin.h" +#include +#include + +using namespace std; +using namespace std::placeholders; + + +class WebConfigPlugin : public Plugin { + private: + MeshNet* net; + AsyncWebServer* server; + public: + WebConfigPlugin(AsyncWebServer* webServer){ + server = webServer; + } + void activate(Scheduler* userScheduler, Network* network){ + + net = static_cast(network); + + server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("GET /heap"); + request->send(200, "text/plain", String(ESP.getFreeHeap())); + }); + server->on("/config", HTTP_GET, [](AsyncWebServerRequest *request){ + Serial.println("GET /config"); + + MeshSprocketConfig config; + config.fromFile("/config.json"); + config.meshPassword = ""; + config.stationPassword = ""; + request->send(200, "text/plain", config.toJsonString()); + }); + // TODO needs testing + server->on("/config", HTTP_POST, [](AsyncWebServerRequest *request){ + Serial.println("POST /config"); + // read existing config + MeshSprocketConfig config; + config.fromFile("/config.json"); + if(request->hasParam("mesh", true)) { + String inStr = request->getParam("mesh", true)->value(); + config.fromJsonString(inStr); + Serial.println(config.toJsonString()); + // config.saveFile("/config.json"); + } + request->send(200, "text/plain", String(ESP.getFreeHeap())); + }); + } +}; + +#endif \ No newline at end of file diff --git a/src/plugins/WebSO.h b/src/plugins/WebSO.h new file mode 100644 index 0000000..3a34072 --- /dev/null +++ b/src/plugins/WebSO.h @@ -0,0 +1,13 @@ +#ifndef __SHARED_PLUGINS__ +#define __SHARED_PLUGINS__ + +#include +extern AsyncWebServer WEBSERVER; + +struct WebServerConfig { + const char* contextPath; + const char* docRoot; + const char* defaultFile; +}; + +#endif \ No newline at end of file diff --git a/src/plugins/WebServerPlugin.cpp b/src/plugins/WebServerPlugin.cpp new file mode 100644 index 0000000..5c458f5 --- /dev/null +++ b/src/plugins/WebServerPlugin.cpp @@ -0,0 +1,33 @@ +#ifndef __WEB_SERVER_PLUGIN__ +#define __WEB_SERVER_PLUGIN__ + +#include +#include "TaskSchedulerDeclarations.h" +#include "ArduinoOTA.h" +#include "MeshNet.h" +#include "Plugin.h" +#include + +using namespace std; +using namespace std::placeholders; + +class WebServerPlugin : public Plugin { + private: + WebServerConfig config; + MeshNet* net; + AsyncWebServer* server; + public: + WebServerPlugin(WebServerConfig cfg, AsyncWebServer* webServer){ + config = cfg; + server = webServer; + } + void activate(Scheduler* userScheduler, Network* network){ + //connectUpdateNetwork(network); + net = static_cast(network); + server->serveStatic(config.contextPath, SPIFFS, config.docRoot).setDefaultFile(config.defaultFile); + server->begin(); + Serial.println("WebServer activated"); + } +}; + +#endif \ No newline at end of file From 74e9b8df4fb9cc1893937afac64af45d9986f5fe Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Wed, 29 Aug 2018 09:57:58 +0200 Subject: [PATCH 27/88] basic web config --- data/www/index.html | 25 ++++++++++++++++++++----- data/www/jquery-3.3.1.min.js | 2 ++ data/www/script.js | 11 +++++++++++ platformio.ini | 2 +- src/plugins/WebConfigPlugin.cpp | 25 +++++++++---------------- src/plugins/WebServerPlugin.cpp | 2 ++ 6 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 data/www/jquery-3.3.1.min.js create mode 100644 data/www/script.js diff --git a/data/www/index.html b/data/www/index.html index 62b5718..27df621 100644 --- a/data/www/index.html +++ b/data/www/index.html @@ -1,7 +1,22 @@ - - - -

Sprocket

- + + + + + + + +

Sprocket Config

+ + +
+
+
+ +
+ +
+ + + \ No newline at end of file diff --git a/data/www/jquery-3.3.1.min.js b/data/www/jquery-3.3.1.min.js new file mode 100644 index 0000000..4d9b3a2 --- /dev/null +++ b/data/www/jquery-3.3.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w("