Merge branch 'release-1.0' into 'master'

Release 1.0

See merge request 0x1d/illucat!2
This commit is contained in:
Patrick Balsiger
2018-09-24 10:36:23 +00:00
9 changed files with 160 additions and 97 deletions

View File

@@ -13,6 +13,8 @@
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp"
"vector": "cpp",
"tuple": "cpp",
"utility": "cpp"
}
}

View File

@@ -1,43 +1,9 @@
# Illumination-Cat
This is the brain of the the almighty [Illumination Cat](https://www.thingiverse.com/thing:2974862).
This is the brain of the the almighty Illumination-Cat.
## API
### WebSocket
Endpoint: /pixel
## Resources & Documentation
[3D Model](https://www.thingiverse.com/thing:2974862)
[Installation](https://gitlab.com/0x1d/illucat/blob/master/installation.md)
[API](https://gitlab.com/0x1d/illucat/blob/master/api.md)
[OctoPrint Stuff](https://github.com/FrYakaTKoP/simple-octo-ws2812)
Fields:
|Field|Type|Description|
| --- |---| ---|
| topic | String | Defines which functionality is executed, usually to set a value or activate a pattern |
| payload | String | data to be set |
Example:
``` json
{
"topic": "pixels/color",
"payload": "13505813"
}
```
#### Topics
| Topic | Data |
| ----- | ---- |
| pixels/color | |
| pixels/color2 | |
| pixels/pattern | |
| pixels/totalSteps | |
| pixels/brightness | |
### REST
#### Endpoints
POST /pixel/state
POST /config
### Mesh
## Features
- Enduser setup: initial setup where the cat opens an access point for configuration
- WiFi: connect to existing AP as client or build a mesh network where all cats act as a collective
- Web controls: colors and patterns can be changed through the web interface
- OTA plugin: cats connected to an AP can be updated over-the-air via TCP flash method
- [0%] audio output
- [0%] OctoPrint plugin: connect to an OctoPrint instance and reflect print status via colors

74
api.md Normal file
View File

@@ -0,0 +1,74 @@
# API
Two API architectures are supported: WebSocket and REST.
Both can be used with the same set of fields.
As everything can be controlled with topics, only a few fields are required:
| Field | Type | Description |
| ----- | ---- | ---- |
| topic | String | Topic where the payload is dispatched |
| payload | String | Payload to be dispatched |
| broadcast | Integer | Where to send the payload; 0 = local, 1 = broadcast |
- topic
- payload
- broadcast
## Topics
All functionality can be used by sending messages to these topics.
| topic | type | payload |
| ----- | ---- | ---- |
| pixels/colorWheel | Integer | Value from 0 to 255 to cycle through all colors |
| pixels/color | Integer | RGB color as integer. By calling this topic, all LEDs of the strip are set synchronously, stopping current running animation. |
| pixels/color2 | Integer |RGB color as integer. Sets the second color used in animations. Does not stop current running animation. |
| pixels/pattern | Integer | Value from 0 to 5 to set the current animation. Available animations: { NONE = 0, RAINBOW_CYCLE = 1, THEATER_CHASE = 2, COLOR_WIPE = 3, SCANNER = 4, FADE = 5 } |
| pixels/totalSteps | Integer | Number of steps of an animation. |
| pixels/brightness | Integer | Integer from 0 to 255 to set the overall brightness of the strip by bitshifting the current colors in memory. Use with caution as running the LEDs on full brightness requires a lot of power. |
## WebSocket
Endpoint: /pixel
Send a JSON String containing the mandatory fields.
Example:
``` json
{
"topic": "pixels/color",
"payload": 13505813
}
```
## REST
#### Endpoints
Content-Type: application/x-www-form-urlencoded
POST /pixel/api
Request: Form post with topic and payload as fields
Response: 200 + final payload as JSON
Examples:
```shell
# set color by wheel
curl -v -X POST \
--data "topic=pixels/colorWheel" \
--data "payload=20" \
http://illucat/pixel/api
# set color only on current node
curl -v -X POST \
--data "topic=pixels/colorWheel" \
--data "payload=20" \
--data "broadcast=1"\
http://illucat/pixel/api
# set brightness
curl -v -X POST \
--data "topic=pixels/brightness" \
--data "payload=10" \
http://illucat/pixel/api
# run rainbow pattern
curl -v -X POST \
--data "topic=pixels/pattern" \
--data "payload=1" \
http://illucat/pixel/api
```

View File

@@ -2,10 +2,10 @@
<html>
<head>
<title>ESP Kit</title>
<title>Illu-Cat</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" sizes="16x16" href="/favicon-32x32.png">
<link rel="icon" type="image/png" href="/favicon-32x32.png">
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="script.js"></script>
</head>

View File

@@ -21,6 +21,13 @@
.sui > .content {
padding: 16px;
}
@media screen and (min-width: 968px) {
.sui > .content {
margin-right: auto;
margin-left: auto;
width: 50%;
}
}
.sui label {
color: #b3b2b2;
}
@@ -150,6 +157,9 @@ input[type=range]:focus::-ms-fill-lower {
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;
@@ -274,13 +284,13 @@ form .form-row input[type="checkbox"] {
transform: scale(2);
}
.ColorPicker {
flex: none !important;
background-color: transparent;
border: 0;
height: 42px;
width: 42px;
}
.sui select {
flex: 5;
padding: .5em;
color: #eeeeee;
background: none;

View File

@@ -20,6 +20,6 @@ void PRINT_MSG(Print &out, const char* prefix, const char* format, ...) {
vsnprintf(ptr, sizeof(formatString)-1-strlen(formatString), formatString, args );
va_end (args);
formatString[ sizeof(formatString)-1 ]='\0';
out.print(ptr);
out.println(ptr);
}
}

View File

@@ -22,6 +22,13 @@ lib_deps =
ESPAsyncTCP
TaskScheduler
SPIFFS
painlessMesh
ESP8266mDNS
ArduinoOTA
ArduinoJson
ESP Async WebServer
ESPAsyncTCP
Adafruit NeoPixel
[env:build]
platform = ${common.platform}
@@ -30,13 +37,17 @@ upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
;lib_extra_dirs = ~/src/illucat/.piolibdeps/sprocket-core/lib
-DSPROCKET_PRINT=1
lib_deps = ${common.lib_deps}
painlessMesh
ESP8266mDNS
ArduinoOTA
ArduinoJson
ESP Async WebServer
ESPAsyncTCP
Adafruit NeoPixel
https://gitlab.com/wirelos/sprocket-core.git#develop
[env:release]
platform = ${common.platform}
board = ${common.board}
upload_speed = ${common.upload_speed}
monitor_baud = ${common.monitor_baud}
framework = ${common.framework}
build_flags = -Wl,-Teagle.flash.4m1m.ld
-DSPROCKET_PRINT=0
lib_deps = ${common.lib_deps}
https://gitlab.com/wirelos/sprocket-core.git#master

View File

@@ -55,98 +55,92 @@ class IlluCat : public MeshSprocket {
pixelConfig.defaultColor = 100;
}
// TDOO remove
virtual void scanningAnimation() {
pixels->Scanner(pixels->Wheel(COLOR_NOT_CONNECTED), pixelConfig.updateInterval);
//pixels->Fade(0, pixels->Color(255,255,255), 4, pixelConfig.updateInterval, FORWARD);
}
virtual void defaultAnimation() {
String defaultStr = String(defaultState.value);
PIXEL_FNCS[defaultState.mode](pixels, defaultStr.c_str());
//pixels->RainbowCycle(150);
}
Sprocket* activate(Scheduler* scheduler, Network* network) {
net = static_cast<MeshNet*>(network);
net->mesh.onNewConnection(bind(&IlluCat::onNewConnection,this, _1));
//net->mesh.onChangedConnections(bind(&IlluCat::onConnectionChanged,this));
// load config files from SPIFFS
if(SPIFFS.begin()){
pixelConfig.fromFile("/pixelConfig.json");
defaultState.fromFile("/pixelState.json");
state = defaultState;
}
// initialize services
pixels = new NeoPattern(pixelConfig.length, pixelConfig.pin, NEO_GRB + NEO_KHZ800);
server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/pixel");
dnsServer = new DNSServer();
// add plugins
addPlugin(new OtaTcpPlugin(otaConfig));
addPlugin(new WebServerPlugin(webConfig, server));
addPlugin(new WebConfigPlugin(server));
addPlugin(new PixelPlugin(pixelConfig, pixels));
// TODO plugin?
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
Serial.println("SoftAP IP: " + WiFi.softAPIP().toString());
defaultAnimation();
// TODO plugin
// configure DNS
// plugin?
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
dnsServer->start(DNS_PORT, "*", WiFi.softAPIP());
String softApPrt = "SoftAP IP: " + WiFi.softAPIP().toString();
PRINT_MSG(Serial, SPROCKET_TYPE, softApPrt.c_str());
// TODO move to plugin
// setup web stuff
server->serveStatic("/pixelConfig.json", SPIFFS, "pixelConfig.json");
server->on("/pixel/pattern", HTTP_POST, bind(&IlluCat::patternWebRequestHandler, this, _1));
server->on("/pixel/api", HTTP_POST, bind(&IlluCat::patternWebRequestHandler, this, _1));
ws->onEvent(bind(&IlluCat::onWsEvent, this, _1, _2, _3, _4, _5, _6));
server->addHandler(ws);
// FIXME OnDisable is triggered after last scan, aprx. 10 sec
// FIXME chck if this is also triggered when new connection arrives
//net->mesh.stationScan.task.setOnDisable(bind(&IlluCat::defaultAnimation,this));
return MeshSprocket::activate(scheduler, network);
} using MeshSprocket::activate;
void patternWebRequestHandler(AsyncWebServerRequest *request) {
Serial.println("POST /pixel/state");
if(request->hasParam("state", true)) {
String inStr = request->getParam("state", true)->value();
dispatch(0, inStr);
// TODO move to utils
String getRequestParameterOrDefault(AsyncWebServerRequest *request, String param, String defaultValue, bool isPost = true){
if(request->hasParam(param, isPost)) {
return request->getParam(param, isPost)->value();
}
request->send(200, "text/plain", "OK");
return defaultValue;
}
void patternWebRequestHandler(AsyncWebServerRequest *request) {
PRINT_MSG(Serial, SPROCKET_TYPE, "POST /pixel/api");
currentMessage.topic = getRequestParameterOrDefault(request, "topic", "");
currentMessage.payload = getRequestParameterOrDefault(request, "payload", "");
currentMessage.broadcast = atoi(getRequestParameterOrDefault(request, "broadcast", "0").c_str());
String msg = currentMessage.toJsonString();
publish(currentMessage.topic, currentMessage.payload);
if(currentMessage.broadcast){
net->mesh.sendBroadcast(msg);
}
request->send(200, "text/plain", msg);
}
virtual void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if(type == WS_EVT_DATA){
String frame = WsUtils::parseFrameAsString(type, arg, data, len, 0);
net->mesh.sendBroadcast(frame);
dispatch(0, frame);
client->text(String(millis()));
}
}
virtual void dispatch( uint32_t from, String &msg ) {
//Serial.println(msg);
currentMessage.fromJsonString(msg);
if(currentMessage.valid){
currentMessage.from = from;
publish(currentMessage.topic, currentMessage.payload);
if(currentMessage.broadcast){
net->mesh.sendBroadcast(msg);
}
}
virtual void onNewConnection(uint32_t nodeId){
PRINT_MSG(Serial, SPROCKET_TYPE, "connected to %u", nodeId);
// publish current state to new node
// TODO broadcast enable/disable flag
//String stateJson = currentMessage.toJsonString();
//net->mesh.sendSingle(nodeId, stateJson);
}
//virtual void onConnectionChanged(){
// PRINT_MSG(Serial, SPROCKET_TYPE, "connection changed");
// //if(!net->mesh.getNodeList().size()){
// // defaultAnimation();
// //}
//}
void loop(){
MeshSprocket::loop();

View File

@@ -38,11 +38,17 @@ class PixelPlugin : public Plugin {
animation.set(TASK_MILLISECOND * pixelConfig.updateInterval, TASK_FOREVER, bind(&PixelPlugin::animate, this));
userScheduler->addTask(animation);
animation.enable();
Serial.println("NeoPixels activated");
PRINT_MSG(Serial, SPROCKET_TYPE, "NeoPixels activated");
}
// TODO set the whole pixel state
void setState(String msg) {
state.fromJsonString(msg);
pixels->setBrightness(state.brightness);
pixels->ColorSet(state.color);
pixels->Index = 0;
pixels->Color1 = state.color;
pixels->Color2 = state.color2;
pixels->TotalSteps = state.totalSteps;
pixels->ActivePattern = (pattern) state.pattern;
}
void colorWheel(String msg){
int color = atoi(msg.c_str());