跳到主要内容

场景存储

SceneStore 是一种响应式存储,用于在应用中管理场景配置与场景操作。场景可以同时保存与恢复多个设备状态,从而通过一次操作快速控制多个设备。

概览

SceneStore 提供以下功能:

  • 管理与同步场景列表
  • 创建与更新场景
  • 激活场景
  • 查询节点配置并自动转换
  • 合并多节点场景
  • 自动同步 NodeStore
  • 响应式状态管理

场景在 CDF 中如何工作

查询节点配置并自动转换场景

在 CDF 中,场景会查询节点配置并进行自动转换。每个节点的 nodeConfig.services 包含一个场景服务,其中含有场景列表。SceneStore 会自动提取并合并这些场景。

无需手动转换 – 使用 CDF 时,场景会自动从以下路径转换:

node.nodeConfig.services[scenes].params[scenes].value

识别场景服务与参数

SceneStore 通过特定的服务类型与参数类型来识别场景:

  • 服务类型esp.service.scenes (ESPRM_SERVICE_SCENES)

    • 标识节点服务数组中的场景服务。
    • 位于 node.nodeConfig.services[],其中 service.type === "esp.service.scenes"
  • 参数类型esp.param.scenes (ESPRM_PARAM_SCENES)

    • 标识场景服务中的场景参数。
    • 位于 service.params[],其中 param.type === "esp.param.scenes"
    • 实际的场景列表存放在 param.value[] 中。

节点配置结构示例:

node.nodeConfig.services = [
{
type: "esp.service.scenes", // 服务类型标识符
params: [
{
type: "esp.param.scenes", // 参数类型标识符
value: [
// 场景数组
{
id: "scene1",
name: "Living Room Scene",
info: "Evening lighting",
action: { light: { power: true, brightness: 80 } },
},
{
id: "scene2",
name: "Bedroom Scene",
action: { fan: { power: false } },
},
],
},
],
},
];

多节点场景合并

多个节点上具有相同 ID 的场景会自动合并为一个场景,便于管理跨多个设备的场景。

示例:

  • 节点 1 中存在 ID 为 scene1 的场景 → { id: 'scene1', nodes: ['node1'], actions: { node1: {...} } }
  • 节点 2 中存在 ID 为 scene1 的场景 → 与节点 1 的场景合并。
  • 节点 3 中存在 ID 为 scene1 的场景 → 与节点 1 和节点 2 的场景合并。
  • 结果:{ id: 'scene1', nodes: ['node1', 'node2', 'node3'], actions: { node1: {...}, node2: {...}, node3: {...} } }

当多个节点共享同一个场景 ID 时,这些节点会被合并为一个场景对象。可以通过查看场景的 nodes 数组属性来确认其涉及哪些节点:

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 } }
// }

场景更新与节点定向

更新场景时,更改的应用方式取决于操作类型:

  • 更新所有节点:如果更新跨多个节点的场景,则改动将应用至包含该场景的所有节点。
  • 仅更新特定节点:如果仅更新某个设备/节点的场景,则改动将仅应用至该节点的场景配置。

SceneStore 会通过 updateNodeScene 函数自动处理节点更新,并在 NodeStore 中更新该节点的场景配置。

访问存储

通过 CDF 实例访问 SceneStore

const sceneStore = espCDF.sceneStore;

属性

属性类型描述
sceneListScene[](计算属性)存储中所有场景的计算数组。添加或移除场景时会自动更新。
scenesByID{ [key: string]: Scene }按场景 ID 建立索引的字典,便于快速查找。

示例用法

// 获取所有场景
const scenes = sceneStore.sceneList;
console.log("Total scenes:", scenes.length);
scenes.forEach((scene) => {
console.log("Scene:", scene.name, scene.id);
});

// 按照 ID 获取场景
const scene = sceneStore.scenesByID["scene123"];
if (scene) {
console.log("Scene found:", scene.name);
}

SceneStore 方法

从节点同步场景

若要从指定节点同步场景,请使用 syncScenesFromNodes() 方法。该方法会:

  1. 从各节点的 nodeConfig.services[scenes] 中提取场景配置。
  2. 将节点配置中的场景转换为场景实例。
  3. 将多个节点中 ID 相同的场景合并为一个场景。
  4. 用同步后的场景更新 SceneStore

工作原理:

  • 通过查找 type === "esp.service.scenes" (ESPRM_SERVICE_SCENES) 的服务来识别场景。
  • 在该服务中,定位 type === "esp.param.scenes"ESPRM_PARAM_SCENES)的参数。
  • param.value[] 数组读取场景。
  • 如果多个节点存在 ID 相同的场景,则进行合并:
    • nodes 数组包含拥有该场景的所有节点 ID。
    • actions 对象按节点保存动作:{ node1: {...}, node2: {...} }
    • devicesCount 为所有节点的设备总数。
/*
- nodeIds: 要同步场景的节点 ID 数组
*/

try {
// 从指定节点同步场景
await sceneStore.syncScenesFromNodes(["node1", "node2", "node3"]);

// 如果 node1、node2 和 node3 都有 ID 为 "scene1" 的场景,
// 则这些节点会合并为一个场景:
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);
}

创建场景

要在存储中创建新场景,请使用 createScene() 方法。场景创建后会自动变成可观察对象,且系统会为其设置拦截器。

/*
- sceneData: 场景数据对象,包含:
- id(可选):场景 ID。如果未提供,会生成基于时间戳的 ID
- name: 场景名称
- info(可选):场景描述/信息
- nodes: 包含在场景中的节点 ID 数组
- actions: 每个节点的设备操作
*/

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);
}

