initial commit

This commit is contained in:
2018-11-15 15:38:39 +01:00
commit 1e533aac6d
20 changed files with 14449 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.pioenvs
.piolibdeps
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json

14
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,14 @@
image: python:2.7-stretch
stages:
- build
before_script:
- "pip install -U platformio"
examples:
stage: build
image: python:2.7-stretch
script:
- pio run -t clean
- pio run -e basic

67
.travis.yml Normal file
View File

@@ -0,0 +1,67 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < http://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < http://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < http://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choice one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to by used as a library with examples
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

7
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"terminal.integrated.env.linux": {
"PATH": "/home/master/.platformio/penv/bin:/home/master/.platformio/penv:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl",
"PLATFORMIO_CALLER": "vscode"
}
}

9
data/config.json Normal file
View File

@@ -0,0 +1,9 @@
{
"stationMode": 1,
"hostname": "websprocket",
"apSSID": "WebSprocket",
"apPassword": "th3r31sn0sp00n",
"connectTimeout": 20000,
"stationSSID": "MyWifi",
"stationPassword": "myWifiPassword"
}

BIN
data/www/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

1351
data/www/gradients.json Normal file

File diff suppressed because it is too large Load Diff

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

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<title>ESP Kit</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="script.js"></script>
</head>
<body class="sui">
<div class="content">
<div class="settings container collapsible">
<span class="heading">Settings</span>
<div class="content">
<h2>Network</h2>
<div class="Form" data-fileName="/config.json" data-name="configForm" data-from="/config.json" data-endpoint="/config"></div>
</div>
</div>
<div class="settings container collapsible">
<span class="heading">System</span>
<div class="content">
<div><label>Free Heap: </label><span class="js-heap"></span><span>&nbsp;bytes</span><br><br></div>
<button class="js-restart">Restart</button>
</div>
</div>
</div>
</body>
</html>

12245
data/www/script.js Normal file

File diff suppressed because it is too large Load Diff

308
data/www/styles.css Normal file
View File

