Skip to content

Implementation of OPC Protocol#2092

Closed
fnirino wants to merge 2 commits intowled:mainfrom
fnirino:opc_pr
Closed

Implementation of OPC Protocol#2092
fnirino wants to merge 2 commits intowled:mainfrom
fnirino:opc_pr

Conversation

@fnirino
Copy link
Copy Markdown

@fnirino fnirino commented Jul 23, 2021

Tested implementation of Open Pixel Control protocol for setting pixel colors.

Test setup: NodeMCU v1.0 (ESP8266-12) with 24 LED matrix

Highlights:

  • Receives 60FPS without dropping any packet with a 30 frame buffer
  • Fixed framerate using same "delay" as E1.31 implementation
  • Displays correct message in "Receiving live data" page

Improvements I've thought of (but don't know how feasible / useful they are):

  • Web page error toast on too many dropped packets.
  • Getting a SysEx ID to set brightness, effects, palettes, etc.
  • Web settings for listening channel and port

Notes
Only supports one client at a time and listens on one channel (besides from broadcast)
Default port is 7890 (as per opc test scripts default port)

Open to suggestions and feedback!

Summary by CodeRabbit

  • New Features
    • Added Open Pixel Control (OPC) protocol support for realtime LED control
    • OPC server listens on port 7890, accepting a single client connection
    • Supports OPC channels 0 and 1 for RGB LED data control

Comment thread wled00/wled.h
#ifndef WLED_DISABLE_WEBSOCKETS
#define WLED_ENABLE_WEBSOCKETS
#endif
#define WLED_ENABLE_OPC
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add #ifndef WLED_DISABLE_OPC to allow complation without OPC. Similar as a line above.

@stale
Copy link
Copy Markdown

stale Bot commented Dec 9, 2021

Hey! This issue has been open for quite some time without any new comments now. It will be closed automatically in a week if no further activity occurs.
Thank you for using WLED!

@stale stale Bot added the stale This issue will be closed soon because of prolonged inactivity label Dec 9, 2021
@stale stale Bot closed this Apr 16, 2022
@Aircoookie
Copy link
Copy Markdown
Member

This still looks potentially interesting. While I don't think realtime over TCP makes enough sense to add this by default, an OPC usermod might be a great idea.

@Aircoookie Aircoookie reopened this Oct 21, 2022
@Aircoookie Aircoookie added keep This issue will never become stale/closed automatically and removed stale This issue will be closed soon because of prolonged inactivity labels Oct 21, 2022
@softhack007
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

Walkthrough

This PR introduces Open Pixel Control (OPC) protocol support to WLED. It adds a new realtime mode constant, an async TCP server accepting client connections on port 7890, packet parsing logic with circular buffer management, LED data extraction, and integration into the main WLED event loop.

Changes

Cohort / File(s) Summary
Configuration & Global State
wled00/const.h, wled00/wled.h
Added REALTIME_MODE_OPC constant (value 9) and compile-time feature flag WLED_ENABLE_OPC with associated global variables for async client/server objects, buffer pointers, and parser state.
Function Declarations
wled00/fcn_declare.h
Added 8 OPC-related function declarations covering server lifecycle (initOPCServer, freeOPCClient), async client event handlers, and packet processing (handleOPCPacket).
OPC Protocol Implementation
wled00/opc.cpp
Implemented complete OPC module with async TCP server on port 7890, circular buffer-based data ingestion, frame parser with header/payload validation, fragmentation handling, LED data extraction for RGB channels, and conditional compilation stubs when feature is disabled.
Integration & Serialization
wled00/wled.cpp, wled00/json.cpp
Integrated OPC server initialization and packet handling into main WLED lifecycle; added JSON serialization mapping REALTIME_MODE_OPC to "Open Pixel Control" in info endpoint.

Sequence Diagram

