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: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ data/v2/examples/
├── controller-push-button-click-hold-dim-dali/ # momentary push button: short click toggles, hold dims brigh
├── controller-toggle-button-hold-dim-dali/ # single toggle button ramps DALI brightness, reverses on press
├── gledopto-gl-rc-001wl-espnow-remote/ # Berry espnow.rx examples for a GLEDOPTO GL-RC-001WL remote
└── network-nested-element-groups/ # App elementsJson + controlPages group → groups → devices pattern
├── network-nested-element-groups/ # App elementsJson + controlPages group → groups → devices pattern
├── seb-causal-scene-recall/ # recall a mixed-ID SEB scene at the causal event time
└── seb-segmented-id-track-player/ # play an ordered one-ID Track from bounded SEB segments
```

## Example Rules
Expand Down
56 changes: 56 additions & 0 deletions data/v2/examples/seb-causal-scene-recall/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Causal NetworkStorage Scene Recall with SEB

This firmware 0.12.11 example turns a normal LABEL Event value into a local
multi-event scene on every prepared Controller.

An authoritative source emits:

```berry
spectoda.emitEvent("scene", "sce01", ID255, 31)
```

`scene-recall.be` observes `$scene[ID255]` and calls the native NetworkStorage
source overload of `SEB.land` with the callback's exact `event_millis`.
All zero-offset scene records therefore receive the same causal Network clock
as the event that requested the scene.

This path is deliberately independent of timeline state. It does not call
`timeline.at()` and remains causal while the shared timeline is paused.

## Requirements

- Firmware must be exactly **0.12.11** with the final fixed-record SEB v1 API.
- Add `scene-recall.be` to the Project Berry loaded on every participating
Controller.
- Upload the exact bytes from the `hex` field in `sce01.seb.json` as a binary
NetworkStorage file named `sce01.seb` on those Controllers.
- The trigger Event is `$scene[ID255]` with a LABEL value.
- Do not include `$scene[ID255]` in the compiled scene, or recall would recurse.

The synthetic scene changes:

- `$brigh[ID255]` to `50%`
- `$color[ID1]` to `#112233` with priority `7`

SEB still uses EventStore as the only state authority. A Controller that misses
the simultaneous local landing may converge later through normal EventStore
synchronization.

## `SEB.land`, not `SEB.emit`

The same Project script runs on multiple Controllers, so it must use
`SEB.land`. Calling `SEB.emit` independently from all of them would multiply
network traffic and duplicate authoritative emissions. Reserve `SEB.emit` for
one deliberately selected source.

## Failure and retry behavior

The scene is validated and its complete deduplicated contribution is reserved
before EventStore changes. A validation, time-conversion or queue-capacity
failure applies nothing and returns a named error. The example logs that error;
a production Project can retry the same `(filename, at)` pair after the runtime
queue has drained.

The provisional variable-record blob from the earlier unreleased development
work is not accepted by this firmware version. Recompile every scene together
with the Project plugin before using 0.12.11.
33 changes: 33 additions & 0 deletions data/v2/examples/seb-causal-scene-recall/example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
schemaVersion: example.v2
slug: seb-causal-scene-recall
title: Causal NetworkStorage Scene Recall with SEB
category: controller
summary: >-
Expand a LABEL Event such as scene=sce01 into a mixed-ID, zero-duration
SEB scene stored in NetworkStorage. Every prepared Controller lands the scene
through EventStore at the exact local-millis token of the trigger event.
tags:
- berry-plugin
- eventstore
- networkstorage
- scene
- seb
- synchronized-events
hardware: >-
Requires exactly firmware 0.12.11 with the final SEB v1 component. Install
the same Project Berry script and sce01.seb bytes on every Controller that
should land the scene simultaneously. The example uses synthetic Event
values brigh[ID255] and color[ID1]. It contains no Network key, MAC address or
customer data.
files:
- path: README.md
role: readme
language: markdown
- path: scene-recall.be
role: copy-ready Project Berry plugin
language: berry
- path: sce01.seb.json
role: exact NetworkStorage artifact bytes and decoded reference
language: json
related:
- seb-segmented-id-track-player
24 changes: 24 additions & 0 deletions data/v2/examples/seb-causal-scene-recall/sce01.seb.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"schema": "spectoda.seb.artifact.v1",
"firmware": "0.12.11",
"filename": "sce01.seb",
"encoding": "hex",
"hex": "534542010c00020000000c000b08af2fc89148020000ff003f332211d2c33c0300000107",
"durationMs": 0,
"records": [
{
"value": { "type": "percentage", "value": 50 },
"identifier": "brigh",
"offsetMs": 0,
"id": 255,
"priority": 0
},
{
"value": { "type": "color", "value": "#112233" },
"identifier": "color",
"offsetMs": 0,
"id": 1,
"priority": 7
}
]
}
31 changes: 31 additions & 0 deletions data/v2/examples/seb-causal-scene-recall/scene-recall.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Project Berry for firmware 0.12.11.
#
# A broadcast $scene[ID255] LABEL event loads <label>.seb from NetworkStorage.
# The trigger itself must not be part of the compiled scene.
def SceneRecall(S)
var label = S.find("label", "scene")
var id = S.find("id", 255)
var extension = S.find("extension", ".seb")
var trigger = EVS(label, id)
var last_millis = nil

