diff --git a/.vscode/settings.json b/.vscode/settings.json index 275567e..e190734 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "type_traits": "cpp", "array": "cpp", "initializer_list": "cpp", - "*.tcc": "cpp" + "*.tcc": "cpp", + "sstream": "cpp" } } \ No newline at end of file diff --git a/lib/NeoPattern/NeoPattern_api_modes.cpp b/lib/NeoPattern/NeoPattern_api_modes.cpp index c87a3e6..76f3a1f 100644 --- a/lib/NeoPattern/NeoPattern_api_modes.cpp +++ b/lib/NeoPattern/NeoPattern_api_modes.cpp @@ -41,13 +41,11 @@ const PIXEL_FP PIXEL_FNCS[] = { }, /* COLOR_MODE - Input: rgb hex color without # - parses the hex string to r,g,b and sets all pixels accordingly + sets the color from an rgb int */ [](NeoPattern* pixels, const char *color){ - int r, g, b; - sscanf(color, "%02x%02x%02x", &r, &g, &b); - pixels->ColorSet(pixels->Color(r,g,b)); + pixels->ActivePattern = NONE; + pixels->ColorSet(atoi(color)); }, /* PATTERN_MODE diff --git a/lib/sprocket-utils/utils_ws.h b/lib/sprocket-utils/utils_ws.h new file mode 100644 index 0000000..f0b972b --- /dev/null +++ b/lib/sprocket-utils/utils_ws.h @@ -0,0 +1,126 @@ +#ifndef __WSUTILS_H___ +#define __WSUTILS_H___ + +#include +#include +#include +#include + +class WsUtils { + public: + static String parseFrame(AwsEventType type, void * arg, uint8_t *data, size_t len) { + String msg = ""; + if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->opcode == WS_TEXT){ + for(size_t i=0; i < info->len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=0; i < info->len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + + } + return msg; + } + static String parseFrameAsString(AwsEventType type, void * arg, uint8_t *data, size_t len, int start = 0) { + String msg = ""; + if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + //if(info->final && info->index == 0 && info->len == len){ + if(info->opcode == WS_TEXT){ + for(size_t i=start; i < info->len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=start; i < info->len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + + //} + } + return msg; + } + static void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { + if(type == WS_EVT_CONNECT){ + Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); + client->printf("Hello Client %u :)", client->id()); + client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + AwsFrameInfo * info = (AwsFrameInfo*)arg; + String msg = ""; + //the whole message is in a single frame and we got all of it's data + if(info->final && info->index == 0 && info->len == len){ + Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + + if(info->opcode == WS_TEXT){ + for(size_t i=0; i < info->len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=0; i < info->len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + Serial.printf("%s\n",msg.c_str()); + + if(info->opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + //message is comprised of multiple frames or the frame is split into multiple packets + else { + if(info->index == 0){ + if(info->num == 0) + Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + + if(info->opcode == WS_TEXT){ + for(size_t i=0; i < info->len; i++) { + msg += (char) data[i]; + } + } else { + char buff[3]; + for(size_t i=0; i < info->len; i++) { + sprintf(buff, "%02x ", (uint8_t) data[i]); + msg += buff ; + } + } + Serial.printf("%s\n",msg.c_str()); + + if((info->index + len) == info->len){ + Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + client->text("I got your text message"); + else + client->binary("I got your binary message"); + } + } + } + } + } + +}; + +#endif \ No newline at end of file diff --git a/library.json b/library.json new file mode 100644 index 0000000..f96c541 --- /dev/null +++ b/library.json @@ -0,0 +1,29 @@ +{ + "name": "illucat", + "keywords": "esp8266, sprocket, stack", + "description": "", + "authors": + { + "name": "Patrick Balsiger", + "email": "frupii@gmail.com", + "url": "https://gitlab.com/0x1d" + }, + "repository": + { + "type": "git", + "url": "https://gitlab.com/0x1d/illucat" + }, + "frameworks": "arduino", + "platforms": "espressif8266", + "examples": "examples/*", + "export": { + "include": + [ + "lib" + ], + "exclude": + [ + "src/examples/" + ] + } +} \ No newline at end of file diff --git a/src/IlluCat.h b/src/IlluCat.h index 16eb459..c37324e 100644 --- a/src/IlluCat.h +++ b/src/IlluCat.h @@ -11,6 +11,7 @@ #include "NeoPattern_api_json.h" #include "NeoPattern_api_modes.cpp" #include "utils_print.h" +#include "utils_ws.h" #include #include #include @@ -27,7 +28,8 @@ class IlluCat : public MeshSprocket { NeoPatternDto state; Task animation; AsyncWebServer* server; - + AsyncWebSocket* ws; + NeoPixelConfig pixelConfig; SprocketConfig sprocketConfig; OtaConfig otaConfig; @@ -68,18 +70,39 @@ class IlluCat : public MeshSprocket { } pixels = new NeoPattern(pixelConfig.length, pixelConfig.pin, NEO_GRB + NEO_KHZ800); server = new AsyncWebServer(80); + ws = new AsyncWebSocket("/pixel"); + ws->onEvent([=](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + this->onWsEvent(server, client, type, arg, data, len); + }); addPlugin(new OtaTcpPlugin(otaConfig)); addPlugin(new WebServerPlugin(webConfig, server)); addPlugin(new WebConfigPlugin(server)); addPlugin(new PixelPlugin(pixelConfig, pixels)); scanningAnimation(); - + server->addHandler(ws); + server->on("/pixel/pattern", HTTP_GET, [this](AsyncWebServerRequest *request){ + Serial.println("POST /pixel/pattern"); + if(request->hasParam("state", true)) { + String inStr = request->getParam("state", true)->value(); + onMessage(0, inStr); + } + request->send(200, "text/plain", "OK"); + }); // FIXME OnDisable is triggered after last scan, aprx. 10 sec net->mesh.stationScan.task.setOnDisable(bind(&IlluCat::defaultAnimation,this)); return MeshSprocket::activate(scheduler, network); } using MeshSprocket::activate; + 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); + onMessage(0, frame); + net->mesh.sendBroadcast(frame); + client->text(String(millis())); + } + } + void onMessage( uint32_t from, String &msg ) { PRINT_MSG(Serial, SPROCKET_TYPE, "msg from %u = %s\n", from, msg.c_str()); state.fromJsonString(msg);