Merge pull request #1023 from Aircoookie/websockets

Merge Websockets branch
This commit is contained in:
Aircoookie 2020-07-02 11:25:38 +02:00 committed by GitHub
commit 533f86c035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 178 additions and 18 deletions

View File

@ -2,6 +2,8 @@
### Development versions after 0.10.0 release
#### Build 2007020
#### Build 2006251
- Added `SV=2` to HTTP API, allow selecting single segment only

View File

@ -165,7 +165,7 @@ lib_deps =
ESPAsyncTCP@1.2.0
ESPAsyncUDP@697c75a025
AsyncTCP@1.0.3
Esp Async WebServer@1.2.0
https://github.com/Aircoookie/ESPAsyncWebServer
IRremoteESP8266@2.7.3
#For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line
#TFT_eSPI

View File

@ -1599,7 +1599,7 @@ uint16_t WS2812FX::mode_oscillate(void)
uint16_t WS2812FX::mode_lightning(void)
{
uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash
uint16_t ledlen = random16(SEGLEN -1 -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1)
uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1)
uint8_t bri = 255/random8(1, 3);
if (SEGENV.step == 0)
@ -2247,7 +2247,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) {
CRGB WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat)
{
// Overall twinkle speed (changed)
uint16_t ticks = ms / (32 - (SEGMENT.speed >> 3));
uint16_t ticks = ms / SEGENV.aux0;
uint8_t fastcycle8 = ticks;
uint16_t slowcycle16 = (ticks >> 8) + salt;
slowcycle16 += sin8(slowcycle16);
@ -2312,10 +2312,11 @@ uint16_t WS2812FX::twinklefox_base(bool cat)
// numbers that it generates is (paradoxically) stable.
uint16_t PRNG16 = 11337;
// Calculate speed
if (SEGMENT.speed > 100) SEGENV.aux0 = 3 + ((255 - SEGMENT.speed) >> 3);
else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1);
// Set up the background color, "bg".
// if AUTO_SELECT_BACKGROUND_COLOR == 1, and the first two colors of
// the current palette are identical, then a deeply faded version of
// that color is used for the background color
CRGB bg;
bg = col_to_crgb(SEGCOLOR(1));
uint8_t bglight = bg.getAverageLight();

View File

@ -87,7 +87,7 @@ void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id);
void serializeState(JsonObject root);
void serializeInfo(JsonObject root);
void serveJson(AsyncWebServerRequest* request);
void serveLiveLeds(AsyncWebServerRequest* request);
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
//led.cpp
void setValuesFromMainSeg();
@ -210,6 +210,11 @@ String settingsProcessor(const String& var);
String dmxProcessor(const String& var);
void serveSettings(AsyncWebServerRequest* request);
//ws.cpp
void handleWs();
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len);
void sendDataWs(AsyncWebSocketClient * client = nullptr);
//xml.cpp
void XML_response(AsyncWebServerRequest *request, char* dest = nullptr);
void URL_response(AsyncWebServerRequest *request);

View File

@ -333,6 +333,12 @@ void serializeInfo(JsonObject root)
root["lip"] = realtimeIP.toString();
}
#ifdef WLED_ENABLE_WEBSOCKETS
root["ws"] = ws.count();
#else
root["ws"] = -1;
#endif
root["fxcount"] = strip.getModeCount();
root["palcount"] = strip.getPaletteCount();
@ -454,21 +460,37 @@ void serveJson(AsyncWebServerRequest* request)
#define MAX_LIVE_LEDS 180
void serveLiveLeds(AsyncWebServerRequest* request)
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient)
{
AsyncWebSocketClient * wsc;
if (!request) { //not HTTP, use Websockets
#ifdef WLED_ENABLE_WEBSOCKETS
wsc = ws.client(wsClient);
if (!wsc || wsc->queueLength() > 0) return false; //only send if queue free
#endif
}
uint16_t used = ledCount;
uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS
char buffer[2000] = "{\"leds\":[";
olen = 9;
obuf = buffer;
olen = 9;
for (uint16_t i= 0; i < used; i += n)
{
olen += sprintf(buffer + olen, "\"%06X\",", strip.getPixelColor(i));
olen += sprintf(obuf + olen, "\"%06X\",", strip.getPixelColor(i));
}
olen -= 1;
oappend("],\"n\":");
oappendi(n);
oappend("}");
request->send(200, "application/json", buffer);
if (request) {
request->send(200, "application/json", buffer);
}
#ifdef WLED_ENABLE_WEBSOCKETS
else {
wsc->text(obuf, olen);
}
#endif
return true;
}

View File