trigger.cb = def(value, event_id, event_millis)
if event_millis == nil || event_millis == last_millis || !value.is(31)
return
end

last_millis = event_millis
var name = value.get(31) + extension
var result = SEB.land(name, {
"source": "networkStorage",
"at": event_millis
})
if !result["ok"]
print("SceneRecall failed", name, result["error"])
end
end

return Plugin(nil)
end

SceneRecall({"label": "scene", "id": ID255})
70 changes: 70 additions & 0 deletions data/v2/examples/seb-segmented-id-track-player/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Timeline-Driven Segmented Single-ID SEB Track

This firmware 0.12.11 example keeps Player policy in Project Berry and uses the
native `SEB.land` component only as an atomic timed EventStore primitive.

The logical Track controls one state: `$brigh[ID1]`. It is represented by two
ordered 500 ms SEB segments:

| NetworkStorage file | Timeline start | Values |
|---|---:|---|
| `brigh-000.seb` | 0 ms | 0% at 0 ms, 75% at 100 ms, 100% at 500 ms |
| `brigh-001.seb` | 500 ms | 25% at 0 ms, 50% at 250 ms, 75% at 500 ms |

This outer ordered list is the Track. SEB itself has no Track name, file name,
ID-homogeneity, loop or seek flag.

## Requirements

- Firmware must be exactly **0.12.11** with final fixed-record SEB v1.
- Add `segmented-track-player.be` to the Project Berry on every participating
Controller.
- Convert every `hex` string in `track-artifacts.json` to its exact binary
bytes and upload it under the listed NetworkStorage filename.
- Start the shared timeline at `0 ms`, or seek to the explicitly supported
`500 ms` checkpoint.

## Forward playback and pause

Berry polls `timeline.getState()`. For each active segment it derives one
local-millis `at` from `timeline.at(segmentStart)`, keeps the
source cursor and calls `SEB.land` with an inclusive relative `until`.

While the timeline is paused, the plugin does not call the forward executor.
No future EventState is inserted into EventStore. Resume keeps the timeline
epoch but creates a new causal projection. The plugin reopens timing for the
remaining source suffix, so future record clocks include wall-clock time spent
paused. Seek or loop changes the epoch and reconstructs both timing and cursor
from a complete checkpoint.

`timeline.at(position)` projects the current `(timeline position P, Network
clock C)` reference as `C + (position - P)`. It returns a signed wrapping local
millis token, or `nil` outside the unambiguous ±2^31 ms conversion window.
Timeline position itself is a 24-hour ring (`0..86,399,999`), independent from
the local token's 32-bit wrap. Midnight stays continuous for Players, Layers
and animations; an exact 12-hour position difference is ambiguous.

## Checkpoint seek

The example has state checkpoints at timeline `0 ms` and `500 ms`. A timeline
epoch discontinuity must match one of those explicit positions. Berry lands the
checkpoint scene at the seek time, then sets the next segment cursor to `1`
because the segment's offset-zero state is already represented by the
checkpoint.

SEB does not infer history and the Player does not replay skipped side effects.
Add another compiled checkpoint and its next `(segment, cursor)` position for
every additional seek target that the Project supports.

## Distributed landing

The reference Player uses `SEB.land` on all prepared Controllers. EventStore
remains the authority and provides its normal later synchronization. Use
`SEB.emit` only in a different design with one explicitly authoritative Player
source.

One SEB segment is limited to 65,535 ms and 340 records (4,092 bytes within one
4,096-byte LittleFS data-block budget). One atomic due contribution accepts the
full 340-record SEB. A 341st pending event fails before queue, EventStore or
cursor mutation. Split longer Tracks in the outer Project list instead of
changing the native format.
34 changes: 34 additions & 0 deletions data/v2/examples/seb-segmented-id-track-player/example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
schemaVersion: example.v2
slug: seb-segmented-id-track-player
title: Timeline-Driven Segmented Single-ID SEB Track
category: controller
summary: >-
Drive one logical brigh[ID1] Track from the shared timeline using bounded SEB
segments. Project Berry owns segment selection, local-millis projections,
pause behavior, cursors and explicit checkpoint reconstruction after seek.
tags:
- berry-plugin
- event-player
- eventstore
- networkstorage
- seb
- timeline
- checkpoint
hardware: >-
Requires exactly firmware 0.12.11 with timeline.getState(), timeline.at()
and final SEB v1. Install the same Berry source and binary NetworkStorage
artifacts on each Controller participating in distributed playback. The
example targets only synthetic brigh[ID1] and supports explicit timeline
entry/seek positions 0 ms and 500 ms.
files:
- path: README.md
role: readme
language: markdown
- path: segmented-track-player.be
role: copy-ready Project Berry player
language: berry
- path: track-artifacts.json
role: exact NetworkStorage segment and checkpoint bytes
language: json
related:
- seb-causal-scene-recall
Loading