Scene Store
The SceneStore is a reactive store that manages scene configurations and scene operations within your application. Scenes allow you to save and restore multiple device states simultaneously, enabling quick control of multiple devices with a single action.
Overview
The SceneStore provides:
- Scene list management and synchronization
- Scene creation and updates
- Scene activation
- Automatic transformation from node configurations
- Multi-node scene combination
- Automatic synchronization with NodeStore
- Reactive state management
How Scenes Work in CDF
Scene Transformation from Node Configurations
Scenes in CDF are automatically transformed from node configurations. Each node's nodeConfig.services contains a scenes service, which includes a list of scenes. The SceneStore automatically extracts and combines these scenes.
No manual transformation needed - When using CDF, scenes are automatically transformed from:
node.nodeConfig.services[scenes].params[scenes].value
Identifying Scene Service and Parameters
The SceneStore identifies scenes using specific service and parameter types:
-
Service Type:
esp.service.scenes(ESPRM_SERVICE_SCENES)- Identifies the scenes service within a node's services array
- Found in
node.nodeConfig.services[]whereservice.type === "esp.service.scenes"
-
Parameter Type:
esp.param.scenes(ESPRM_PARAM_SCENES)- Identifies the scenes parameter within the scenes service
- Found in
service.params[]whereparam.type === "esp.param.scenes" - Contains the actual scene list in
param.value[]
Example Node Configuration Structure:
node.nodeConfig.services = [
{
type: "esp.service.scenes", // Service type identifier
params: [
{
type: "esp.param.scenes", // Parameter type identifier
value: [
// Array of scenes
{
id: "scene1",
name: "Living Room Scene",
info: "Evening lighting",
action: { light: { power: true, brightness: 80 } },
},
{
id: "scene2",
name: "Bedroom Scene",
action: { fan: { power: false } },
},
],
},
],
},
];
Multi-Node Scene Combination
Scenes with the same ID across multiple nodes are automatically combined into a single scene. This allows you to manage scenes that span multiple devices.
Example:
- Node 1 has scene with ID
scene1→{ id: 'scene1', nodes: ['node1'], actions: { node1: {...} } } - Node 2 has scene with ID
scene1→ Combined with Node 1's scene - Node 3 has scene with ID
scene1→ Combined with Node 1 and Node 2's scene - Result:
{ id: 'scene1', nodes: ['node1', 'node2', 'node3'], actions: { node1: {...}, node2: {...}, node3: {...} } }
When multiple nodes share the same scene ID, they are merged into one scene object. You can check the nodes array property of a scene to see which nodes it spans:
const scene = sceneStore.getScene("scene1");
console.log("Scene spans nodes:", scene.nodes); // ['node1', 'node2', 'node3']
console.log("Actions per node:", scene.actions);
// {
// node1: { light: { power: true } },
// node2: { fan: { power: false } },
// node3: { switch: { power: true } }
// }
Scene Updates and Node Targeting
When you update a scene, the changes are applied based on the operation:
- Update all nodes: If you update a scene that spans multiple nodes, the update is applied to all nodes that contain that scene
- Update specific node: If you update a scene for a specific device/node, only that node's scene configuration is updated
The SceneStore automatically handles node updates through the updateNodeScene function, which updates the node's scene configuration in the NodeStore.
Accessing the Store
Access the SceneStore through the CDF instance:
const sceneStore = espCDF.sceneStore;
Properties
| Property | Type | Description |
|---|---|---|
| sceneList | Scene[] (computed) | A computed array of all scenes in the store. This is automatically updated when scenes are added or removed. |
| scenesByID | { [key: string]: Scene } | A dictionary of scenes indexed by their scene ID for quick lookup. |
Example Usage
// Get all scenes
const scenes = sceneStore.sceneList;
console.log("Total scenes:", scenes.length);
scenes.forEach((scene) => {
console.log("Scene:", scene.name, scene.id);
});
// Get scene by ID
const scene = sceneStore.scenesByID["scene123"];
if (scene) {
console.log("Scene found:", scene.name);
}
SceneStore Methods
Sync Scenes From Nodes
To synchronize scenes from specified nodes, use the syncScenesFromNodes() method. This method:
- Extracts scene configurations from each node's
nodeConfig.services[scenes] - Transforms scenes from node configurations into Scene instances
- Combines scenes with the same ID across multiple nodes into a single scene
- Updates the SceneStore with the synchronized scenes
How it works:
- Scenes are identified by finding the service with
type === "esp.service.scenes"(ESPRM_SERVICE_SCENES) - Within that service, the parameter with
type === "esp.param.scenes"(ESPRM_PARAM_SCENES) is located - Scenes are read from
param.value[]array - If multiple nodes have a scene with the same ID, they are merged:
- The
nodesarray contains all node IDs that have this scene - The
actionsobject contains actions per node:{ node1: {...}, node2: {...} } - The
devicesCountis the total count across all nodes
- The
/*
- nodeIds: Array of node IDs to sync scenes from
*/
try {
// Sync scenes from specific nodes
await sceneStore.syncScenesFromNodes(["node1", "node2", "node3"]);
// If node1, node2, and node3 all have scene with ID "scene1",
// it will be combined into one scene:
const scene = sceneStore.getScene("scene1");
console.log("Scene spans nodes:", scene.nodes); // ['node1', 'node2', 'node3']
console.log("Actions per node:", scene.actions);
} catch (error) {
console.error("Error syncing scenes:", error);
}
Create Scene
To create a new scene in the store, use the createScene() method. The scene is automatically made observable and interceptors are set up.
/*
- sceneData: Scene data object containing:
- id (optional): Scene ID. If not provided, a timestamp-based ID is generated
- name: Scene name
- info (optional): Scene description/info
- nodes: Array of node IDs included in the scene
- actions: Device actions per node
*/
try {
const newScene = await sceneStore.createScene({
name: "Living Room Scene",
info: "Cozy evening lighting",
nodes: ["node1", "node2"],
actions: {
node1: {
light: {
power: true,
brightness: 80,
},
},
node2: {
fan: {
power: false,
},
},
},
});
console.log("Scene created:", newScene);
} catch (error) {
console.error("Error creating scene:", error);
}
Get Scene
To retrieve a scene by its ID, use the getScene() method.
/*
- sceneId: The ID of the scene to retrieve
*/
const scene = sceneStore.getScene("scene123");
if (scene) {
console.log("Scene details:", scene);
} else {
console.log("Scene not found");
}
Set Scene List
To set the entire scene list, replacing all existing scenes, use the setSceneList() method. Each scene is made observable and interceptors are set up.
/*
- scenes: Array of scenes to set
*/
sceneStore.setSceneList(scenes);
Add Scene
To add a single scene to the store and make it observable, use the addScene() method.
/*
- scene: The scene to add
*/
const scene = new Scene(sceneData, espCDF);
const observableScene = sceneStore.addScene(scene);
Update Scene by ID
To update a scene by ID without making it observable, use the updateSceneByID() method.
/*
- id: The scene ID
- scene: The scene object to set
*/
sceneStore.updateSceneByID("scene123", updatedScene);
Delete Scenes
To remove multiple scenes from the store by their IDs, use the deleteScenes() method.
/*
- ids: Array of scene IDs to delete
*/
sceneStore.deleteScenes(["scene1", "scene2"]);
Activate Scene
To activate a scene by triggering its actions across all associated nodes, use the activateScene() method.
/*
- sceneId: The ID of the scene to activate
*/
try {
await sceneStore.activateScene("scene123");
console.log("Scene activated successfully");
} catch (error) {
console.error("Error activating scene:", error);
}
Activate Multiple Scenes
To activate multiple scenes concurrently in parallel, use the activateMultipleScenes() method.
/*
- sceneIds: Array of scene IDs to activate
*/
try {
await sceneStore.activateMultipleScenes(["scene1", "scene2", "scene3"]);
console.log("All scenes activated");
} catch (error) {
console.error("Error activating scenes:", error);
}
Clear Store
To clear all scenes from the store and reset hooks, use the clear() method.
sceneStore.clear();
Add Custom Property
To dynamically add an observable property to the store with getter and setter methods, use the addProperty() method.
/*
- propertyName: Name of the property to add
- initialValue: Initial value for the property
*/
sceneStore.addProperty("customData", {});
// Creates: customData, getCustomData(), setCustomData(value)
Accessing Scene Properties
Once you have a scene from the store, you can access its properties:
const scene = sceneStore.scenesByID["scene123"];
// Scene properties
console.log("Scene ID:", scene.id);
console.log("Scene Name:", scene.name);
console.log("Scene Info:", scene.info);
console.log("Scene Nodes:", scene.nodes);
console.log("Scene Actions:", scene.actions);
console.log("Devices Count:", scene.devicesCount);
Automatic Store Synchronization
The SceneStore automatically synchronizes with the NodeStore when scenes are created, updated, or deleted. Scene operations update the node configurations in the NodeStore to keep everything in sync.
How Scene Updates Work
When you perform scene operations (create, edit, remove), the SceneStore:
- Updates Node Configurations: The scene configuration is updated in each node's
nodeConfig.services[scenes]through theupdateNodeScenefunction - Targets Specific Nodes: Based on the operation:
- If updating a scene that spans multiple nodes, you can target specific nodes
- If updating for a specific device/node, only that node's scene configuration is updated
- The
updateNodeScenefunction handles updating the node's scene list innodeConfig.services[scenes].params[scenes].value
- Synchronizes NodeStore: The NodeStore is automatically updated with the new node configurations using
NodeStore.updateNode() - Maintains Scene Integrity: Multi-node scenes remain combined, with updates applied to the appropriate nodes
Example:
// Scene spans node1, node2, node3
const scene = sceneStore.getScene("scene1");
console.log("Scene nodes:", scene.nodes); // ['node1', 'node2', 'node3']
// Update scene - affects all nodes
await scene.edit({
name: "Updated Name",
actions: {
node1: { light: { power: true } },
node2: { fan: { power: false } },
node3: { switch: { power: true } },
},
});
// All three nodes' scene configurations are updated in their nodeConfig
// Or update for specific node only
await scene.edit({
actions: {
node1: { light: { brightness: 50 } },
},
});
// Only node1's scene configuration is updated in its nodeConfig
Scene Transformation Process
The #transformNodeListToScenes function automatically:
- Finds the scenes service by matching
service.type === "esp.service.scenes"(ESPRM_SERVICE_SCENES) - Locates the scenes parameter by matching
param.type === "esp.param.scenes"(ESPRM_PARAM_SCENES) - Extracts scenes from
param.value[]array - Combines scenes with the same ID from multiple nodes
- Creates a unified scene representation with:
nodes: Array of all node IDs that have this sceneactions: Object mapping node IDs to their scene actionsdevicesCount: Total device count across all nodes
Example transformation:
// Input: 3 nodes with scene ID "scene1"
// Node 1: { id: 'scene1', action: { light: { power: true } } }
// Node 2: { id: 'scene1', action: { fan: { power: false } } }
// Node 3: { id: 'scene1', action: { switch: { power: true } } }
// Output: Single combined scene
// {
// id: 'scene1',
// nodes: ['node1', 'node2', 'node3'],
// actions: {
// node1: { light: { power: true } },
// node2: { fan: { power: false } },
// node3: { switch: { power: true } }
// },
// devicesCount: 3
// }
Additional Resources
- Scenes Documentation - Overview of scenes feature
- Scenes Usage Guide (Firmware) - Firmware implementation
- Scenes Usage Guide (App) - App implementation