Skip to content

Conversation

@spectrenoir06
Copy link
Contributor

@spectrenoir06 spectrenoir06 commented Dec 6, 2025

I'm building a custom remote and I want to control WLED with it.
The WIZMote packet works great, but it isn’t easily customizable for my needs.

This PR adds support for sending JSON API commands over ESP-NOW.
ESP-NOW limits packets to 255 bytes, but this is more than enough for remote commands.

For security, JSON parsing only happens when the sender (the remote) is present in the trusted device list, and only when the received payload starts with {

Summary by CodeRabbit

  • New Features
    • ESP-NOW wireless protocol now supports receiving JSON-formatted data packets, allowing your devices to exchange structured state information and configuration updates in a standardized format. This enhancement enables improved remote device management capabilities, more flexible control options, and better synchronization across your entire network of connected devices.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 6, 2025

Walkthrough

The ESP-NOW receive callback in wled00/udp.cpp is enhanced to handle JSON data. When incoming ESP-NOW packets begin with '{', the code deserializes the data as JSON and applies it via the existing state deserialization mechanism, then returns early to skip further processing.

Changes

Cohort / File(s) Change Summary
ESP-NOW JSON handler
wled00/udp.cpp
Added conditional logic to ESP-NOW receive callback to detect and process JSON-formatted data. Acquires JSON buffer lock, deserializes incoming data as JSON document, applies deserialized state, releases lock, and returns early if successful.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Thread safety of JSON buffer lock acquisition and release pattern in the callback context
  • JSON deserialization error handling and robustness when receiving malformed data
  • Interaction between deserializeState and the existing ESP-NOW packet handling flow
  • Potential edge cases where packets starting with '{' but not valid JSON could cause issues

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the main change: adding JSON data handling to the ESP-NOW receive callback function.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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: 0

🧹 Nitpick comments (2)
wled00/udp.cpp (2)

956-956: Add defensive length check before accessing data[0].

While the callback is unlikely to be invoked with len == 0, add an explicit check for defensive programming consistency with similar checks elsewhere in this file (e.g., lines 502, 605).

Apply this diff:

   // handle JSON data over ESP-NOW
-  if (data[0] == '{') {
+  if (len > 0 && data[0] == '{') {
     if (requestJSONBufferLock(18)) {

955-966: Consider adding debug output for JSON handling.

For consistency with the WiZ Mote handler (line 928 area) and to aid troubleshooting, consider adding debug output when JSON is processed.

Example addition:

  // handle JSON data over ESP-NOW
  if (len > 0 && data[0] == '{') {
+   DEBUG_PRINTLN(F("ESP-NOW: Processing JSON"));
    if (requestJSONBufferLock(18)) {
      DeserializationError error = deserializeJson(*pDoc, data, len);
      JsonObject root = pDoc->as<JsonObject>();
      if (!error && !root.isNull()) {
        deserializeState(root);
+     } else {
+       DEBUG_PRINTLN(F("ESP-NOW: JSON parse failed"));
      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc5b504 and f2797c0.

📒 Files selected for processing (1)
  • wled00/udp.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.cpp

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use 2-space indentation for C++ source files (.cpp)

Files:

  • wled00/udp.cpp
🧠 Learnings (3)
📓 Common learnings
Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.
Learnt from: ksedgwic
Repo: wled/WLED PR: 4883
File: usermods/usermod_v2_skystrip/rest_json_client.h:6-14
Timestamp: 2025-08-29T00:26:15.808Z
Learning: WLED uses a vendored ArduinoJson library (version 6) located at "src/dependencies/json/ArduinoJson-v6.h" which is included through wled.h. Usermods should not directly include ArduinoJson headers but instead rely on wled.h for ArduinoJson symbols. The standard pattern is to include wled.h and use JsonObject, JsonArray, DynamicJsonDocument, etc. without additional includes.
Learnt from: ksedgwic
Repo: wled/WLED PR: 4883
File: usermods/usermod_v2_skystrip/rest_json_client.h:6-14
Timestamp: 2025-08-29T00:26:15.808Z
Learning: In WLED projects, ArduinoJson.h is not directly included via #include <ArduinoJson.h> - the ArduinoJson symbols are made available through the WLED build system and wled.h transitive includes, so explicitly adding #include <ArduinoJson.h> is not necessary and may not work.
📚 Learning: 2025-10-10T18:34:06.550Z
Learnt from: DedeHai
Repo: wled/WLED PR: 4997
File: wled00/e131.cpp:33-44
Timestamp: 2025-10-10T18:34:06.550Z
Learning: In WLED's DDP packet handling (ws.cpp and e131.cpp), only prevent out-of-bounds memory access rather than enforcing DDP spec compliance. Don't check the 1440-byte spec limit—accept out-of-spec packets assuming correct encoding. The bounds check `maxDataIndex = c + numLeds * ddpChannelsPerLed; if (maxDataIndex > dataLen) reject` is sufficient and already covers the timecode flag case (when c=4) without needing separate validation.

Applied to files:

  • wled00/udp.cpp
📚 Learning: 2025-08-21T00:00:17.196Z
Learnt from: willmmiles
Repo: wled/WLED PR: 4859
File: usermods/udp_name_sync/udp_name_sync.cpp:62-70
Timestamp: 2025-08-21T00:00:17.196Z
Learning: In WLED codebase, UDP packet processing code must never rely on null termination being present on the wire and must always validate packet size first. The codebase already has some vulnerabilities where malformed data can crash the software, so avoiding introduction of new ones is a priority.

Applied to files:

  • wled00/udp.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp32c3dev_qio)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp32S3_wroom2)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp32dev_debug)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
🔇 Additional comments (2)
wled00/udp.cpp (2)

955-967: JSON over ESP-NOW implementation follows secure pattern.

The implementation correctly:

  • Relies on the trusted sender check (lines 936-947) for security
  • Passes len to deserializeJson, avoiding null termination issues per learnings
  • Acquires the JSON buffer lock before parsing
  • Returns early to prevent double-processing

This is actually more secure than the UDP JSON API (line 654), which lacks authentication.

However, see separate comments for minor defensive improvements and design verification.


955-966: Verify that JSON packets intentionally bypass sync mode requirements.

The early return at line 965 means JSON packets bypass the checks at line 969 (broadcast flag, 'W' magic byte, useESPNowSync setting, WiFi connection state). This differs from WLED sync packets but matches the WiZ Mote pattern (lines 950-953).

Given the PR objectives (custom remote, similar to WiZMote), this is likely intentional, but please confirm this design choice is correct.

@netmindz netmindz added enhancement connectivity Issue regarding protocols, WiFi connection or availability of interfaces labels Dec 7, 2025
@DedeHai
Copy link
Collaborator

DedeHai commented Dec 21, 2025

Sounds like a good addition. What I am not too sure about is to use "{" as an indicator and the lack of suporting longer JSONs. For a simle single command there is already the way to go through a remote.json file which does exactly what you want to achieve, just not in a dynamic way.

@spectrenoir06
Copy link
Contributor Author

remote.json is to remaps a wizmote button to a commande ( on the wled side ) right ?
I like the idea to have different remote who do different thing.
image

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 22, 2025

remote.json is to remaps a wizmote button to a commande ( on the wled side ) right ?

correct, you can have up to 256 button functions to map to any JSON command.
See https://github.com/DedeHai/WLED-ESPNow-Remote for examples

@spectrenoir06
Copy link
Contributor Author

I already looked at your repo and really like it.
My remote is using the esp32-c6 ( because i want to play with zigbee / thread ), and is using 2 AAA battery ( with a power latch to have good battery )
for now both work pretty great the wizmote ( who is comment in my code ) + json
image

image

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 23, 2025

the advantage of using remote.json is you can change remote functionality without changing the firmware on the remote. Disadvantage is that you need that file on each WLED device.

@blazoncek would such an implementation clash with any of your ESPNow work? What are your thoughts on this in general?

@blazoncek
Copy link
Contributor

For purposes like this (home made ESP-NOW remote) you have usermod callback which can be used for handling custom payloads. Write a usermod and it can be included.

If you have found readily available (generic?) remote that is sending JSON payloads (and preferably WLED's JSON API) then I would request to implement split packets as well. Otherwise this is enhancement just for (one) specific use.

Be mindful that 99% of WLED users are not electronics tinkerers and they will prefer to buy off the shelf remote (like Wizmote) which don't benefit from this enhancement.

However this is just my view on the topic.

@DedeHai
Copy link
Collaborator

DedeHai commented Dec 23, 2025

I do see some use-cases like writing custom sync sequence for art installations, though that could also be done using the remote.json. I am not opposed to adding this if its done properly. Its not much code for a feature some may find useful.

@blazoncek
Copy link
Contributor

am not opposed to adding this if its done properly

I would require split packet processing as ~250 bytes is really very little for JSON.

not much code for a feature some may find useful

I do agree.

@spectrenoir06
Copy link
Contributor Author

spectrenoir06 commented Dec 24, 2025

I'm not fan of the way UsermodManager::onEspNowMessage is done right now, because he don't check if the remote is in the whitelist.

One of the problem with the Wizmote, to send 1 pqt it send a espnow pqt to every channels.
Because the esp only listen on the same channel as the one use for the wifi.

I think be able to send a "small" pqt is important so a remote can do the same. ( spam all channel )

But yes I should also think about a way send the "cmd" in a multiple pqt ( probally only on one channel ), but both should be possible

@blazoncek
Copy link
Contributor

he don't check if the remote is in the whitelist.

You can do that in usermod.

One of the problem with the Wizmote

That is not a problem. That's a benefit.

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 enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants