.... actually we undo gamma adjustments, because screens (laptop, pad, etc) will apply their own gamma corrections. Activate in LED settings: "Use Gamma correction for preview" Colors in WLED are sometimes gamma corrected, sometimes not. This change tries to make the best out of the color mess, but its still not working properly in all configurations.
283 lines
9.4 KiB
C++
283 lines
9.4 KiB
C++
#include "wled.h"
|
|
|
|
/*
|
|
* WebSockets server for bidirectional communication
|
|
*/
|
|
#ifdef WLED_ENABLE_WEBSOCKETS
|
|
|
|
static volatile uint16_t wsLiveClientId = 0; // WLEDMM added "static"
|
|
static volatile unsigned long wsLastLiveTime = 0; // WLEDMM
|
|
//uint8_t* wsFrameBuffer = nullptr;
|
|
|
|
#if !defined(ARDUINO_ARCH_ESP32) || defined(WLEDMM_FASTPATH) // WLEDMM
|
|
#define WS_LIVE_INTERVAL 120
|
|
#else
|
|
#define WS_LIVE_INTERVAL 80
|
|
#endif
|
|
|
|
void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)
|
|
{
|
|
if(type == WS_EVT_CONNECT){
|
|
//client connected
|
|
DEBUG_PRINTLN(F("WS client connected."));
|
|
sendDataWs(client);
|
|
} else if(type == WS_EVT_DISCONNECT){
|
|
//client disconnected
|
|
if (client->id() == wsLiveClientId) wsLiveClientId = 0;
|
|
DEBUG_PRINTLN(F("WS client disconnected."));
|
|
} else if(type == WS_EVT_DATA){
|
|
DEBUG_PRINTLN(F("WS event 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 its data (max. 1450 bytes)
|
|
if(info->opcode == WS_TEXT)
|
|
{
|
|
if (len > 0 && len < 10 && data[0] == 'p') {
|
|
// application layer ping/pong heartbeat.
|
|
// client-side socket layer ping packets are unresponded (investigate)
|
|
client->text(F("pong"));
|
|
return;
|
|
}
|
|
|
|
bool verboseResponse = false;
|
|
if (!requestJSONBufferLock(11)) return;
|
|
|
|
DeserializationError error = deserializeJson(doc, data, len);
|
|
JsonObject root = doc.as<JsonObject>();
|
|
if (error || root.isNull()) {
|
|
releaseJSONBufferLock();
|
|
return;
|
|
}
|
|
if (root["v"] && root.size() == 1) {
|
|
//if the received value is just "{"v":true}", send only to this client
|
|
verboseResponse = true;
|
|
} else if (root.containsKey("lv")) {
|
|
wsLiveClientId = root["lv"] ? client->id() : 0;
|
|
} else {
|
|
verboseResponse = deserializeState(root);
|
|
}
|
|
releaseJSONBufferLock(); // will clean fileDoc
|
|
|
|
// force broadcast in 500ms after updating client
|
|
if (verboseResponse) {
|
|
sendDataWs(client);
|
|
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500);
|
|
} else {
|
|
// we have to send something back otherwise WS connection closes
|
|
client->text(F("{\"success\":true}"));
|
|
lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500);
|
|
}
|
|
}
|
|
} 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(F("{\"error\":9}")); //we do not handle split packets right now
|
|
}
|
|
}
|
|
}
|
|
DEBUG_PRINTLN(F("WS multipart message."));
|
|
}
|
|
} else if(type == WS_EVT_ERROR){
|
|
//error was received from the other end
|
|
USER_PRINTLN(F("WS error."));
|
|
|
|
} else if(type == WS_EVT_PONG){
|
|
//pong message was received (in response to a ping request maybe)
|
|
DEBUG_PRINTLN(F("WS pong."));
|
|
|
|
}
|
|
}
|
|
|
|
void sendDataWs(AsyncWebSocketClient * client)
|
|
{
|
|
DEBUG_PRINTF("sendDataWs\n");
|
|
if (!ws.count()) return;
|
|
AsyncWebSocketMessageBuffer * buffer;
|
|
|
|
if (!requestJSONBufferLock(12)) return;
|
|
|
|
JsonObject state = doc.createNestedObject("state");
|
|
serializeState(state);
|
|
JsonObject info = doc.createNestedObject("info");
|
|
serializeInfo(info);
|
|
|
|
size_t len = measureJson(doc);
|
|
DEBUG_PRINTF("JSON buffer size: %u for WS request (%u).\n", doc.memoryUsage(), len);
|
|
|
|
#ifdef ESP8266
|
|
size_t heap1 = ESP.getFreeHeap(); // WLEDMM moved into 8266 specific section
|
|
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap());
|
|
if (len>heap1) {
|
|
DEBUG_PRINTLN(F("Out of memory (WS)!"));
|
|
return;
|
|
}
|
|
#else
|
|
// DEBUG_PRINTF("%s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM
|
|
#endif
|
|
if (len < 1) return; // WLEDMM do not allocate 0 size buffer
|
|
|
|
// WLEDMM use exceptions to catch out-of-memory errors
|
|
#if __cpp_exceptions
|
|
try{
|
|
buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes on ESP8266
|
|
} catch(...) {
|
|
buffer = nullptr;
|
|
}
|
|
#else
|
|
buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes on ESP8266
|
|
#endif
|
|
#ifdef ESP8266
|
|
size_t heap2 = ESP.getFreeHeap();
|
|
DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap());
|
|
#else
|
|
size_t heap1 = len+16; // WLEDMM
|
|
size_t heap2 = 0; // ESP32 variants do not have the same issue and will work without checking heap allocation
|
|
#endif
|
|
if (!buffer || heap1-heap2<len) {
|
|
releaseJSONBufferLock();
|
|
USER_PRINTLN(F("WS buffer allocation failed."));
|
|
ws.closeAll(1013); //code 1013 = temporary overload, try again later
|
|
ws.cleanupClients(0); //disconnect all clients to release memory
|
|
ws._cleanBuffers();
|
|
return; //out of memory
|
|
}
|
|
|
|
buffer->lock();
|
|
serializeJson(doc, (char *)buffer->get(), len);
|
|
|
|
DEBUG_PRINT(F("Sending WS data "));
|
|
if (client) {
|
|
client->text(buffer);
|
|
DEBUG_PRINTLN(F("to a single client."));
|
|
} else {
|
|
ws.textAll(buffer);
|
|
DEBUG_PRINTLN(F("to multiple clients."));
|
|
}
|
|
buffer->unlock();
|
|
ws._cleanBuffers();
|
|
|
|
releaseJSONBufferLock();
|
|
}
|
|
|
|
// WLEDMM function to recover full-brigh pixel (based on code from upstream alt-buffer, which is based on code from NeoPixelBrightnessBus)
|
|
static uint32_t restoreColorLossy(uint32_t c, uint_fast8_t _restaurationBri) {
|
|
if (_restaurationBri == 255) return c;
|
|
uint8_t* chan = (uint8_t*) &c;
|
|
for (uint_fast8_t i=0; i<4; i++) {
|
|
uint_fast16_t val = chan[i];
|
|
chan[i] = ((val << 8) + _restaurationBri) / (_restaurationBri + 1); //adding _bri slighly improves recovery / stops degradation on re-scale
|
|
}
|
|
return c;
|
|
}
|
|
|
|
static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static"
|
|
{
|
|
AsyncWebSocketClient * wsc = ws.client(wsClient);
|
|
if (!wsc || wsc->queueLength() > 0) return false; //only send if queue free
|
|
|
|
size_t used = strip.getLengthTotal();
|
|
#ifdef ESP8266
|
|
constexpr size_t MAX_LIVE_LEDS_WS = 256U;
|
|
#else
|
|
constexpr size_t MAX_LIVE_LEDS_WS = 4096U; //WLEDMM use 4096 as max matrix size
|
|
#endif
|
|
size_t n = ((used -1)/MAX_LIVE_LEDS_WS) +1; //only serve every n'th LED if count over MAX_LIVE_LEDS_WS
|
|
size_t pos = (strip.isMatrix ? 4 : 2);
|
|
size_t bufSize = pos + (used/n)*3;
|
|
//WLEDMM: no skipLines
|
|
|
|
if ((bufSize < 1) || (used < 1)) return(false); // WLEDMM should not happen
|
|
//AsyncWebSocketMessageBuffer * wsBuf = ws.makeBuffer(bufSize);
|
|
// WLEDMM protect against exceptions due to low memory
|
|
AsyncWebSocketMessageBuffer * wsBuf = nullptr;
|
|
#if __cpp_exceptions
|
|
try{
|
|
#endif
|
|
wsBuf = ws.makeBuffer(bufSize);
|
|
#if __cpp_exceptions
|
|
} catch(...) {
|
|
#else
|
|
if (wsBuf == nullptr) { // 8266 does not support exceptions
|
|
#endif
|
|
wsBuf = nullptr;
|
|
USER_PRINTLN(F("WS buffer allocation failed."));
|
|
//ws.closeAll(1013); //code 1013 = temporary overload, try again later
|
|
//ws.cleanupClients(0); //disconnect all clients to release memory
|
|
ws._cleanBuffers();
|
|
}
|
|
|
|
if (!wsBuf) return false; //out of memory
|
|
uint8_t* buffer = wsBuf->get();
|
|
if (!buffer) return false; //out of memory
|
|
|
|
wsBuf->lock(); // protect buffer from being cleaned by another WS instance
|
|
buffer[0] = 'L';
|
|
buffer[1] = 1; //version
|
|
#ifndef WLED_DISABLE_2D
|
|
if (strip.isMatrix) {
|
|
buffer[1] = 2; //version
|
|
buffer[2] = min(Segment::maxWidth, (uint16_t) 255); // WLEDMM prevent overflow on buffer type uint8_t
|
|
buffer[3] = min(Segment::maxHeight, (uint16_t) 255);
|
|
//WLEDMM: no skipLines
|
|
}
|
|
#endif
|
|
|
|
uint8_t stripBrightness = strip.getBrightness();
|
|
for (size_t i = 0; pos < bufSize -2; i += n)
|
|
{
|
|
//WLEDMM: no skipLines
|
|
uint32_t c = restoreColorLossy(strip.getPixelColor(i), stripBrightness); // WLEDMM full bright preview - does _not_ recover ABL reductions
|
|
// WLEDMM begin: preview with color gamma correction
|
|
if (gammaCorrectPreview && gammaCorrectCol) {
|
|
uint8_t w = W(c); // not sure why, but it looks better if using "white" without corrections
|
|
buffer[pos++] = qadd8(w, unGamma8(R(c))); //R, add white channel to RGB channels as a simple RGBW -> RGB map
|
|
buffer[pos++] = qadd8(w, unGamma8(G(c))); //G
|
|
buffer[pos++] = qadd8(w, unGamma8(B(c))); //B
|
|
} else {
|
|
// WLEDMM end
|
|
uint8_t w = W(c); // WLEDMM small optimization
|
|
buffer[pos++] = qadd8(w, R(c)); //R, add white channel to RGB channels as a simple RGBW -> RGB map
|
|
buffer[pos++] = qadd8(w, G(c)); //G
|
|
buffer[pos++] = qadd8(w, B(c)); //B
|
|
}
|
|
}
|
|
|
|
wsc->binary(wsBuf);
|
|
wsBuf->unlock(); // un-protect buffer
|
|
ws._cleanBuffers(); // cleans up if the message is not added to any clients.
|
|
return true;
|
|
}
|
|
|
|
void handleWs()
|
|
{
|
|
if (millis() - wsLastLiveTime > max((strip.getLengthTotal()/20), WS_LIVE_INTERVAL)) //WLEDMM dynamic nr of peek frames per second
|
|
{
|
|
#ifdef ESP8266
|
|
ws.cleanupClients(3);
|
|
#else
|
|
ws.cleanupClients();
|
|
#endif
|
|
bool success = true;
|
|
if (wsLiveClientId) success = sendLiveLedsWs(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
|