diff --git a/wled00/const.h b/wled00/const.h index 874776e323..c0bd9cc4d5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -134,6 +134,7 @@ #define REALTIME_MODE_ARTNET 6 #define REALTIME_MODE_TPM2NET 7 #define REALTIME_MODE_DDP 8 +#define REALTIME_MODE_OPC 9 //realtime override modes #define REALTIME_OVERRIDE_NONE 0 diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ab5096b550..36671cbdd6 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -187,6 +187,16 @@ void handleOverlayDraw(); void _overlayAnalogCountdown(); void _overlayAnalogClock(); +//opc.cpp +void initOPCServer(); +void freeOPCClient(AsyncClient* client); +void handleOPCClient(void* arg, AsyncClient* client); +void handleOPCData(void* arg, AsyncClient* client, void *data, size_t len); +void handleOPCTimeOut(void* arg, AsyncClient* client, uint32_t time); +void handleOPCError(void* arg, AsyncClient* client, int8_t error); +void handleOPCDisconnect(void* arg, AsyncClient* client); +void handleOPCPacket(); + //playlist.cpp void shufflePlaylist(); void unloadPlaylist(); diff --git a/wled00/json.cpp b/wled00/json.cpp index dd6494aa0f..213626e85b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -607,6 +607,7 @@ void serializeInfo(JsonObject root) case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break; case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break; case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break; + case REALTIME_MODE_OPC: root["lm"] = F("Open Pixel Control"); break; } if (realtimeIP[0] == 0) diff --git a/wled00/opc.cpp b/wled00/opc.cpp new file mode 100644 index 0000000000..b21bd05461 --- /dev/null +++ b/wled00/opc.cpp @@ -0,0 +1,143 @@ +/* + OPC Protocol support +*/ + +#include "wled.h" +#include "cbuf.h" + +#ifdef WLED_ENABLE_OPC + +#define OPC_SERVER_PORT 7890 +#define OPC_LISTEN_CHANNEL 1 +#define OPC_MAX_FRAME_STORAGE 30 +#define OPC_HEADER_SIZE 4 + +void freeOPCClient(AsyncClient* client) { + client->close(true); + delete clientServed; + clientServed = nullptr; +} + +void handleOPCError(void* arg, AsyncClient* client, int8_t error) { + freeOPCClient(client); +} + +void handleOPCDisconnect(void* arg, AsyncClient* client) { + freeOPCClient(client); +} + +void handleOPCTimeOut(void* arg, AsyncClient* client, uint32_t time) { + freeOPCClient(client); +} + +void handleOPCData(void* arg, AsyncClient* client, void *data, size_t len) { + if (len > opcCircularBufPtr->room()) return; + for (unsigned int i = 0; i < len; i++) { + opcCircularBufPtr->write(*((uint8_t*)data + i)); + } +} + +void handleOPCClient(void* arg, AsyncClient* client) { + //add client + if (clientServed != nullptr) freeOPCClient(client); + clientServed = client; + + //clean and initialize buffer + opcDataLen = 0; + opcCircularBufPtr->flush(); + opcParserWaitingForData = false; + + // register client events + client->onData(&handleOPCData, NULL); + client->onError(&handleOPCError, NULL); + client->onDisconnect(&handleOPCDisconnect, NULL); + client->onTimeout(&handleOPCTimeOut, NULL); +} + +void discardInvalidData(uint16_t len) { + if (len > opcCircularBufPtr->available()) { + opcCircularBufPtr->flush(); + } else { + for (uint16_t i = 0; i < len; i++) opcCircularBufPtr->read(); + } +} + +void parseOPCLedValues(uint8_t chan, uint8_t cmd) { + const uint16_t opcDataLenDiv = opcDataLen / 3; + uint8_t r, g, b; + if ((chan == 0 || chan == OPC_LISTEN_CHANNEL) && cmd == 0) { + realtimeLock(realtimeTimeoutMs, REALTIME_MODE_OPC); + + for (uint16_t i = 0; i < opcDataLenDiv && i < ledCount; i++) { + r = opcCircularBufPtr->read(); + g = opcCircularBufPtr->read(); + b = opcCircularBufPtr->read(); + setRealtimePixel(i, r, g, b, 0); + + } + strip.show(); + if (opcDataLen > ledCount * 3) discardInvalidData(opcDataLen - ledCount * 3); + } else { + discardInvalidData(opcDataLen); + } +} + +void handleOPCPacket() { + // channel and command variables need staticity because of fragmented packet possibility + static uint8_t chan, cmd; + + if (opcCircularBufPtr != nullptr && millis() - strip.getLastShow() > 15) { + if (opcCircularBufPtr->available() >= 4 && !realtimeOverride) { + //there's at least a header and no override + if(!opcParserWaitingForData) { + chan = (uint8_t)opcCircularBufPtr->read(); + cmd = (uint8_t)opcCircularBufPtr->read(); + opcDataLen = (uint8_t)opcCircularBufPtr->read() << 8 | (uint8_t)opcCircularBufPtr->read(); + if (opcDataLen > opcCircularBufPtr->available()) { + opcParserWaitingForData = true; + } else { + parseOPCLedValues(chan, cmd); + } + } else { + if (opcDataLen <= opcCircularBufPtr->available()) { + parseOPCLedValues(chan, cmd); + opcParserWaitingForData = false; + } + } + } else if (realtimeOverride && opcCircularBufPtr->available()) { + opcCircularBufPtr->flush(); + opcParserWaitingForData = false; + } + } +} + +void initOPCServer() { + if (opcServer != nullptr) { + opcServer->end(); + delete opcServer; + opcServer = nullptr; + } + if (opcCircularBufPtr != nullptr) { + opcCircularBufPtr->flush(); + delete opcCircularBufPtr; + opcCircularBufPtr = nullptr; + } + opcCircularBufPtr = new cbuf(OPC_MAX_FRAME_STORAGE * (ledCount + OPC_HEADER_SIZE)); + opcServer = new AsyncServer(OPC_SERVER_PORT); + opcServer->onClient(&handleOPCClient, opcServer); + opcServer->setNoDelay(true); + opcServer->begin(); + + opcDataLen = 0; + opcParserWaitingForData = false; +} + +#else +void initOPCServer(){} +void handleOPCClient() {} +void freeOPCClient(){} +void handleOPCData() {} +void handleOPCTimeOut (){} +void handleOPCError (){} +void handleOPCDisconnect (){} +#endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index b0b95165f0..6ef987cb40 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -69,6 +69,9 @@ void WLED::loop() handleAlexa(); #endif + #ifdef WLED_ENABLE_OPC + handleOPCPacket(); + #endif yield(); if (doSerializeConfig) serializeConfig(); @@ -706,6 +709,9 @@ void WLED::initInterfaces() ddp.begin(false, DDP_DEFAULT_PORT); reconnectHue(); initMqtt(); +#ifdef WLED_ENABLE_OPC + initOPCServer(); +#endif interfacesInited = true; wasConnected = true; } diff --git a/wled00/wled.h b/wled00/wled.h index 98748bee6b..0211add012 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -40,6 +40,7 @@ #ifndef WLED_DISABLE_WEBSOCKETS #define WLED_ENABLE_WEBSOCKETS #endif +#define WLED_ENABLE_OPC #define WLED_ENABLE_FS_EDITOR // enable /edit page for editing FS content. Will also be disabled with OTA lock @@ -455,6 +456,14 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start #endif +#ifdef WLED_ENABLE_OPC //opc test support + WLED_GLOBAL AsyncClient* clientServed _INIT(nullptr); + WLED_GLOBAL AsyncServer* opcServer _INIT(nullptr); + WLED_GLOBAL uint16_t opcDataLen _INIT(0); + WLED_GLOBAL cbuf* opcCircularBufPtr _INIT(nullptr); + WLED_GLOBAL bool opcParserWaitingForData _INIT(false); +#endif + // internal global variable declarations // wifi WLED_GLOBAL bool apActive _INIT(false);