跳到主要内容
about

The Scene module provides functionality for managing device scenes across multiple nodes in the ESP RainMaker system. It allows users to create, edit, trigger, and delete scenes that can control multiple devices simultaneously.

Scenes are implemented completely on the node side as a "service", with the cloud backend acting as a gateway between nodes and clients like phone apps.

  • To know more about this feature, please click here.
  • On the usage of scenes on firmware, click here.

Scenes

This guide explains how the Scene module works, focusing on the major operations and how CDF APIs are used throughout the system.

User Flow Overview

The Scene module has two main user flows:

  1. Create New Scene: Scenes → CreateScene → Select Device → Configure Device Parameters
  2. Edit Existing Scene: Scenes → CreateScene (with existing data) → Modify → Save

Architecture Overview

Components Structure

(scene)/
├── _layout.tsx # Navigation and context setup
├── Scenes.tsx # Main scene list screen
├── CreateScene.tsx # Create/edit scene screen
├── DeviceSelection.tsx # Choose devices for scene
└── DeviceParamsSelection.tsx # Configure device parameters

1. Scenes List (Scenes.tsx)

This is the main screen where users see all their scenes and can trigger them.

// Get CDF stores
const { store } = useCDF();
const { sceneStore, nodeStore, userStore } = store;

// Get scene list from CDF
const { sceneList } = sceneStore;

// Fetch and sync scenes data from all nodes in the currently active home
const fetchScenes = async () => {
const currentHome = groupStore?._groupsByID[groupStore?.currentHomeId];
await sceneStore.syncScenesFromNodes(currentHome.nodes || []);
};

Scene Operations

// Trigger a scene
const handleSceneAction = async (sceneId: string, action: string) => {
const selectedScene = sceneList.find((scene) => scene.id === sceneId);

switch (action) {
case "activate":
await selectedScene.activate(); // CDF handles communication
break;
case "edit":
setSceneInfo(selectedScene); // Load scene into context
router.push("/(scene)/CreateScene");
break;
case "delete":
await selectedScene.remove(); // CDF removes from all nodes
break;
}
};

What this does:

  • Lists all scenes from CDF scene store
  • Handles scene activation, editing, and deletion
  • Uses CDF methods like scene.activate(), scene.remove(), scene.edit()

2. Create New Scene Flow

Step 1: CreateScene.tsx (Empty State)

// Get CDF stores
const { store } = useCDF();
const { sceneStore } = store;

// Initialize empty scene
const [state, setState] = useState({
sceneName: "",
sceneId: generateRandomId(),
isEditing: false,
config: {},
nodes: [],
});

Step 2: DeviceSelection.tsx

// Get nodes from CDF store
const { store } = useCDF();
const nodeList = store?.nodeStore?.nodeList as ESPRMNode[];

// Filter nodes that support Scenes service
const sceneNodes = nodeList.filter((node) =>
node.nodeConfig?.services?.some(
(service) => service.type === ESPRM_SCENES_SERVICE
)
);

// Extract devices from each node
sceneNodes.forEach((node) => {
const devices = node.nodeConfig?.devices ?? [];
const scene = node.nodeConfig?.services?.find(
(service) => service.type === ESPRM_SCENES_SERVICE
)?.params[0] as any;

// Check if node has reached max scenes
const isMaxSceneReached =
scene && scene.bounds?.max && scene.bounds.max == scene.value.length;

devices.forEach((device) => {
allDevices.push({
node: deepClone(node),
device: deepClone(device),
isSelected: false, // New scene, no existing actions
isMaxSceneReached,
});
});
});

Step 3: DeviceParamsSelection.tsx

// Get device from CDF node store
const { nodeId, params } = useMemo(() => {
const nodeId = state.selectedDevice?.nodeId;
if (!nodeId) return {};

const node = store.nodeStore.nodesByID[nodeId];
if (!node || !node.nodeConfig) return {};

const device = node.nodeConfig.devices.find(
(device) => device.name === state.selectedDevice?.deviceName
);
if (!device) return {};

// Map parameters with default values
const params = device.params?.map((param) => ({
...param,
value: defaultValueBasedOnParamDataType(param.dataType),
}));

return { selectedDevice: device, nodeId, params };
}, [state.selectedDevice]);

Step 4: Save Scene

const handleSave = async () => {
setLoading((prev) => ({ ...prev, save: true }));

try {
// Prepare scene data
const sceneData = {
id: state.sceneId,
name: state.sceneName,
description: "",
nodes: Object.keys(state.config), // Node IDs involved
config: state.config || {}, // Device actions
};

// Create new scene using CDF
await sceneStore.createScene(sceneData as any);
toast.showSuccess(t("sceneManagement.sceneCreatedSuccessfully"));

resetState();
router.dismissTo("/(scene)/Scenes");
} catch (error) {
console.error("Error saving scene:", error);
toast.showError(t("scene.errors.sceneCreationFailed"));
} finally {
setLoading((prev) => ({ ...prev, save: false }));
}
};

3. Edit Existing Scene Flow

Flow: Scenes → CreateScene (with existing data) → Modify → Save

Step 1: Load Existing Scene Data

// When editing, scene data is already loaded in context
useEffect(() => {
if (state.isEditing && state.sceneId) {
// Scene data is already in context from Scenes.tsx
// No need to fetch from CDF again
}
}, [state.isEditing, state.sceneId]);

Step 2: Modify Scene Data

// User can modify existing scene data
// Changes are tracked in scene context
const handleSceneNameChange = (name: string) => {
setSceneName(name);
};

const handleDeviceActionChange = (
nodeId: string,
deviceName: string,
paramName: string,
value: any
) => {
setActionValue(nodeId, deviceName, paramName, value);
};

Step 3: Save Changes

const handleSave = async () => {
setLoading((prev) => ({ ...prev, save: true }));

try {
// Update existing scene using CDF
await sceneStore.scenesByID[state.sceneId]?.edit({
sceneName: state.sceneName,
config: state.config,
nodes: Object.keys(state.config),
});
toast.showSuccess(t("sceneManagement.sceneUpdatedSuccessfully"));

resetState();
router.dismissTo("/(scene)/Scenes");
} catch (error) {
console.error("Error updating scene:", error);
toast.showError(t("scene.errors.sceneUpdateFailed"));
} finally {
setLoading((prev) => ({ ...prev, save: false }));
}
};

4. Scene Operations (Delete & Activate)

Delete Scene Operation

// Delete button only visible when editing existing scene
const handleDelete = async () => {
setLoading((prev) => ({ ...prev, delete: true }));

try {
// Use CDF to remove scene from all nodes
await sceneStore.scenesByID[state.sceneId]?.remove();
toast.showSuccess(t("sceneManagement.sceneDeletedSuccessfully"));
resetState();
router.dismissTo("/(scene)/Scenes");
} catch (error) {
console.error("Error deleting scene:", error);
toast.showError(t("sceneManagement.sceneDeletionFailed"));
} finally {
setLoading((prev) => ({ ...prev, delete: false }));
}
};

Key Points:

  • Delete button only appears when editing an existing scene
  • Uses CDF's scene.remove() method
  • Automatically removes scene from all involved nodes

Activate Scene Operation

// Activate scene from Scenes.tsx
const handleSceneActivate = async (sceneId: string) => {
try {
const selectedScene = sceneStore.scenesByID[state.sceneId];
if (selectedScene) {
await selectedScene.activate(); // CDF handles communication
toast.showSuccess("Scene activated successfully");
}
} catch (error) {
console.error("Error activating scene:", error);
toast.showError("Failed to activate scene");
}
};

What this does:

  • Triggers scene execution across all involved nodes
  • Uses CDF's scene.activate() method
  • Automatically sends activation payload to all nodes

CDF API Usage Summary

1. Scene Store Operations

// Get all scenes
const { sceneList } = sceneStore;

// Create new scene
await sceneStore.createScene(sceneData);

// Edit existing scene
await sceneStore.scenesByID[sceneId].edit(updates);

// Delete scene
await sceneStore.scenesByID[sceneId].remove();

// Activate scene
await scene.activate();

// Sync scenes from nodes (requires node IDs array)
const currentHome = groupStore?._groupsByID[groupStore?.currentHomeId];
await sceneStore.syncScenesFromNodes(currentHome.nodes || []);

Data Flow Summary

Create New Scene Flow:

  1. Scenes.tsx → User clicks "Create Scene"
  2. CreateScene.tsx → User enters scene name
  3. DeviceSelection.tsx → User selects devices from CDF node store
  4. DeviceParamsSelection.tsx → User configures parameters
  5. CreateScene.tsx → Save scene using CDF sceneStore.createScene()

Edit Existing Scene Flow:

  1. Scenes.tsx → User clicks "Edit" on existing scene
  2. CreateScene.tsx → Scene data loaded from context
  3. User modifies → Scene name, device actions, etc.
  4. Save changes → Using CDF sceneStore.scenesByID[sceneId].edit()

Delete & Activate Operations:

  • Delete: Only available in edit mode, uses CDF scene.remove()
  • Activate: Available from scene list, uses CDF scene.activate()

Key Takeaways

  1. Two Main Flows: Create (4 steps) vs Edit (2 steps)
  2. CDF is the Backbone: All operations use CDF stores
  3. Context Management: Scene context manages temporary state
  4. Delete Visibility: Delete button only shows when editing
  5. Async Operations: All CDF operations are async with proper error handling
  6. Data Flow: Scenes → CreateScene → Device Selection → Parameter Configuration → Save