sequenceDiagram
    participant Client as OPC Client
    participant Server as AsyncServer
    participant Buffer as Circular Buffer
    participant Parser as handleOPCPacket
    participant LEDs as LED Strip

    Client->>Server: Connect on port 7890
    activate Server
    Server->>Server: Accept & register client
    deactivate Server

    loop Incoming Data
        Client->>Server: Send OPC frame
        Server->>Buffer: handleOPCData()<br/>(write to circular buffer)
        activate Buffer
        Buffer-->>Server: Data buffered
        deactivate Buffer
    end

    loop Main Event Loop
        Parser->>Buffer: Check for ≥4 byte header
        activate Buffer
        Buffer-->>Parser: Read header (cmd, channel, length)
        deactivate Buffer
        
        alt Complete Frame Available
            Parser->>Parser: parseOPCLedValues()<br/>(extract RGB data)
            Parser->>LEDs: Apply realtime LED values
            LEDs->>LEDs: strip.show()
            Parser->>Buffer: Consume frame bytes
        else Incomplete Frame
            Parser->>Parser: Wait for more data
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implementation of OPC Protocol' directly and clearly summarizes the main change in the changeset: adding support for the Open Pixel Control protocol.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch opc_pr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
wled00/opc.cpp (1)

135-143: ⚠️ Potential issue | 🔴 Critical

Stub function signatures don't match declarations.

When WLED_ENABLE_OPC is not defined, the stub functions have mismatched signatures compared to fcn_declare.h. For example, handleOPCClient() is declared with (void* arg, AsyncClient* client) but stubbed as void handleOPCClient() {}. This causes linker errors.

See the fix proposed in the fcn_declare.h review comment — either wrap the declarations in #ifdef or provide properly-signed stubs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 135 - 143, The stub functions under the `#else`
block currently have wrong signatures; update them to exactly match the
declarations in fcn_declare.h (or conditionally compile those declarations) —
specifically change initOPCServer, handleOPCClient, freeOPCClient,
handleOPCData, handleOPCTimeOut, handleOPCError, and handleOPCDisconnect to the
same parameter lists and return types as declared (e.g., handleOPCClient(void*
arg, AsyncClient* client) etc.), or surround the related declarations in
fcn_declare.h with `#ifdef` WLED_ENABLE_OPC so that the no-OP stubs and the
declarations stay consistent.
wled00/wled.h (1)

43-43: ⚠️ Potential issue | 🟠 Major

OPC is unconditionally enabled — should be opt-in.

WLED_ENABLE_OPC is defined without any conditional guard, meaning OPC will be compiled into every build. Given the maintainer's feedback about OPC potentially being better suited as a usermod rather than a default feature, this should follow the pattern of other optional features like WLED_ENABLE_DMX (line 35) which is commented out by default.

Proposed fix: make OPC opt-in
-#define WLED_ENABLE_OPC
+//#define WLED_ENABLE_OPC          // uses ~Xkb (TCP server for Open Pixel Control protocol)

Users who want OPC can then enable it via build flags (-D WLED_ENABLE_OPC) or by uncommenting this line.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/wled.h` at line 43, WLED_ENABLE_OPC is currently unconditionally
defined causing OPC to be compiled into every build; make it opt-in by removing
the unconditional definition and following the same pattern as WLED_ENABLE_DMX —
either comment out the line defining WLED_ENABLE_OPC in wled.h so users must
enable it manually or wrap it behind a guarded macro check so it is only active
when passed via build flags (e.g., -D WLED_ENABLE_OPC); update any accompanying
comment to instruct users to enable OPC via build flags or by uncommenting the
define.
🧹 Nitpick comments (1)
wled00/opc.cpp (1)

114-133: Potential race: server accepts clients before buffer is ready.

opcServer->begin() (line 129) starts accepting connections immediately, but opcCircularBufPtr is allocated on line 125. If a client connects and sends data extremely quickly, callbacks could fire while initialization is still in progress. Consider moving begin() after all state is initialized, or add null guards in callbacks (as noted above).

Also, use 2-space indentation per coding guidelines (tabs detected on lines 22-23, 26, 43, 49-54).

Suggested reordering
 void initOPCServer() {
   if (opcServer != nullptr) {
     opcServer->end();
     delete opcServer;
     opcServer = nullptr;
   }
   if (opcCircularBufPtr != nullptr) {
     opcCircularBufPtr->flush();
     delete opcCircularBufPtr;
     opcCircularBufPtr = nullptr;
   }
+
+  // Initialize all state before starting server
   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;
+
+  // Now start server
+  opcServer = new AsyncServer(OPC_SERVER_PORT);
+  opcServer->onClient(&handleOPCClient, opcServer);
+  opcServer->setNoDelay(true);
+  opcServer->begin();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 114 - 133, The initOPCServer function starts
accepting clients before all state is ready (opcServer->begin() is called before
ensuring opcCircularBufPtr is fully initialized), which can race with
handleOPCClient callbacks; fix by allocating and initializing opcCircularBufPtr
and setting opcDataLen/opcParserWaitingForData and any other state first, then
construct opcServer, register onClient (handleOPCClient, opcServer) and only
call opcServer->begin() after all state is ready, and/or add defensive null
checks inside handleOPCClient to guard against opcCircularBufPtr/opcServer being
null; also reformat the function to use 2-space indentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/fcn_declare.h`:
- Around line 190-198: The OPC-related function declarations (initOPCServer,
freeOPCClient, handleOPCClient, handleOPCData, handleOPCTimeOut, handleOPCError,
handleOPCDisconnect, handleOPCPacket) must be wrapped with `#ifdef`
WLED_ENABLE_OPC ... `#endif` in fcn_declare.h so they are only declared when OPC
is enabled, and the stub implementations in opc.cpp must have matching
signatures (e.g., change empty-parameter stubs like void handleOPCClient() {} to
void handleOPCClient(void* arg, AsyncClient* client) {} and similarly for
handleOPCData, handleOPCTimeOut, handleOPCError, handleOPCDisconnect) so the
declarations and definitions match and avoid linker errors when WLED_ENABLE_OPC
is not defined.

