Group
The Group module provides functionality for managing homes and rooms within the ESP RainMaker system. It allows users to create, organize, and manage logical groups.
Groups are implemented as hierarchical structures where homes contain rooms, and rooms contain devices.
This guide explains how the Group module works, focusing on the major operations and how CDF APIs are used throughout the system.
User Flow Overview
The Group module has four main user flows:
- Home Management: Create, edit, and manage homes
- Room Management: Create, edit, and manage rooms within homes
- Device Organization: Assign devices to rooms and control them
- Group Sharing: Share homes with other users, transfer ownership, and manage permissions
Architecture Overview
Components Structure
(group)/
├── _layout.tsx # Navigation setup
├── Home.tsx # Main home dashboard
├── HomeManagement.tsx # Home management screen
├── Settings.tsx # Home settings, sharing, and info
├── Rooms.tsx # Room listing and management
├── CreateRoom.tsx # Create/edit room screen
├── CustomizeRoomName.tsx # Room name selection
└── CreateRoomSuccess.tsx # Success confirmation
components/HomeSettings/
├── HomeSharing.tsx # Display shared users and pending requests
└── AddUserModal.tsx # Modal for adding users with sharing options
1. Main Home Dashboard (Home.tsx)
This is the main screen where users see their home, rooms, and devices organized in tabs.
Fetch Group List from CDF store
// Get CDF stores
const { store } = useCDF();
const { groupStore, nodeStore, userStore } = store;
// Get current home ID and groups
const currentHomeId = groupStore.currentHomeId; // String ID
const currentHome = groupStore._groupsByID?.[currentHomeId]; // Get actual group object
const groupList = groupStore.groupList;
// Get devices from nodes
const nodes = nodeStore.nodeList;
const devices = transformNodesToDevices(nodes);
Home Operations
// Create default home if none exists
const createDefaultGroup = async () => {
userStore.user?.createGroup({
name: DEFAULT_HOME_GROUP_NAME,
nodeIds: [],
description: "",
customData: {},
type: GROUP_TYPE_HOME,
mutuallyExclusive: true, // Ensures nodes can only belong to one home at a time
});
};
// Switch between homes
const handleHomeSelect = (home: ESPRMGroup) => {
if (home?.id) {
groupStore.currentHomeId = home.id; // Update store with ID
updateLastSelectedHome(userStore, home.id); // Persist to user preferences
setSelectedHome(home); // Update local UI state
}
};
// Initialize home (runs on screen focus)
const initializeHome = async () => {
// Step 1: Handle initial setup - create default home if none exists
if (groupStore?.groupList.length === 0) {
const unAssignedNodes = getUnassignedNodes(nodeStore?.nodeList, []);
await createDefaultHomeGroup(userStore.user, unAssignedNodes);
}
// Step 2-3: Ensure homes are mutually exclusive and assign unassigned nodes
// Step 4: Determine current home
// Retrieve lastSelectedHomeId from user preferences
const lastSelectedHomeId =
userStore.userInfo?.customData?.lastSelectedHomeId?.value || null;
// Find home by preferredId or fallback to first valid home
const primaryHome = findHomeGroup(groupStore?.groupList, {
preferredId: lastSelectedHomeId,
});
const currentHomeId = primaryHome?.id || groupStore.currentHomeId;
const currentHome = groupStore._groupsByID?.[currentHomeId];
// Set current home ID in group store
groupStore.currentHomeId = currentHome.id;
// Step 5: Persist selection if changed
if (lastSelectedHomeId !== currentHome.id) {
updateLastSelectedHome(userStore, currentHome.id);
}
// Step 6: Update UI state
setSelectedHome({ ...currentHome });
};
// Refresh home data
const onRefresh = async () => {
setRefreshing(true);
try {
const shouldFetchFirstPage = true;
await fetchNodesAndGroups(shouldFetchFirstPage);
initializeHome();
} finally {
setRefreshing(false);
}
};
What this does:
- Shows home selection banner with all available homes
- Displays devices organized by rooms in tabs
- Handles home switching and device refresh
- Uses CDF methods like
fetchNodesAndGroups(),groupStore.syncGroupList() - Manages current home via
groupStore.currentHomeId(stores ID, not object) - Persists home selection to user preferences via
updateLastSelectedHome()
Key Implementation Details:
currentHomeIdis stored as a string ID ingroupStore.currentHomeId- To get the actual group object:
groupStore._groupsByID[currentHomeId] - Home selection is persisted to
userStore.userInfo.customData.lastSelectedHomeId.value - The
initializeHome()function handles initialization, node assignment, and home selection logic
2. Home Management
HomeManagement.tsx
// Get homes from CDF store
const { store } = useCDF();
const homes = store?.groupStore?.groupList || [];
// Create new home
const handleAddHome = (newHomeName: string) => {
store?.userStore?.user?.createGroup({
mutuallyExclusive: true,
name: newHomeName,
nodeIds: [],
type: GROUP_TYPE_HOME,
}).then(() => {
toast.showSuccess("Home created successfully");
setShowDialog(false);
});
};
// Refresh home list
const onRefresh = async () => {
await store.groupStore.syncGroupList();
updateHomes();
};
3. Room Management
List Rooms - (Rooms.tsx)
// Get rooms from current home
const home = groupStore?.groupList?.find(
(home) => home.id === (id || groupStore?.currentHomeId)
);
// Fetch rooms from home
const fetchGroup = async () => {
await groupStore?.syncGroupList();
if (home) {
await home.getSubGroups();
const rooms = (home.subGroups as ESPRMGroup[]) || [];
state.rooms = rooms;
}
};
// Handle room operations
const handleEditRoom = (room: ESPRMGroup) => {
router.push({
pathname: "/(group)/CreateRoom",
params: { roomId: room.id, id: state.home?.id },
});
};
const handleDeleteRoom = async (room: ESPRMGroup) => {
await room.delete();
await onRefresh();
toast.showSuccess("Room removed successfully");
};
Create a new Room in selected home (CreateRoom.tsx)
// Get home and room from store
const home = store?.groupStore?.groupList?.find((home) => home.id === id);
const room = home?.subGroups?.find((room) => room.id === roomId);
// Get available devices
const availableDevices = nodeStore?.nodeList
?.filter((node: any) => homeNodes.includes(node.id))
?.filter((node: any) => !existingNodes.includes(node.id))
?.map((node: any) => ({
id: node.id,
name: node.nodeConfig?.devices
.map((device: any) => device.displayName)
.join(", "),
node: node,
}));
// Create new room
const handleSave = () => {
home?.createSubGroup({
name: roomName,
nodeIds: selectedDevices.map((device) => device.id) || [],
customData: {},
type: GROUP_TYPE_ROOM,
mutuallyExclusive: true,
}).then(async (group) => {
toast.showSuccess("Room created successfully");
router.replace({
pathname: "/(group)/CreateRoomSuccess",
params: { id: id },
});
});
};
// Update existing room
const handleUpdate = async () => {
const existingNodes = room?.nodes;
const newNodes = selectedDevices.map((device) => device.id);
const nodesToRemove = existingNodes?.filter(
(node) => !newNodes.includes(node)
) || [];
const nodesToAdd = newNodes.filter(
(node) => !existingNodes?.includes(node)
) || [];
await Promise.allSettled([
room?.updateGroupInfo({ groupName: roomName }),
nodesToAdd.length > 0 && room?.addNodes(nodesToAdd),
nodesToRemove.length > 0 && room?.removeNodes(nodesToRemove),
]);
toast.showSuccess("Room updated successfully");
};
Select Room name (CustomizeRoomName.tsx)
// Predefined room names
const predefinedRooms: RoomType[] = [
{ key: "bedroom", label: t("group.customizeRoomName.roomNames.bedroom") },
{ key: "livingRoom", label: t("group.customizeRoomName.roomNames.livingRoom") },
{ key: "kitchen", label: t("group.customizeRoomName.roomNames.kitchen") },
// ... more room types
];
// Handle room name selection
const handleConfirm = () => {
const finalRoomName = roomName.trim() || selectedRoom;
if (finalRoomName) {
router.dismissTo({
pathname: "/(group)/CreateRoom",
params: { roomName: finalRoomName, id: id, roomId: roomId },
});
}
};
4. Home Settings Operations (Settings.tsx)
Home Management
// Update home name
const handleHomeNameUpdate = async () => {
if (homeName?.length > 0) {
setIsLoading(true);
home?.updateGroupInfo({
groupName: homeName,
}).then((res: HomeUpdateResponse) => {
if (res.status === SUCESS) {
toast.showSuccess("Home name updated successfully");
} else {
toast.showError("Failed to update home name");
}
}).finally(() => {
setIsLoading(false);
});
}
};
// Delete home or leave group
const handleRemoveHome = () => {
setIsLoading(true);
const action = isPrimary ? home?.delete() : home?.leave();
const successMessage = isPrimary
? "Home removed successfully"
: "Left home successfully";
action?.then((res: HomeUpdateResponse) => {
if (res.status === SUCESS) {
toast.showSuccess(successMessage);
router.dismiss(1);
} else {
toast.showError("Operation failed");
}
}).finally(() => {
setIsLoading(false);
});
};
Group Sharing
Group sharing allows primary users to share homes with other users, enabling collaborative home management. The system supports multiple sharing modes including standard sharing, ownership transfer, and role assignment.
User Roles
Primary Users:
- Can manage sharing (add/remove users)
- Can create and manage rooms
- Can edit home name
- Can delete the home
- Can transfer ownership
Secondary Users:
- Can view and control devices
- Cannot manage sharing
- Cannot create/edit rooms
- Cannot edit home name
- Can only leave the home (not delete)