Skip to content
Draft
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

Date-based versions use `YYYYMMDD`.

## 20260603

### Added

- Added a GLEDOPTO GL-RC-001WL ESP-NOW remote example using Berry
`espnow.rx(filter, callback)`.
- Added a single-ID mapping helper for `toggl`, `brigh`, and `tempe`
EventStates.
- Added a direct/indirect mapping helper that preserves the previous
ESPNOWRADIO remote behavior while keeping normal Controller-to-Controller
ESP-NOW enabled.
- Added copyable TNGL usage snippets for both variants.

### Impact

- Technicians can connect the GLEDOPTO remote to Spectoda EventStates without
disabling normal ESP-NOW communication between controllers.
- The public example now documents the native `size`/`magic` raw ESP-NOW filter
and the optional MAC lock for one physical remote.

## 20260527

### Added
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ developer can see why the pieces are connected that way.
- `controller-toggle-button-hold-dim-dali/` - a single digital toggle button
pattern that ramps DALI brightness while pressed or latched on, stops on
release, and reverses dimming direction on the next press.
- `gledopto-gl-rc-001wl-espnow-remote/` - Berry `espnow.rx` examples that map
the GLEDOPTO GL-RC-001WL remote to either one Spectoda ID or direct/indirect
light IDs.

## Example Rules

Expand Down
121 changes: 121 additions & 0 deletions gledopto-gl-rc-001wl-espnow-remote/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# GLEDOPTO GL-RC-001WL ESP-NOW Remote

This example maps the GLEDOPTO GL-RC-001WL / WLED-style ESP-NOW remote to
Spectoda EventStates through the native Berry `espnow.rx(filter, callback)`
API.

It replaces the older `ESPNOWRADIO` pattern for installations where normal
Controller-to-Controller ESP-NOW communication must stay enabled.

## Files

- `gledopto-single-id.be` - maps the remote to one Spectoda ID.
- `gledopto-direct-indirect.be` - maps the remote to two IDs, typically direct
and indirect light.
- `usage-single-id.tngl` - copyable TNGL usage for one ID.
- `usage-direct-indirect.tngl` - copyable TNGL usage for direct/indirect IDs.

## Firmware Assumptions

Use firmware `0.12.11` or newer with the native raw ESP-NOW Berry API:

```berry
espnow.rx(filter, callback)
espnow.tx(mac, data)
```

Do not disable the normal ESP-NOW connector. `espnow.enable` is enabled by the
controller runtime by default; this example does not require adding an
`espnow.enable` flag to controller config.

The example uses a native RX filter:

```berry
{
"size": 13,
"magic": [129, 145]
}
```

For this remote:

- `13` is the observed payload size.
- `129` is `0x81`, one observed program prefix.
- `145` is `0x91`, the other observed program prefix.

Add `"mac": "AA:BB:CC:DD:EE:FF"` when the project should accept only one
physical remote. The MAC address in the snippets is synthetic; replace it with
your remote MAC or set `"macFilter": false` while testing.

## Button Mapping

Both variants deduplicate packets by the remote sequence in payload bytes
`1..4`, because one physical button press is repeated across Wi-Fi channels.

Common buttons:

- ON (`1`) sets `toggl` to `100%`.
- OFF (`2`) sets `toggl` to `0%`.
- Brightness + (`9`) increases `brigh`.
- Brightness - (`8`) decreases `brigh`.
- Preset 3 (`18`) makes `tempe` warmer.
- Preset 4 (`19`) makes `tempe` colder.

Single ID behavior:

- Night (`3`) turns the ID on and sets `tempe` warm.
- Preset 1 (`16`) toggles the ID.
- Preset 2 (`17`) also toggles the ID.

Direct/indirect behavior:

- Night (`3`) turns direct off, indirect on, and sets both `tempe` values warm.
- Preset 1 (`16`) toggles indirect.
- Preset 2 (`17`) toggles direct.

## Config Map

Single ID:

```berry
GledoptoRemoteOne({
"id": ID1,
"mac": "AA:BB:CC:DD:EE:FF",
"macFilter": true,
"brigh": 50,
"brighStep": 10,
"tempeStep": 10,
"debug": false
})
```

Direct/indirect:

```berry
GledoptoRemoteDirectIndirect({
"direct": ID1,
"indirect": ID2,
"mac": "AA:BB:CC:DD:EE:FF",
"macFilter": true,
"brigh": 50,
"brighStep": 10,
"tempeStep": 10,
"debug": false
})
```

`brigh` is the real Spectoda brightness label. The shortened spelling is
intentional and should stay consistent with EventState labels.

## Copy Pattern

1. Copy the matching `.be` helper into a Berry block or controller script.
2. Copy one of the `usage-*.tngl` snippets into the controller where the remote
should be received.
3. Replace `AA:BB:CC:DD:EE:FF` with the remote MAC, or temporarily set
`"macFilter": false` during bring-up.
4. Press the remote buttons and watch `toggl`, `brigh`, and `tempe` EventStates
for the selected ID or IDs.