获取场景

若想按 ID 获取场景,请使用 getScene() 方法。

/*
- sceneId: 要获取的场景 ID
*/

const scene = sceneStore.getScene("scene123");
if (scene) {
console.log("Scene details:", scene);
} else {
console.log("Scene not found");
}

设置场景列表

要替换现有的全部场景列表,请使用 setSceneList() 方法。每个场景都会自动变成可观察对象,且系统会为其设置拦截器。

/*
- scenes: 要设置的场景数组
*/

sceneStore.setSceneList(scenes);

添加场景

要向存储中添加单个场景并使其成为可观察对象,请使用 addScene() 方法。

/*
- scene: 要添加的场景
*/

const scene = new Scene(sceneData, espCDF);
const observableScene = sceneStore.addScene(scene);

按 ID 更新场景

要按 ID 更新场景且不将其转换为可观察对象,请使用 updateSceneByID() 方法。

/*
- id: 场景 ID
- scene: 要设置的场景对象
*/

sceneStore.updateSceneByID("scene123", updatedScene);

删除场景

要按 ID 从存储中移除多个场景,请使用 deleteScenes() 方法。

/*
- ids: 要删除的场景 ID 数组
*/

sceneStore.deleteScenes(["scene1", "scene2"]);

激活场景

若想在所有关联节点上触发场景操作以激活该场景,请使用 activateScene() 方法。

/*
- sceneId: 要激活的场景 ID
*/

try {
await sceneStore.activateScene("scene123");
console.log("Scene activated successfully");
} catch (error) {
console.error("Error activating scene:", error);
}

并行激活多个场景

若想同时激活多个场景,请使用 activateMultipleScenes() 方法。

/*
- sceneIds: 要激活的场景 ID 数组
*/

try {
await sceneStore.activateMultipleScenes(["scene1", "scene2", "scene3"]);
console.log("All scenes activated");
} catch (error) {
console.error("Error activating scenes:", error);
}

清空存储

要清除存储中的所有场景并重置钩子,请使用 clear() 方法。

sceneStore.clear();

添加自定义属性

若想向存储动态添加带有 getter 和 setter 的可观察属性,请使用 addProperty() 方法。

/*
- propertyName: 要添加的属性名称
- initialValue: 属性的初始值
*/

sceneStore.addProperty("customData", {});
// 创建:customData、getCustomData()、setCustomData(value)

访问场景属性

从存储中获取到场景后,可以访问其属性:

const scene = sceneStore.scenesByID["scene123"];

// 场景属性
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);

存储自动同步

当场景被创建、更新或删除时,SceneStore 会与 NodeStore 自动同步。场景操作会更新 NodeStore 中的节点配置以保持一致。

场景更新的工作方式

当执行场景操作(创建、编辑、移除)时,SceneStore 会:

  1. 更新节点配置:通过 updateNodeScene 函数,在每个节点的 nodeConfig.services[scenes] 中更新场景配置。
  2. 定向目标节点:根据操作类型,针对性地进行不同更新。
    • 若更新跨多个节点的场景,可以只针对特定节点。
    • 若只更新某个设备/节点,则仅更新该节点的场景配置。
    • updateNodeScene 函数会负责更新 nodeConfig.services[scenes].params[scenes].value 中该节点的场景列表。
  3. 同步 NodeStore:使用 NodeStore.updateNode() 自动写回新的节点配置。
  4. 保持场景一致性:多节点场景保持合并状态,并将更新应用到相应节点。

示例:

// 场景涉及 node1、node2、node3
const scene = sceneStore.getScene("scene1");
console.log("Scene nodes:", scene.nodes); // ['node1', 'node2', 'node3']

// 更新场景 – 影响所有节点
await scene.edit({
name: "Updated Name",
actions: {
node1: { light: { power: true } },
node2: { fan: { power: false } },
node3: { switch: { power: true } },
},
});
// 三个节点的场景配置都会在其 nodeConfig 中更新

// 或者仅更新特定节点
await scene.edit({
actions: {
node1: { light: { brightness: 50 } },
},
});
// 仅 node1 的场景配置会在其 nodeConfig 中更新

场景转换流程

#transformNodeListToScenes 函数会自动:

  • 通过匹配 service.type === "esp.service.scenes" (ESPRM_SERVICE_SCENES) 找到场景服务。
  • 通过匹配 param.type === "esp.param.scenes" (ESPRM_PARAM_SCENES) 定位场景参数。
  • param.value[] 数组中提取场景。
  • 合并多个节点中 ID 相同的场景。
  • 生成统一的场景表示,包含:
    • nodes:拥有该场景的所有节点 ID 的数组
    • actions:将节点 ID 映射到其场景操作的对象
    • devicesCount:所有节点上的设备总数

转换示例:

// 输入:3 个节点,场景 ID 为 "scene1"
// 节点 1:{ id: 'scene1', action: { light: { power: true } } }
// 节点 2:{ id: 'scene1', action: { fan: { power: false } } }
// 节点 3:{ id: 'scene1', action: { switch: { power: true } } }

// 输出:合并后的单个场景
// {
// id: 'scene1',
// nodes: ['node1', 'node2', 'node3'],
// actions: {
// node1: { light: { power: true } },
// node2: { fan: { power: false } },
// node3: { switch: { power: true } }
// },
// devicesCount: 3
// }

其他参考材料