In `@wled00/opc.cpp`:
- Around line 33-38: handleOPCData currently dereferences opcCircularBufPtr
without verifying it is valid; add a null check at the top of handleOPCData to
return immediately if opcCircularBufPtr is nullptr (or otherwise not
initialized) before calling opcCircularBufPtr->room() or ->write(), so the
callback cannot crash if invoked before initialization or after teardown; ensure
the early return covers both the room() size check and the write loop to avoid
any race where opcCircularBufPtr becomes null between checks.
- Around line 40-55: In handleOPCClient: do not call freeOPCClient(client) when
clientServed != nullptr — that closes the new client and corrupts the served
pointer; instead immediately reject the incoming connection by closing/stopping
the incoming AsyncClient (e.g., client->close(true) or client->stop()) and
return without changing clientServed; only assign clientServed = client and
initialize state (opcDataLen, opcParserWaitingForData) and register callbacks
when accepting a client; also guard the buffer flush call with a null check (if
(opcCircularBufPtr) opcCircularBufPtr->flush()) so opcCircularBufPtr->flush()
cannot crash when uninitialized.
- Around line 15-19: freeOPCClient deletes the global clientServed instead of
the passed-in AsyncClient* client causing undefined behavior; change the
function to consistently operate on the passed parameter by calling
client->close(true), deleting client, and only nulling clientServed if the
pointer being deleted equals clientServed (i.e., if (clientServed == client)
clientServed = nullptr), or alternatively make the function operate only on the
global by removing the parameter and using clientServed throughout — update
freeOPCClient, its signature/usages, and any callers to match the chosen
consistent approach.
- Around line 65-83: In parseOPCLedValues, replace the undeclared variable
ledCount with strip.getLengthTotal() in all uses (the loop bound, the later
comparison for discardInvalidData, and any other occurrence) so the code
compiles; keep the logic using opcDataLen/opcDataLenDiv,
opcCircularBufPtr->read(), setRealtimePixel(...), realtimeLock(...,
REALTIME_MODE_OPC) and strip.show() unchanged. After the replacement, ensure the
call to discardInvalidData(opcDataLen - strip.getLengthTotal() * 3) is safe by
confirming discardInvalidData correctly bounds/checks large or negative lengths
when opcDataLen is corrupted or malformed.

---

Duplicate comments:
In `@wled00/opc.cpp`:
- Around line 135-143: The stub functions under the `#else` block currently have
wrong signatures; update them to exactly match the declarations in fcn_declare.h
(or conditionally compile those declarations) — specifically change
initOPCServer, handleOPCClient, freeOPCClient, handleOPCData, handleOPCTimeOut,
handleOPCError, and handleOPCDisconnect to the same parameter lists and return
types as declared (e.g., handleOPCClient(void* arg, AsyncClient* client) etc.),
or surround the related declarations in fcn_declare.h with `#ifdef`
WLED_ENABLE_OPC so that the no-OP stubs and the declarations stay consistent.

