Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/develop/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ Before merging:
For the most urgent items (blockers or high-risk changes), include a ready-to-paste
prompt that a Claude Code agent can execute immediately before merge.

2. **End-user docs prompt** — a ready-to-paste prompt for a Claude Code agent to update
2. **End-user docs prompt** — a ready-to-paste prompt for AI agents to update
`/docs`. Rules: only describe usage implications (what changed for the user);
no internals, no code, no architecture; check existing pages before adding —
update in place rather than duplicating; keep additions compact and user-friendly.

3. **Developer docs prompt** — a ready-to-paste prompt for a Claude Code agent to update
3. **Developer docs prompt** — a ready-to-paste prompt for AI agents to update
`/docs/develop`. Rules: target contributors, not end users; be concise — if the
detail is already in the code or commit messages, do not repeat it; focus on
decisions, patterns, and guidance that are not obvious from reading the source.
Expand Down
21 changes: 21 additions & 0 deletions docs/develop/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,24 @@ if (_connecting) {
#### 30-second timeout

If the IDF event loop drops both `STA_CONNECTED` and `STA_DISCONNECTED` (rare but observed on congested stacks), `_connecting` would stay `true` permanently and block all future reconnections. The 30-second timeout in the guard above is the escape hatch: after 30 s with no event, `_connecting` is reset with a `LOGW` and the next `manageSTA()` call retries normally.

## ModuleDevices — UDP Port Segregation

**Problem:** WLED devices listen on port 65506 for metadata/discovery. When MoonLight sent control commands (brightness, palette, preset) to the same port, WLED devices received and misinterpreted them, causing interference.

**Solution:** Port segregation via two separate UDP sockets in `ModuleDevices` ([`src/MoonBase/Modules/ModuleDevices.h`](https://github.com/MoonModules/MoonLight/blob/main/src/MoonBase/Modules/ModuleDevices.h)):

| Port | Socket | Direction | Purpose | WLED visibility |
|------|--------|-----------|---------|-----------------|
| **65506** | `deviceUDP` | Bidirectional | Device discovery & metadata broadcasts; shared with WLED | ✅ Sees WLED packets, WLED sees device info |
| **65507** | `deviceControlUDP` | Outbound unicast | MoonLight-only control commands (from UI → device) | ❌ WLED cannot see |

**Implementation:**

- `loop10s()` initializes both sockets; control socket binding failures are logged.
- `onUpdate()` sends targeted device control changes via `deviceControlUDP.beginPacket(..., 65507)` instead of `deviceUDP`.
- `receiveUDP()` polls both sockets via a shared `processUDPMessage()` helper:
- **Port 65506 (discovery):** Use existing group/broadcast logic; also process device info from WLED.
- **Port 65507 (control):** Enforce `isControlCommand + hostname match` — only apply if message is addressed to this device.

**Trade-off:** Adds a second socket (small memory cost, ~300 bytes stack per packet). Benefit: Clean isolation; no cross-vendor interference; WLED remains compatible.
33 changes: 33 additions & 0 deletions docs/develop/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,39 @@ A node implements the following (overloaded) functions:

Node construction is routed through `LayerManager`: `ModuleEffects::addNode()` calls `layerMgr.getSelectedLayer()` and `layerP.ensureLayer(index)` to target the currently active layer — never hard-code `layers[0]` in a new module. Modules that host layers must also override `onNodeRemoved()` and `onBeforeStateLoad()` to delegate to `layerMgr.onNodeRemoved()` and `layerMgr.prepareForPresetLoad()` respectively; `ModuleEffects` is the reference implementation for both.

## Shared Audio Data

Audio effects access microphone or network audio data via shared global fields. Both **FastLED Audio** (`D_FastLEDAudio.h`) and **WLED Audio** (`D_WLEDAudio.h`) drivers populate the same fields — effects should not depend on which driver is active.

### Audio fields and normalization

| Field | Type | Range | Source | Notes |
|-------|------|-------|--------|-------|
| `bands[16]` | `uint8_t[]` | 0–255 | FastLED FFT or WLED FFT_Magnitude | 16-band graphic EQ; 0–3 (bass) to 12–15 (treble) |
| `volume` | `float` | 0–255 | FastLED sampleAvg or WLED volumeSmth | Smoothed volume; direct use in effects |
| `volumeRaw` | `uint8_t` | 0–255 | FastLED sampleRaw or WLED volumeRaw | Unsmoothed sample; for peak detection |
| `majorPeak` | `float` | ~64–8183 Hz | FastLED getEqDominantFreqHz() or WLED FFT_MajorPeak | Dominant frequency; compare to bass/treble thresholds |
| `magnitude` | `float` | 0–unbounded | FastLED or WLED FFT_Magnitude | Peak frequency strength; **no guaranteed upper bound** |

### Using magnitude safely

`magnitude` has no clamped upper bound from FastLED's FFT. Effects should clamp or normalize before using it as an intensity scalar or bar height:

```cpp
// Bad: magnitude can exceed 255 and overflow
uint8_t barHeight = magnitude;

// Good: clamp to 0–255 range
uint8_t barHeight = (magnitude > 255) ? 255 : (uint8_t)magnitude;

// Or: normalize to 0.0–1.0 before scaling
float normalized = magnitude / 256.0f; // arbitrary normalisation
```

### Field synchronization

Both drivers call the same `updateSharedAudioData()` function from their `loop()`. If neither driver is active, all fields remain at their previous value (do not auto-reset). Effects depending on audio should check driver presence via the Drivers module state before applying audio logic.

## Drivers

### Initless drivers
Expand Down
1 change: 1 addition & 0 deletions docs/moonbase/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Part 1:
* If one of the controls is changed for a device in the devices overview, it sends a message to that device updating to update its controls
* Every module receives these messages and updates them in the devices overview

**Network isolation:** MoonLight uses separate UDP ports for device discovery (65506) and control (65507). This keeps MoonLight control commands isolated from WLED devices on the same network, preventing interference while still allowing device discovery.

Part 2:

Expand Down
4 changes: 4 additions & 0 deletions docs/moonlight/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Custom layouts can also be created as **Live Scripts** — `.sc` files with an `
| IR Driver | <img width="100" src="../../media/moonlight/drivers/IRDriver.jpeg"/> | <img width="100" src="../../media/moonlight/drivers/irdrivercontrols.png"/> | Receive IR commands and [Lights Control](lightscontrol.md) |
| IMU Driver | <img width="100" src="../../media/moonlight/drivers/MPU-6050.jpg"/> | <img width="100" src="../../media/moonlight/drivers/IMUDriverControls.png"/> | Receive inertial data from an IMU / I2C peripheral, see [IO](../moonbase/inputoutput.md#i2c-peripherals)<br>Used in [particles effect](effects.md#moonlight-effects) |

### Audio Driver Data

Both **FastLED Audio** and **WLED Audio** drivers provide audio data to effects. In addition to volume and frequency bands (FFT), effects can now access **magnitude** — the strength of the dominant frequency peak. This enables more sophisticated audio-reactive visualizations. See [Effects](effects.md) for details on using audio data in effects.

### Light Preset

* **Max Power**: moved to [IO Module](../moonbase/inputoutput.md) board presets.
Expand Down
Loading
Loading