@@ -0,0 +1,308 @@
.hidden {
display: none;
}
.shown {
display: block;
}
.disabled {
opacity: 0.65;
}
.sui {
background: #000000;
color: #0eb8c0;
font-family: Tahoma, Geneva, sans-serif;
font-size: 16px;
}
.sui * {
margin: 0;
padding: 0;
-webkit-tap-highlight-color: transparent;
}
.sui > .content {
padding: 16px;
}
@media screen and (min-width: 968px) {
.sui > .content {
margin-right: auto;
margin-left: auto;
width: 50%;
}
}
.sui label {
color: #b3b2b2;
}
.sui button {
background: #097479;
color: #eeeeee;
font-size: 0.9em;
border: 0;
padding: .8em;
margin: 0 .4em;
}
.heading {
font-size: 1.2em;
display: block;
margin-bottom: 16px;
}
.sui h2 {
font-size: 16px;
font-weight: 400;
}
.container {
background: #333333;
padding: 16px;
border-radius: 2px;
border: solid 1px #555555;
margin-bottom: 8px;
}
.container.collapsible > .heading {
margin-bottom: 0;
}
.container.collapsible > .heading:hover {
cursor: pointer;
}
.container.collapsible > .content {
display: none;
}
.container.collapsible.open > .heading {
margin-bottom: 16px;
}
.container.collapsible.open > .content {
display: block;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
margin: 8.4px 0;
padding: 0 !important;
background-color: transparent;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 3.2px;
cursor: pointer;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
background: #097479;
border-radius: 1.3px;
border: 0.2px solid #010101;
}
input[type=range]::-webkit-slider-thumb {
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
border: 0.4px solid #000000;
height: 20px;
width: 20px;
border-radius: 50px;
background: #0eb8c0;
cursor: pointer;
-webkit-appearance: none;
margin-top: -8.6px;
}
input[type=range]:focus::-webkit-slider-runnable-track {
background: #0eb4bb;
}
input[type=range]::-moz-range-track {
width: 100%;
height: 3.2px;
cursor: pointer;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
background: #097479;
border-radius: 1.3px;
border: 0.2px solid #010101;
}
input[type=range]::-moz-range-thumb {
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
border: 0.4px solid #000000;
height: 20px;
width: 20px;
border-radius: 50px;
background: #0eb8c0;
cursor: pointer;
}
input[type=range]::-ms-track {
width: 100%;
height: 3.2px;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-ms-fill-lower {
background: #043437;
border: 0.2px solid #010101;
border-radius: 2.6px;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
input[type=range]::-ms-fill-upper {
background: #097479;
border: 0.2px solid #010101;
border-radius: 2.6px;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
}
input[type=range]::-ms-thumb {
box-shadow: 0.6px 0.6px 2.8px #000000, 0px 0px 0.6px #0d0d0d;
border: 0.4px solid #000000;
height: 20px;
width: 20px;
border-radius: 50px;
background: #0eb8c0;
cursor: pointer;
height: 3.2px;
}
input[type=range]:focus::-ms-fill-lower {
background: #097479;
}
input[type=range]:focus::-ms-fill-upper {
background: #0eb4bb;
}
.form-row input[type="range"] {
margin-top: 12px;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline;
/* The slider */
/* Rounded sliders */
}
.switch input {
display: none;
}
.switch .slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #7b7b7b;
-webkit-transition: .4s;
transition: .4s;
margin-left: -50px;
margin-top: 4px;
}
.switch .slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
.switch input:checked + .slider {
background-color: #097479;
}
.switch input:focus + .slider {
box-shadow: 0 0 1px #0eb8c0;
}
.switch input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
.switch .slider.round {
border-radius: 34px;
height: 34px;
width: 60px;
}
.switch .slider.round:before {
border-radius: 50%;
}
form .form-row {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
padding: .2em;
}
form .form-row > label {
padding: .5em 1em .5em 0;
flex: 1;
}
form .form-row > label + label {
flex: 0;
}
form .form-row > label.switch + label {
flex: 1;
}
form .form-row > input {
flex: 2;
}
form .form-row > span {
flex: 2;
text-align: right;
}
form .form-row input[type="checkbox"] {
margin-top: 12px;
}
@media screen and (min-width: 768px) {
form .form-row > input,
form .form-row > span {
flex: 3;
}
}
@media screen and (max-width: 460px) {
form .form-row.wrapped {
display: block;
}
form .form-row.wrapped * {
display: block;
}
}
@media screen and (min-width: 992px) {
form .form-row > input,
form .form-row > span {
flex: 4;
}
}
@media screen and (min-width: 1200px) {
form .form-row > input,
form .form-row > span {
flex: 5;
}
}
.sui input[type="text"],
.sui input[type="password"] {
height: 32px;
background: transparent;
border: none;
color: #eeeeee;
border-bottom: solid 1px #097479;
}
.sui input[type="text"]:focus,
.sui input[type="password"]:focus,
.sui input[type="text"]:hover,
.sui input[type="password"]:hover {
outline: none;
border-bottom: 1px solid #0eb8c0;
}
.sui input[type="checkbox"] {
transform: scale(2);
}
.ColorPicker {
background-color: transparent;
border: 0;
height: 42px;
width: 42px;
}
.sui select {
flex: 5;
padding: .5em;
color: #eeeeee;
background: none;
border: none;
border-bottom: solid 1px #097479;
}
.sui select:focus,
.sui select:hover {
outline: none;
border: none;
border-bottom: solid 1px #0eb8c0 !important;
}
.sui select option {
background: #333333;
}

41
lib/readme.txt Normal file
View File

@@ -0,0 +1,41 @@
This directory is intended for the project specific (private) libraries.
PlatformIO will compile them to static libraries and link to executable file.
The source code of each library should be placed in separate directory, like
"lib/private_lib/[here are source files]".
For example, see how can be organized `Foo` and `Bar` libraries:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) http://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- readme.txt --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Then in `src/main.c` you should use:
#include <Foo.h>
#include <Bar.h>
// rest H/C/CPP code
PlatformIO will find your libraries automatically, configure preprocessor's
include paths and build them.
More information about PlatformIO Library Dependency Finder
- http://docs.platformio.org/page/librarymanager/ldf.html

25
library.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "sprocket-plugin-template",
"keywords": "sprocket, plugin",
"description": "Template for a sprocket plugin",
"authors":
{
"name": "Patrick Balsiger",
"email": "patrick.balsiger@wirelos.net",
"url": "https://gitlab.com/0x1d"
},
"repository":
{
"type": "git",
"url": "https://gitlab.com/wirelos/sprocket-plugin-template"
},
"frameworks": "arduino",
"platforms": "espressif8266, esp32",
"examples": "examples/*",
"export": {
"exclude":
[
"src/examples/"
]
}
}

29
platformio.ini Normal file
View File

@@ -0,0 +1,29 @@
[platformio]
env_default = basic
[common]
framework = arduino
platform = espressif8266
board = esp12e
upload_speed = 921600
monitor_baud = 115200
lib_deps =
Hash
TaskScheduler
SPIFFS
ArduinoJson
ArduinoOTA
ESPAsyncTCP
ESP8266mDNS
ESP Async WebServer
painlessMesh
https://gitlab.com/wirelos/sprocket-lib.git#develop
[env:basic]
src_filter = +<*> -<examples/> +<examples/basic/>
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
lib_deps = ${common.lib_deps}

126
src/WebApiPlugin.cpp Normal file
View File

@@ -0,0 +1,126 @@
#ifndef __WEBAPI_PLUGIN__
#define __WEBAPI_PLUGIN__
#include <TaskSchedulerDeclarations.h>
#include <Sprocket.h>
#include <Updater.h>
#include "config.h"
#include "utils/utils_print.h"
#include "utils/utils_web.h"
#include "WebServerPlugin.cpp"
#include "WebConfigPlugin.cpp"
using namespace std;
using namespace std::placeholders;
// TODO headerfile
// FIXME constants
class WebApiPlugin : public Plugin
{
public:
AsyncWebServer *server;
AsyncWebSocket *ws;
SprocketMessage currentMessage;
WebApiPlugin(AsyncWebServer *_server)
{
server = _server;
Update.runAsync(true);
}
void activate(Scheduler *_scheduler)
{
ws = new AsyncWebSocket("/ws");
ws->onEvent(bind(&WebApiPlugin::onWsEvent, this, _1, _2, _3, _4, _5, _6));
server->addHandler(ws);
server->on("/api", HTTP_POST, bind(&WebApiPlugin::postRequestHandler, this, _1));
server->on("/update", HTTP_GET, bind(&WebApiPlugin::simpleFirmwareUploadFormvoid, this, _1));
server->on("/update", HTTP_POST, bind(&WebApiPlugin::onFirmwareUpdateRequest, this, _1), bind(&WebApiPlugin::onFirmwareUpload, this, _1, _2, _3, _4, _5, _6));
subscribe("ws/broadcast", bind(&WebApiPlugin::wsBroadcast, this, _1));
PRINT_MSG(Serial, "WEB", "API activated");
}
void wsBroadcast(String msg)
{
ws->textAll(msg);
}
void postRequestHandler(AsyncWebServerRequest *request)
{
PRINT_MSG(Serial, "WEB", "POST WebApiPlugin");
currentMessage.topic = WebUtils::getRequestParameterOrDefault(request, "topic", "");
currentMessage.payload = WebUtils::getRequestParameterOrDefault(request, "payload", "");
currentMessage.broadcast = atoi(WebUtils::getRequestParameterOrDefault(request, "broadcast", "0").c_str());
String msg = currentMessage.toJsonString();
publish(currentMessage.topic, currentMessage.payload);
request->send(200, "text/plain", msg);
}
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)
{
// FIXME to limitted
if (type == WS_EVT_DATA)
{
String frame = WebUtils::parseFrameAsString(type, arg, data, len, 0);
dispatch(frame);
}
}
void simpleFirmwareUploadFormvoid(AsyncWebServerRequest *request)
{
request->send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
}
void onFirmwareUpdateRequest(AsyncWebServerRequest *request)
{
bool hasError = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", hasError ? "OK" : "FAIL");
response->addHeader("Connection", "close");
request->send(response);
publish("esp/reboot", String(hasError));
}
void onFirmwareUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{
if (!index)
{
PRINT_MSG(Serial, "OTA", "Update Start %s", filename.c_str());
Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000))
{
Update.printError(Serial);
}
}
if (!Update.hasError())
{
if (Update.write(data, len) != len)
{
Update.printError(Serial);
}
}
if (final)
{
if (Update.end(true))
{
PRINT_MSG(Serial, "OTA", "Update Success with %uB", index + len);
}
else
{
Update.printError(Serial);
}
}
}
void dispatch(String &msg)
{
currentMessage.fromJsonString(msg);
if (currentMessage.valid)
{
publish(currentMessage.topic, currentMessage.payload);
}
}
};
#endif

56
src/WebConfigPlugin.cpp Normal file
View File