@ -3,7 +3,6 @@
/*
* LED methods
*/
void setValuesFromMainSeg()
{
WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId());
@ -167,6 +166,7 @@ void colorUpdated(int callMode)
void updateInterfaces(uint8_t callMode)
{
sendDataWs();
#ifndef WLED_DISABLE_ALEXA
if (espalexaDevice != nullptr && callMode != NOTIFIER_CALL_MODE_ALEXA) {
espalexaDevice->setValue(bri);

View File

@ -14,8 +14,11 @@ WLED::WLED()
void WLED::reset()
{
briT = 0;
#ifdef WLED_ENABLE_WEBSOCKETS
ws.closeAll(1012);
#endif
long dly = millis();
while (millis() - dly < 250) {
while (millis() - dly < 450) {
yield(); // enough time to send response to client
}
setAllLeds();
@ -94,7 +97,8 @@ void WLED::loop()
if (lastMqttReconnectAttempt > millis()) rolloverMillis++; //millis() rolls over every 50 days
initMqtt();
}
yield();
handleWs();
// DEBUG serial logging
#ifdef WLED_DEBUG
@ -283,6 +287,10 @@ void WLED::initAP(bool resetAP)
void WLED::initConnection()
{
#ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent);
#endif
WiFi.disconnect(true); // close old connections
#ifdef ESP8266
WiFi.setPhyMode(WIFI_PHY_MODE_11N);

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2006251
#define VERSION 2007020
// ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS).
@ -30,6 +30,7 @@
#endif
#define WLED_ENABLE_ADALIGHT // saves 500b only
//#define WLED_ENABLE_DMX // uses 3.5kb (use LEDPIN other than 2)
//#define WLED_ENABLE_WEBSOCKETS
#define WLED_DISABLE_FILESYSTEM // SPIFFS is not used by any WLED feature yet
//#define WLED_ENABLE_FS_SERVING // Enable sending html file from SPIFFS before serving progmem version
@ -480,6 +481,9 @@ WLED_GLOBAL bool doPublishMqtt _INIT(false);
// server library objects
WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));
#ifdef WLED_ENABLE_WEBSOCKETS
WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws")));
#endif
WLED_GLOBAL AsyncClient* hueClient _INIT(NULL);
WLED_GLOBAL AsyncMqttClient* mqtt _INIT(NULL);

View File

@ -205,10 +205,10 @@ void initServer()
} else
{
server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254);
serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254);
});
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254);
serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254);
});
}
@ -226,6 +226,10 @@ void initServer()
if (captivePortal(request)) return;
serveIndexOrWelcome(request);
});
#ifdef WLED_ENABLE_WEBSOCKETS
server.addHandler(&ws);
#endif
//called when the url is not defined here, ajax-in; get-settings
server.onNotFound([](AsyncWebServerRequest *request){
@ -374,7 +378,7 @@ void serveSettings(AsyncWebServerRequest* request)
if (subPage == 1 && wifiLock && otaLock)
{
serveMessage(request, 500, "Access Denied", "Please unlock OTA in security settings!", 254); return;
serveMessage(request, 500, "Access Denied", F("Please unlock OTA in security settings!"), 254); return;
}
#ifdef WLED_DISABLE_MOBILE_UI //disable welcome page if not enough storage

114
wled00/ws.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "wled.h"
/*
* WebSockets server for bidirectional communication
*/
#ifdef WLED_ENABLE_WEBSOCKETS
uint16_t wsLiveClientId = 0;
unsigned long wsLastLiveTime = 0;
//uint8_t* wsFrameBuffer = nullptr;
#define WS_LIVE_INTERVAL 40
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)
{
if(type == WS_EVT_CONNECT){
//client connected
sendDataWs(client);
//client->ping();
} else if(type == WS_EVT_DISCONNECT){
//client disconnected
if (client->id() == wsLiveClientId) wsLiveClientId = 0;
} else if(type == WS_EVT_DATA){
//data packet
AwsFrameInfo * info = (AwsFrameInfo*)arg;
if(info->final && info->index == 0 && info->len == len){
//the whole message is in a single frame and we got all of it's data (max. 1450byte)
if(info->opcode == WS_TEXT)
{
bool verboseResponse = false;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument jsonBuffer(8192);
DeserializationError error = deserializeJson(jsonBuffer, data, len);
JsonObject root = jsonBuffer.as<JsonObject>();
if (error || root.isNull()) return;
if (root.containsKey("lv"))
{
wsLiveClientId = root["lv"] ? client->id() : 0;
}
verboseResponse = deserializeState(root);
}
if (verboseResponse || millis() - lastInterfaceUpdate < 1900) sendDataWs(client); //update if it takes longer than 100ms until next "broadcast"
}
} else {
//message is comprised of multiple frames or the frame is split into multiple packets
//if(info->index == 0){
//if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096];
//}
//if (wsFrameBuffer && len < 4096 && info->index + info->)
//{
//}
if((info->index + len) == info->len){
if(info->final){
if(info->message_opcode == WS_TEXT) {
client->text("{\"error\":10}"); //we do not handle split packets right now
}
}
}
}
} else if(type == WS_EVT_ERROR){
//error was received from the other end
} else if(type == WS_EVT_PONG){
//pong message was received (in response to a ping request maybe)
}
}
void sendDataWs(AsyncWebSocketClient * client)
{
if (!ws.count()) return;
AsyncWebSocketMessageBuffer * buffer;
{ //scope JsonDocument so it releases its buffer
DynamicJsonDocument doc(8192);
JsonObject state = doc.createNestedObject("state");
serializeState(state);
JsonObject info = doc.createNestedObject("info");
serializeInfo(info);
size_t len = measureJson(doc);
buffer = ws.makeBuffer(len);
if (!buffer) return; //out of memory
serializeJson(doc, (char *)buffer->get(), len +1);
}
if (client) {
client->text(buffer);
} else {
ws.textAll(buffer);
}
}
void handleWs()
{
if (millis() - wsLastLiveTime > WS_LIVE_INTERVAL)
{
ws.cleanupClients();
bool success = true;
if (wsLiveClientId)
success = serveLiveLeds(nullptr, wsLiveClientId);
wsLastLiveTime = millis();
if (!success) wsLastLiveTime -= 20; //try again in 20ms if failed due to non-empty WS queue
}
}
#else
void handleWs() {}
void sendDataWs(AsyncWebSocketClient * client) {}
#endif