Set `"debug": true` only during smoke testing. The remote sends several raw
packets for one button press, so serial output can get noisy quickly.
180 changes: 180 additions & 0 deletions gledopto-gl-rc-001wl-espnow-remote/gledopto-direct-indirect.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import espnow

# GLEDOPTO GL-RC-001WL -> direct + indirect Spectoda IDs.
# Short locals and numeric literals keep Berry bytecode/RAM small. Comments
# act as the readable symbol table and are stripped by Studio preprocessing.
def GledoptoRemoteDirectIndirect(S)
if S == nil
S = {}
end

# Public config: direct/indirect are the two Spectoda IDs controlled by
# the remote. mac locks the script to one physical remote when macFilter is
# true. debug prints accepted packets and decoded buttons.
var d = S.find("direct", 1)
var i = S.find("indirect", 2)
var m = S.find("mac", "")
var mf = S.find("macFilter", m != "")
var dbg = S.find("debug", false)

if mf && m == ""
mf = false
end

# td/bd/cd expand to toggleDirect/brighDirect/tempeDirect.
# "brigh" is the real Spectoda brightness label, not a typo.
var td = EVS("toggl", d)
var bd = EVS("brigh", d)
var cd = EVS("tempe", d)

# ti/bi/ci expand to toggleIndirect/brighIndirect/tempeIndirect.
var ti = EVS("toggl", i)
var bi = EVS("brigh", i)
var ci = EVS("tempe", i)

# bs/ts are brighStep/tempeStep. od/oi cache direct/indirect ON states.
# b caches shared brigh. t/u cache direct/indirect tempe. ls deduplicates
# repeated remote packets from one physical button press.
var bs = S.find("brighStep", 10)
var ts = S.find("tempeStep", 10)
var od = 100
var oi = 100
var b = S.find("brigh", 50)
var t = 0
var u = 0
var ls = -1

# z is clamp(v, lo, hi).
def z(v, lo, hi)
if v < lo
return lo
elif v > hi
return hi
end
return v
end

# rx args: a = sender MAC, x = payload bytes, r = RSSI, ch = Wi-Fi channel.
# The native ESP-NOW filter below handles MAC when mf is true; avoid a
# second Berry string compare so lowercase config MACs still work.
def rx(a, x, r, ch)
# 13 = GLEDOPTO payload size.
if x.size() != 13
return
end

# Byte 0 is program/magic. 129/145 are 0x81/0x91.
var p = x.get(0)
if p != 129 && p != 145
return
end

# Bytes 1..4 are the sequence used to ignore duplicate channel sends.
var s = x.get(1, 4)
if s == ls
return
end
ls = s

# Byte 6 is the button code.
var k = x.get(6)

if dbg
print("GLEDOPTO di", a, "btn", k, "rssi", r, "ch", ch, x.tohex())
end

# 30 below is PERCENTAGE value type.
if k == 1
# 1 = ON: direct + indirect ON.
od = 100
oi = 100
td.set(100, 30)
ti.set(100, 30)

elif k == 2
# 2 = OFF: direct + indirect OFF.
od = 0
oi = 0
td.set(0, 30)
ti.set(0, 30)

elif k == 9
# 9 = brightness plus for both zones, then both zones ON.
b = z(b + bs, 0, 100)
bd.set(b, 30)
bi.set(b, 30)
od = 100
oi = 100
td.set(100, 30)
ti.set(100, 30)

elif k == 8
# 8 = brightness minus for both zones, clamped to 0..100%.
b = z(b - bs, 0, 100)
bd.set(b, 30)
bi.set(b, 30)
od = 100
oi = 100
td.set(100, 30)
ti.set(100, 30)

elif k == 3
# 3 = night: direct OFF, indirect ON, both tempe warm.
od = 0
oi = 100
td.set(0, 30)
ti.set(100, 30)
t = 100
u = 100
cd.set(100, 30)
ci.set(100, 30)

elif k == 16
# 16 = preset 1: toggle indirect.
oi = oi > 0 ? 0 : 100
ti.set(oi, 30)

elif k == 17
# 17 = preset 2: toggle direct.
od = od > 0 ? 0 : 100
td.set(od, 30)

elif k == 18
# 18 = preset 3: warmer, tempe range -100..100.
t = z(t + ts, -100, 100)
u = z(u + ts, -100, 100)
cd.set(t, 30)
ci.set(u, 30)

elif k == 19
# 19 = preset 4: colder.
t = z(t - ts, -100, 100)
u = z(u - ts, -100, 100)
cd.set(t, 30)
ci.set(u, 30)

elif dbg
print("GLEDOPTO di unknown", k, x.tohex())
end
end

# C++ filter: only 13-byte packets whose first byte is 0x81 or 0x91.
var f = {
"size": 13,
"magic": [129, 145]
}
if mf
f = {
"mac": m,
"size": 13,
"magic": [129, 145]
}
end

# espnow.rx returns h(), an unsubscribe function for this one registration.
var h = espnow.rx(f, rx)

return Plugin(def()
# All work is done by rx().
end)
end
Loading