@@ -0,0 +1,56 @@
#ifndef __WEB_CONFIG_PLUGIN_H__
#define __WEB_CONFIG_PLUGIN_H__
#include <FS.h>
#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include "Plugin.h"
#include "WebServerConfig.h"
#include "utils/utils_print.h"
using namespace std;
using namespace std::placeholders;
class WebConfigPlugin : public Plugin
{
private:
AsyncWebServer *server;
public:
WebConfigPlugin(AsyncWebServer *webServer)
{
server = webServer;
server->serveStatic("/config.json", SPIFFS, "config.json");
}
void activate(Scheduler *userScheduler)
{
server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
PRINT_MSG(Serial, "WEB", "GET /heap");
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
server->on("/restart", HTTP_POST, [](AsyncWebServerRequest *request) {
PRINT_MSG(Serial, "WEB", "POST /restart");
ESP.restart();
});
server->on("/config", HTTP_POST, [](AsyncWebServerRequest *request) {
PRINT_MSG(Serial, "WEB", "POST /config");
if (request->hasParam("config", true) && request->hasParam("fileName", true))
{
String inStr = request->getParam("config", true)->value();
String fileName = request->getParam("fileName", true)->value();
File f = SPIFFS.open(fileName, "w");
if (!f)
{
PRINT_MSG(Serial, "WEB", String(String("file open for read failed: ") + fileName).c_str());
}
PRINT_MSG(Serial, "WEB", String(String("writing to SPIFFS file: ") + fileName).c_str());
f.print(inStr);
f.close();
}
request->redirect("/");
});
PRINT_MSG(Serial, "WEB", "WebConfig activated");
}
};
#endif

15
src/WebServerConfig.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef __WEB_SERVER_CONFIG__
#define __WEB_SERVER_CONFIG__
struct WebServerConfig
{
const char *contextPath;
const char *docRoot;
const char *defaultFile;
int port;
int useBasicAuth;
const char *user;
const char *password;
};
#endif

43
src/WebServerPlugin.cpp Normal file
View File

@@ -0,0 +1,43 @@
#ifndef __WEB_SERVER_PLUGIN__
#define __WEB_SERVER_PLUGIN__
#include <FS.h>
#include <ESPAsyncWebServer.h>
#include "TaskSchedulerDeclarations.h"
#include "Plugin.h"
#include "WebServerConfig.h"
#include "utils/utils_print.h"
using namespace std;
using namespace std::placeholders;
class WebServerPlugin : public Plugin
{
private:
WebServerConfig config;
public:
AsyncWebServer *server;
WebServerPlugin(WebServerConfig cfg)
{
config = cfg;
server = new AsyncWebServer(config.port);
}
WebServerPlugin(WebServerConfig cfg, AsyncWebServer *webServer)
{
config = cfg;
server = webServer;
}
void activate(Scheduler *userScheduler)
{
AsyncStaticWebHandler &staticWebHandler = server->serveStatic(config.contextPath, SPIFFS, config.docRoot).setDefaultFile(config.defaultFile);
if (config.useBasicAuth)
{
staticWebHandler.setAuthentication(config.user, config.password);
}
server->begin();
PRINT_MSG(Serial, "WEB", "Server activated");
}
};
#endif

View File

@@ -0,0 +1,24 @@
#ifndef __DEVICE_CONFIG__
#define __DEVICE_CONFIG__
// Scheduler config
#define _TASK_SLEEP_ON_IDLE_RUN
#define _TASK_STD_FUNCTION
#define _TASK_PRIORITY
// Chip config
#define SPROCKET_TYPE "SPROCKET"
#define SERIAL_BAUD_RATE 115200
#define STARTUP_DELAY 1000
// network config
#define SPROCKET_MODE 1
#define WIFI_CHANNEL 11
#define AP_SSID "sprocket"
#define AP_PASSWORD "th3r31sn0sp00n"
#define STATION_SSID "MyAP"
#define STATION_PASSWORD "th3r31sn0sp00n"
#define HOSTNAME "sprocket"
#define CONNECT_TIMEOUT 10000
#endif

View File

@@ -0,0 +1,46 @@
#include "config.h"
#include "WiFiNet.h"
#include "Sprocket.h"
#include "ESPAsyncWebServer.h"
#include "WebServerConfig.h"
#include "WebServerPlugin.cpp"
#include "WebConfigPlugin.cpp"
#include "WebApiPlugin.cpp"
WiFiNet *network;
Sprocket *sprocket;
AsyncWebServer *server;
WebServerPlugin *webServerPlugin;
WebConfigPlugin *webConfigPlugin;
WebApiPlugin *webApiPlugin;
void setup()
{
sprocket = new Sprocket({STARTUP_DELAY, SERIAL_BAUD_RATE});
webServerPlugin = new WebServerPlugin({"/", "/www", "index.html", 80});
webConfigPlugin = new WebConfigPlugin(webServerPlugin->server);
webApiPlugin = new WebApiPlugin(webServerPlugin->server);
sprocket->addPlugin(webServerPlugin);
sprocket->addPlugin(webConfigPlugin);
sprocket->addPlugin(webApiPlugin);
network = new WiFiNet(
SPROCKET_MODE,
STATION_SSID,
STATION_PASSWORD,
AP_SSID,
AP_PASSWORD,
HOSTNAME,
CONNECT_TIMEOUT);
network->connect();
sprocket->activate();
}
void loop()
{
sprocket->loop();
yield();
}