In `@wled00/wled.h`:
- Line 43: WLED_ENABLE_OPC is currently unconditionally defined causing OPC to
be compiled into every build; make it opt-in by removing the unconditional
definition and following the same pattern as WLED_ENABLE_DMX — either comment
out the line defining WLED_ENABLE_OPC in wled.h so users must enable it manually
or wrap it behind a guarded macro check so it is only active when passed via
build flags (e.g., -D WLED_ENABLE_OPC); update any accompanying comment to
instruct users to enable OPC via build flags or by uncommenting the define.

---

Nitpick comments:
In `@wled00/opc.cpp`:
- Around line 114-133: The initOPCServer function starts accepting clients
before all state is ready (opcServer->begin() is called before ensuring
opcCircularBufPtr is fully initialized), which can race with handleOPCClient
callbacks; fix by allocating and initializing opcCircularBufPtr and setting
opcDataLen/opcParserWaitingForData and any other state first, then construct
opcServer, register onClient (handleOPCClient, opcServer) and only call
opcServer->begin() after all state is ready, and/or add defensive null checks
inside handleOPCClient to guard against opcCircularBufPtr/opcServer being null;
also reformat the function to use 2-space indentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 048ca7c2-8c48-4e46-9456-5588b9859218

📥 Commits

Reviewing files that changed from the base of the PR and between c2ac215 and 784e223.

📒 Files selected for processing (6)
  • wled00/const.h
  • wled00/fcn_declare.h
  • wled00/json.cpp
  • wled00/opc.cpp
  • wled00/wled.cpp
  • wled00/wled.h

Comment thread wled00/fcn_declare.h
Comment on lines +190 to +198
//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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Function declarations lack #ifdef WLED_ENABLE_OPC guard, and stub signatures mismatch.

The OPC function declarations here have full signatures (e.g., void handleOPCClient(void* arg, AsyncClient* client)), but the stubs in opc.cpp (lines 137-142) have empty parameter lists (e.g., void handleOPCClient() {}). This will cause linker errors when WLED_ENABLE_OPC is not defined.

Proposed fix: wrap declarations in ifdef and fix stub signatures

In fcn_declare.h:

 //opc.cpp
+#ifdef WLED_ENABLE_OPC
 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();
+#endif
+void handleOPCPacket();  // Always declared, has stub when OPC disabled

In opc.cpp, fix stubs:

 `#else`
 void initOPCServer(){}
-void handleOPCClient() {}
-void freeOPCClient(){}
-void handleOPCData() {}
-void handleOPCTimeOut (){}
-void handleOPCError (){}
-void handleOPCDisconnect (){}
+void handleOPCPacket(){}
 `#endif`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/fcn_declare.h` around lines 190 - 198, The OPC-related function
declarations (initOPCServer, freeOPCClient, handleOPCClient, handleOPCData,
handleOPCTimeOut, handleOPCError, handleOPCDisconnect, handleOPCPacket) must be
wrapped with `#ifdef` WLED_ENABLE_OPC ... `#endif` in fcn_declare.h so they are only
declared when OPC is enabled, and the stub implementations in opc.cpp must have
matching signatures (e.g., change empty-parameter stubs like void
handleOPCClient() {} to void handleOPCClient(void* arg, AsyncClient* client) {}
and similarly for handleOPCData, handleOPCTimeOut, handleOPCError,
handleOPCDisconnect) so the declarations and definitions match and avoid linker
errors when WLED_ENABLE_OPC is not defined.

Comment thread wled00/opc.cpp
Comment on lines +15 to +19
void freeOPCClient(AsyncClient* client) {
client->close(true);
delete clientServed;
clientServed = nullptr;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: freeOPCClient deletes wrong pointer.

The function receives client as a parameter but deletes clientServed (the global). This causes undefined behavior when client != clientServed. The function should consistently operate on either the parameter or the global, not mix them.

Proposed fix
 void freeOPCClient(AsyncClient* client) {
-  client->close(true);
-  delete clientServed;
+  if (client == nullptr) return;
+  client->close(true);
+  client->free();
+  delete client;
+  if (client == clientServed) clientServed = nullptr;
-  clientServed = nullptr;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 15 - 19, freeOPCClient deletes the global
clientServed instead of the passed-in AsyncClient* client causing undefined
behavior; change the function to consistently operate on the passed parameter by
calling client->close(true), deleting client, and only nulling clientServed if
the pointer being deleted equals clientServed (i.e., if (clientServed == client)
clientServed = nullptr), or alternatively make the function operate only on the
global by removing the parameter and using clientServed throughout — update
freeOPCClient, its signature/usages, and any callers to match the chosen
consistent approach.

Comment thread wled00/opc.cpp
Comment on lines +33 to +38
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));
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing null check on opcCircularBufPtr — potential crash.

handleOPCData is registered as a callback and can be invoked asynchronously. If data arrives before opcCircularBufPtr is fully initialized (or after it's been deleted during re-initialization), dereferencing it will crash. Based on learnings, UDP/TCP packet processing must validate state before accessing buffers.

Proposed fix
 void handleOPCData(void* arg, AsyncClient* client, void *data, size_t len) {
+  if (opcCircularBufPtr == nullptr) return;
   if (len > opcCircularBufPtr->room()) return;
   for (unsigned int i = 0; i < len; i++) { 
     opcCircularBufPtr->write(*((uint8_t*)data + i));
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 33 - 38, handleOPCData currently dereferences
opcCircularBufPtr without verifying it is valid; add a null check at the top of
handleOPCData to return immediately if opcCircularBufPtr is nullptr (or
otherwise not initialized) before calling opcCircularBufPtr->room() or
->write(), so the callback cannot crash if invoked before initialization or
after teardown; ensure the early return covers both the room() size check and
the write loop to avoid any race where opcCircularBufPtr becomes null between
checks.

Comment thread wled00/opc.cpp
Comment on lines +40 to +55
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);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical bug: frees wrong client when rejecting new connection.

Line 42 calls freeOPCClient(client) when clientServed != nullptr, intending to reject the new client. However, combined with the bug in freeOPCClient, this closes the new client but deletes the old clientServed pointer — leaving memory corruption and the new (closed) client assigned to clientServed on line 43.

Additionally, line 47 calls opcCircularBufPtr->flush() without a null check, which can crash if opcCircularBufPtr hasn't been initialized yet.

Proposed fix for client handling
 void handleOPCClient(void* arg, AsyncClient* client) {
-  //add client
-  if (clientServed != nullptr) freeOPCClient(client);
-	clientServed = client;
+  // Reject new client if one is already being served
+  if (clientServed != nullptr) {
+    client->close(true);
+    delete client;
+    return;
+  }
+  clientServed = client;
 
-  //clean and initialize buffer
+  // Clean and initialize buffer
   opcDataLen = 0;
-  opcCircularBufPtr->flush();
+  if (opcCircularBufPtr != nullptr) opcCircularBufPtr->flush();
   opcParserWaitingForData = false;
-	
-	// register client events
-	client->onData(&handleOPCData, NULL);
-	client->onError(&handleOPCError, NULL);
-	client->onDisconnect(&handleOPCDisconnect, NULL);
-	client->onTimeout(&handleOPCTimeOut, NULL);
+
+  // Register client events
+  client->onData(&handleOPCData, NULL);
+  client->onError(&handleOPCError, NULL);
+  client->onDisconnect(&handleOPCDisconnect, NULL);
+  client->onTimeout(&handleOPCTimeOut, NULL);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 40 - 55, In handleOPCClient: do not call
freeOPCClient(client) when clientServed != nullptr — that closes the new client
and corrupts the served pointer; instead immediately reject the incoming
connection by closing/stopping the incoming AsyncClient (e.g.,
client->close(true) or client->stop()) and return without changing clientServed;
only assign clientServed = client and initialize state (opcDataLen,
opcParserWaitingForData) and register callbacks when accepting a client; also
guard the buffer flush call with a null check (if (opcCircularBufPtr)
opcCircularBufPtr->flush()) so opcCircularBufPtr->flush() cannot crash when
uninitialized.

Comment thread wled00/opc.cpp
Comment on lines +65 to +83
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);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ledCount is defined as a global/accessible variable
rg -n '\bledCount\b' --type=cpp --type-add 'cpp:*.h' -g '!opc.cpp' | head -30

Repository: wled/WLED

Length of output: 595


🏁 Script executed:

#!/bin/bash
# Search for ledCount declaration (extern, variable definition)
rg -n 'extern.*ledCount|^\s*uint.*ledCount|^uint.*ledCount' --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Check includes in opc.cpp
head -30 wled00/opc.cpp | grep -E '^\s*#include'

Repository: wled/WLED

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for ledCount in FX.h
rg -n 'ledCount' wled00/FX.h | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search for ledCount in wled.h
rg -n 'ledCount' wled00/wled.h

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search for all ledCount declarations/definitions (excluding comments)
rg -n '^\s*(extern\s+)?(uint\d+_t|uint|int)\s+ledCount' --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search for ledCount usage pattern - what does the strip object have?
rg -n 'strip\.getLedCount|ledCount\s*=' wled00/ --type=cpp --type-add 'cpp:*.h' | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search for how LED count is accessed in the codebase
rg -n 'strip\.(ledCount|getLedCount|length|maxLeds)' wled00/ --type=cpp -A 1 | head -30

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check for any variable that might store LED count
rg -n '\blenstrip\b|\bstrip_len\b|\blastLed\b' wled00/ --type=cpp | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Look at actual opc.cpp full context around line 65-83
sed -n '60,90p' wled00/opc.cpp

Repository: wled/WLED

Length of output: 1058


🏁 Script executed:

#!/bin/bash
# Search for ledCount as macro or in any context
rg -n 'ledCount' wled00/ --type=cpp --type-add 'cpp:*.h' -i

Repository: wled/WLED

Length of output: 885


🏁 Script executed:

#!/bin/bash
# Check how other protocol handlers (like e131, mqtt, etc.) access LED count
rg -n 'for.*<.*ledCount|for.*<.*strip\.|setRealtimePixel' wled00/e131.cpp wled00/mqtt.cpp 2>/dev/null | head -20

Repository: wled/WLED

Length of output: 708


🏁 Script executed:

#!/bin/bash
# Search for LED count access in FX_fcn.cpp (where setRealtimePixel is likely defined)
rg -n 'setRealtimePixel|ledCount|SEGLEN' wled00/FX_fcn.cpp | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search the entire opc.cpp for ledCount declaration
cat wled00/opc.cpp | head -70 | tail -40

Repository: wled/WLED

Length of output: 1202


🏁 Script executed:

#!/bin/bash
# Look for what strip object provides for LED access
rg -n 'strip\.' wled00/opc.cpp -o | sort -u

Repository: wled/WLED

Length of output: 72


🏁 Script executed:

#!/bin/bash
# Check what type of LED count the strip object has
rg -n 'class.*WS2812FX|uint.*_ledcount|_leds' wled00/FX.h | head -10

Repository: wled/WLED

Length of output: 86


🏁 Script executed:

#!/bin/bash
# Get the full beginning of opc.cpp to see all globals
head -150 wled00/opc.cpp

Repository: wled/WLED

Length of output: 4018


🏁 Script executed:

#!/bin/bash
# Check WS2812FX class members around ledcount
rg -n '_leds|_ledcount|ledcount|getLedCount|length' wled00/FX.h -A 1 -B 1 | head -40

Repository: wled/WLED

Length of output: 1666


🏁 Script executed:

#!/bin/bash
# Look for how many LEDs are stored - check strip initialization
rg -n 'WS2812FX.*strip|new WS2812FX' wled00/ --type=cpp -A 2 | head -20

Repository: wled/WLED

Length of output: 331


🏁 Script executed:

#!/bin/bash
# Search for ledCount extern or global declaration in header files
rg -n 'extern.*ledCount|WLED_GLOBAL.*ledCount' wled00/ --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check if ledCount might be in a previous version or another branch
# Let's look for what globals are defined in wled.h that relate to LED count
rg -n 'WLED_GLOBAL.*uint.*LED|WLED_GLOBAL.*ledCount' wled00/wled.h

Repository: wled/WLED

Length of output: 308


🏁 Script executed:

#!/bin/bash
# Verify getLengthTotal() works and is how other code accesses LED count
rg -n 'getLengthTotal\|_length' wled00/ --type=cpp | grep -v 'MIN_SHOW_DELAY\|groupLength' | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Search for any declaration of ledCount as a variable (not in comments)
rg -n '(uint\d+_t|uint|int)\s+ledCount\s*[;=]' wled00/ --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check if ledCount might be a macro
rg -n '#define ledCount' wled00/ --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Look at the entire wled.h for global declarations
grep -n 'ledCount\|_length' wled00/wled.h | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check if ledCount is perhaps defined conditionally or in another included file
rg -n 'ledCount' wled00/ --type=cpp -B 2 -A 2 | grep -E '^\s*(extern|static|const|uint)' | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Do a comprehensive search - ledCount must be used somewhere or code won't compile
# Let's check if it's perhaps a very recent addition or if code is broken
git log --oneline -20 wled00/opc.cpp 2>/dev/null || echo "Git not available"

Repository: wled/WLED

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Check what the correct method should be - strip has getLengthTotal()
# Let's see if other realtime handlers use it
rg -n 'realtimeTimeoutMs|setRealtimePixel|getLengthTotal' wled00/mqtt.cpp wled00/ws.cpp 2>/dev/null | head -30

Repository: wled/WLED

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Final check: look at compilation context - search for any conditional defines
# that might create ledCount
rg -n 'ledCount' wled00/ -B 5 -A 1 | head -50

Repository: wled/WLED

Length of output: 2957


🏁 Script executed:

#!/bin/bash
# Check if there's a forward declaration or extern in any header included by opc.cpp
rg -n 'extern.*ledCount' wled00/fcn_declare.h wled00/wled.h wled00/FX.h 2>/dev/null || echo "No extern ledCount found"

Repository: wled/WLED

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Final verification: check the actual current state of the OPC branch
# Look for any variable or macro that might shadow ledCount
rg -n '\bledCount\b' wled00/opc.cpp -B 10 | head -40

Repository: wled/WLED

Length of output: 1130


🏁 Script executed:

#!/bin/bash
# Verify ws.cpp approach works - confirm getLengthTotal() is the right method
rg -n 'class WS2812FX' wled00/FX.h -A 150 | grep -E 'getLengthTotal|getLength' | head -5

Repository: wled/WLED

Length of output: 87


🏁 Script executed:

#!/bin/bash
# One final check - search entire repository for any ledCount declaration or definition
find wled00 -name "*.h" -o -name "*.cpp" | xargs grep -l 'ledCount' | xargs grep -n '^[^/]*\(extern\|static\|uint\|int\).*ledCount'

Repository: wled/WLED

Length of output: 253


🏁 Script executed:

#!/bin/bash
# Verify the exact error - see if ledCount could possibly be defined anywhere
rg -n 'ledCount\s*=' wled00/ --type=cpp --type-add 'cpp:*.h' -B 2

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

#!/bin/bash
# Check if there's a using statement or alias
rg -n 'using.*ledCount|#define.*ledCount' wled00/ --type=cpp --type-add 'cpp:*.h'

Repository: wled/WLED

Length of output: 479


Replace undeclared ledCount with strip.getLengthTotal().

ledCount is not declared anywhere in the codebase and will cause a compilation error. The WLED codebase uses strip.getLengthTotal() to access LED count (as seen in ws.cpp). Replace all three occurrences (lines 71, 79, and 125) with strip.getLengthTotal().

Additionally, verify that discardInvalidData() handles the case where opcDataLen - ledCount * 3 might exceed available buffer bytes—the function already includes this check, but ensure it's correct when opcDataLen is corrupted or malformed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/opc.cpp` around lines 65 - 83, In parseOPCLedValues, replace the
undeclared variable ledCount with strip.getLengthTotal() in all uses (the loop
bound, the later comparison for discardInvalidData, and any other occurrence) so
the code compiles; keep the logic using opcDataLen/opcDataLenDiv,
opcCircularBufPtr->read(), setRealtimePixel(...), realtimeLock(...,
REALTIME_MODE_OPC) and strip.show() unchanged. After the replacement, ensure the
call to discardInvalidData(opcDataLen - strip.getLengthTotal() * 3) is safe by
confirming discardInvalidData correctly bounds/checks large or negative lengths
when opcDataLen is corrupted or malformed.

@softhack007 softhack007 added connectivity Issue regarding protocols, WiFi connection or availability of interfaces needs_rework PR needs improvements before merging (RED FLAG) labels Apr 13, 2026
@netmindz
Copy link
Copy Markdown
Member

It seems a bit of a dead protocol that didn't really take off, folk can just use ArtNet, sACN or DDP

@netmindz netmindz closed this Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connectivity Issue regarding protocols, WiFi connection or availability of interfaces keep This issue will never become stale/closed automatically needs_rework PR needs improvements before merging (RED FLAG)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants