跳到主要内容
about

The Device Control module provides functionality for controlling IoT devices in the ESP RainMaker system. It dynamically renders device-specific control panels based on device type and parameters, with support for custom control screens and parameter configurations.

Device control is implemented using a configuration-driven approach where device types, parameters, and control panels are defined in configuration files. The system automatically selects appropriate control interfaces based on device capabilities.


Device Control

This guide explains how the Device Control module works, focusing on the major operations and how device configurations, parameters, and control panels are managed throughout the system.

User Flow Overview

The Device Control module has three main user flows:

  1. Device Control: View and control device parameters
  2. Device Settings: Configure device name, OTA updates, and removal

Architecture Overview

Components Structure

(device)/
├── Control.tsx # Main device control screen
├── Settings.tsx # Device settings and configuration
└── device_panels/ # Device-specific control panels
├── Light.tsx # Light device control panel
├── Switch.tsx # Switch device control panel
├── Temperature.tsx # Temperature sensor control panel
└── Fallback.tsx # Generic fallback control panel

config/
├── devices.config.ts # Device type configurations
└── params.config.ts # Parameter type configurations

utils/
└── device.ts # Device utility functions

1. Main Device Control (Control.tsx)

This is the main screen that dynamically renders device-specific control panels based on device type.

Identify the selected device

// Get CDF store for device operations
const { store } = useCDF();
const { nodeStore } = store;

// Get device from node
const nodeList = store?.nodeStore?.nodeList || [];
const node = nodeList.find((n) => n.id === id) as ESPRMNode | undefined;
const device = node?.nodeConfig?.devices.find((d) => d.name === _device) as ESPRMDevice | undefined;

Device Type Detection

// Device type detection using utility function
const deviceType = extractDeviceType(device.type);

// Find device configuration from config
const deviceConfig = findDeviceConfig(deviceType);

// Control panel mapping
const CONTROL_PANELS: Record<string, React.FC<any>> = {
light: LightControl,
switch: SwitchControl,
temperature: TemperatureControl,
};

// Device control renderer
const renderDeviceControl = () => {
const deviceConfig = findDeviceConfig(deviceType);
if (!deviceConfig || !deviceConfig.controlPanel) {
return <Fallback node={node as any} device={device} />;
}

const ControlPanel = CONTROL_PANELS[deviceConfig.controlPanel as keyof typeof CONTROL_PANELS];
return <ControlPanel node={node} device={device} />;
};

What this does:

  • Dynamically detects device type from node configuration
  • Maps device types to appropriate control panel components
  • Falls back to generic control panel if no specific panel is configured
  • Uses CDF node store to access device information

2. Device Configuration (devices.config.ts)

Device Type Configuration Structure

export const DEVICE_TYPE_LIST = [
{
label: "Lighting", // Human-readable category label
groupLabel: "Lights", // Group label for organization
type: [ // Supported device types
"lightbulb",
"lightbulb3",
"lightbulb4",
"lightbulb5",
"lightstrip",
"lightstrip1",
"light",
],
name: "Light", // Device name
param: "Light", // Parameter name
deviceType: ["1", "2"], // ESP device type codes
icon: { // Icon mappings for different types
lightbulb: { icon: "light-3" },
lightbulb3: { icon: "light-1" },
lightbulb4: { icon: "light-1" },
lightbulb5: { icon: "light-1" },
lightstrip: { icon: "light-5" },
lightstrip1: { icon: "light-5" },
},
defaultIcon: "light-1", // Default icon if no specific mapping
disabled: false, // Whether device type is enabled
controlPanel: "light", // Control panel component to use
}
];

What this does:

  • Defines all supported device types in the system
  • Maps device types to control panel components
  • Provides icon mappings for different device variants
  • Enables/disables specific device types
  • Associates ESP device type codes with human-readable names

3. Parameter Configuration (params.config.ts)

Parameter Type Configuration Structure

export const PARAM_CONTROLS = [
{
name: string, // Parameter control name
types: string[], // Supported parameter types (ESPRM_*_PARAM_TYPE)
control: React.Component | null, // React component to render
dataTypes: string | string[], // Supported data types (DATA_TYPE_*)
paramType?: string, // Optional: Specific parameter type
hide?: boolean, // Optional: Whether to hide in room view
roomLabel?: string, // Optional: Label for room view
requirements?: string, // Optional: Requirements description
},
// ... more parameter controls
];

Structure Properties:

  • name: Human-readable parameter control name
  • types: Array of ESPRM parameter type constants this control handles
  • control: React component to render for this parameter type (null for hidden params)
  • dataTypes: Data type(s) this control supports (BOOL, INT, FLOAT, STRING)
  • paramType: Optional specific parameter type identifier
  • hide: Optional flag to hide parameter in room view
  • roomLabel: Optional label for room view display
  • requirements: Optional description of requirements (e.g., "bounds (min, max)")

What this does:

  • Defines all supported parameter types and their UI controls
  • Maps parameter types to React components
  • Specifies data type requirements for each control
  • Provides room view labels and requirements
  • Enables parameter hiding and special handling

4. Device Utility Functions (device.ts)

Core Utility Functions

/**
* Extracts the core device type from a full type string
* @param fullType The complete device type (e.g. "esp.device.lightbulb")
* @returns The extracted device type (e.g. "lightbulb")
*/
extractDeviceType(fullType: string | undefined): string

/**
* Finds a matching device configuration from the device type list
* @param deviceType The device type to find
* @returns The matching device configuration or undefined
*/
findDeviceConfig(deviceType: string): DeviceConfig | undefined

/**
* Gets the appropriate device image based on device type and connection status
* @param type The device type identifier
* @param isConnected Whether the device is currently connected
* @returns The image resource for the device
*/
getDeviceImage(type: string | undefined, isConnected: boolean): ImageResource

/**
* Transforms ESP RainMaker nodes into flattened device arrays
* @param nodes Array of ESPRMNode objects
* @returns Flattened array of devices with weak node references
*/
transformNodesToDevices(nodes: ESPRMNode[]): Array<ESPRMDevice & { node: WeakRef<ESPRMNode> }>

Function Descriptions:

  • extractDeviceType: Extracts the core device type identifier from full type strings (handles formats like "esp.device.lightbulb" or direct "lightbulb")
  • findDeviceConfig: Searches the device configuration list to find a matching device configuration based on device type
  • getDeviceImage: Returns the appropriate device image/icon based on device type and connection status (online/offline variants)
  • transformNodesToDevices: Converts ESP RainMaker node objects into a flattened array of devices with weak references to their parent nodes

5. Device Control Panels

Specific device control pannel example: Light Control Panel (Light.tsx)

// Example of device-specific control panel
const LightControl = ({ node, device }: { node: ESPRMNode; device: ESPRMDevice }) => {
// Get device parameters
const powerParam = device.params?.find(param => param.type === ESPRM_POWER_PARAM_TYPE);
const brightnessParam = device.params?.find(param => param.type === ESPRM_BRIGHTNESS_PARAM_TYPE);
const hueParam = device.params?.find(param => param.type === ESPRM_HUE_PARAM_TYPE);
const saturationParam = device.params?.find(param => param.type === ESPRM_SATURATION_PARAM_TYPE);

// Handle parameter changes
const handlePowerChange = async (value: boolean) => {
try {
await powerParam?.setValue(value);
} catch (error) {
console.error("Error setting power:", error);
}
};

const handleBrightnessChange = async (value: number) => {
try {
await brightnessParam?.setValue(value);
} catch (error) {
console.error("Error setting brightness:", error);
}
};

return (
<View style={styles.container}>
{/* Power Toggle */}
<ToggleSwitch
value={powerParam?.value || false}
onValueChange={handlePowerChange}
label="Power"
/>

{/* Brightness Slider */}
{brightnessParam && (
<BrightnessSlider
value={brightnessParam.value || 0}
onValueChange={handleBrightnessChange}
min={brightnessParam.bounds?.min || 0}
max={brightnessParam.bounds?.max || 100}
/>
)}

{/* Color Controls */}
{hueParam && saturationParam && (
<ColorControls
hue={hueParam.value || 0}
saturation={saturationParam.value || 0}
onHueChange={(value) => hueParam.setValue(value)}
onSaturationChange={(value) => saturationParam.setValue(value)}
/>
)}
</View>
);
};

Fallback Control Panel (Fallback.tsx)

// Generic control panel for unsupported devices
const Fallback = ({ node, device }: { node: ESPRMNode; device: ESPRMDevice }) => {
// Get all device parameters
const params = device.params || [];

// Filter out hidden parameters
const visibleParams = params.filter(param =>
param.uiType !== ESPRM_UI_HIDDEN_PARAM_TYPE
);

return (
<View style={styles.container}>
<Text style={styles.title}>{device.displayName || device.name}</Text>

{/* Render generic controls for each parameter */}
{visibleParams.map((param, index) => {
const control = findParamControl(param.uiType || param.type);

if (!control) return null;

return (
<View key={index} style={styles.paramContainer}>
<Text style={styles.paramLabel}>{param.name}</Text>
<control.control
value={param.value}
onValueChange={(value) => param.setValue(value)}
meta={param.bounds}
/>
</View>
);
})}
</View>
);
};

6. Device Settings (Settings.tsx)

Device Management Operations

The Device Settings screen provides comprehensive device management functionality:

Key Features:

  • Device Name Management: Update device display name via esp.param.name parameter
  • Device Information Display: Shows firmware version, model, and device capabilities
  • OTA Update Management: Check for firmware updates and initiate OTA updates
  • Device Sharing: Share device with other users via email
  • Factory Reset: Reset device to factory defaults using system service parameters
  • Device Removal: Remove device from user account and perform cleanup

SDK Operations:

  • nameParam.setValue(deviceName) - Update device name
  • node.checkOTAUpdate() - Check for available firmware updates
  • node.performOTAUpdate() - Initiate OTA update process
  • node.shareWith({ username }) - Share device with another user
  • node.setMultipleParams() - Set factory reset parameter
  • node.delete() - Remove device from account

Adding Support for New Devices

Step 1: Add Device Configuration

// In devices.config.ts
export const DEVICE_TYPE_LIST = [
// ... existing devices
{
label: "New Device Type",
groupLabel: "New Devices",
type: ["newdevicetype", "newdevicetype2"],
name: "New Device",
param: "NewDevice",
deviceType: ["100", "101"],
icon: {
newdevicetype: { icon: "new-device" },
newdevicetype2: { icon: "new-device-2" },
},
defaultIcon: "new-device",
disabled: false,
controlPanel: "newdevice", // This will map to a control panel
},
];

Step 2: Create Control Panel Component

// Create app/(device)/device_panels/NewDevice.tsx
const NewDeviceControl = ({ node, device }: { node: ESPRMNode; device: ESPRMDevice }) => {
// Get device parameters
const powerParam = device.params?.find(param => param.type === ESPRM_POWER_PARAM_TYPE);
const customParam = device.params?.find(param => param.name === "custom");

// Handle parameter changes
const handlePowerChange = async (value: boolean) => {
try {
await powerParam?.setValue(value);
} catch (error) {
console.error("Error setting power:", error);
}
};

const handleCustomChange = async (value: any) => {
try {
await customParam?.setValue(value);
} catch (error) {
console.error("Error setting custom param:", error);
}
};

return (
<View style={styles.container}>
{/* Custom UI for new device */}
<ToggleSwitch
value={powerParam?.value || false}
onValueChange={handlePowerChange}
label="Power"
/>

{/* Custom parameter control */}
{customParam && (
<CustomControl
value={customParam.value}
onValueChange={handleCustomChange}
meta={customParam.bounds}
/>
)}
</View>
);
};

export default NewDeviceControl;

Step 3: Register Control Panel

// In Control.tsx, add to CONTROL_PANELS
import NewDeviceControl from "@/app/(device)/device_panels/NewDevice";

const CONTROL_PANELS: Record<string, React.FC<any>> = {
light: LightControl,
switch: SwitchControl,
temperature: TemperatureControl,
newdevice: NewDeviceControl, // Add new control panel
};

Adding Support for New Parameters

Step 1: Add Parameter Configuration

// In params.config.ts
export const PARAM_CONTROLS = [
// ... existing parameters
{
name: "Custom Parameter",
types: ["esp.ui.custom", "esp.param.custom"],
control: CustomControl, // Your custom React component
dataTypes: [DATA_TYPE_INT, DATA_TYPE_FLOAT],
requirements: "custom bounds and validation",
roomLabel: "Custom",
},
];

Step 2: Create Parameter Control Component

// Create components/ParamControls/CustomControl.tsx
const CustomControl = ({
value,
onValueChange,
meta,
disabled = false
}: CustomControlProps) => {
const [localValue, setLocalValue] = useState(value);

const handleChange = (newValue: any) => {
setLocalValue(newValue);
onValueChange(newValue);
};

return (
<View style={styles.container}>
<Text style={styles.label}>Custom Parameter</Text>
{/* Your custom UI implementation */}
<CustomSlider
value={localValue}
onValueChange={handleChange}
min={meta?.min || 0}
max={meta?.max || 100}
disabled={disabled}
/>
</View>
);
};

ESP SDK API Usage Summary

1. Device Parameter Operations

// Get device parameters
const params = device.params || [];

// Find specific parameter
const powerParam = device.params?.find(param => param.type === ESPRM_POWER_PARAM_TYPE);

// Set parameter value
await powerParam?.setValue(true);

// Set multiple parameters
await device.setMultipleParams({
[device.name]: [
{ power: true },
{ brightness: 50 }
]
});

2. Node Operations

// Check OTA updates
const hasUpdate = await node.checkOTAUpdate();

// Delete node (remove device)
const result = await node.delete();

// Get node configuration
const nodeConfig = node.nodeConfig;
const devices = nodeConfig.devices;
const services = nodeConfig.services;

3. Device Configuration Access

// Get device display name
const displayName = device.displayName;

// Get device type
const deviceType = device.type;

// Get device parameters
const params = device.params;

// Check device connectivity
const isConnected = node.connectivityStatus?.isConnected;

Data Flow Summary

Device Control Flow:

  1. Control.tsx → User opens device control screen
  2. Device Detection → Extract device type using extractDeviceType()
  3. Configuration Lookup → Find device config using findDeviceConfig()
  4. Control Panel Selection → Map to appropriate control panel component
  5. Parameter Rendering → Render parameters using params.config.ts
  6. User Interaction → Handle parameter changes via ESP SDK

Device Settings Flow:

  1. Settings.tsx → User opens device settings
  2. Parameter Access → Access device parameters for configuration
  3. Name Management → Update device display name
  4. OTA Updates → Check and initiate firmware updates
  5. Device Removal → Factory reset and remove device

Parameter Control Flow:

  1. Parameter Detection → Find device parameters
  2. Control Mapping → Map parameter types to UI controls
  3. Value Binding → Bind current parameter values to UI
  4. Change Handling → Handle user input and update parameters
  5. ESP Communication → Send updates via ESP SDK

Key Takeaways for Developers

  1. Configuration-Driven: Device types and parameters are defined in config files
  2. Dynamic Control Panels: Control panels are selected based on device type
  3. Parameter Mapping: Parameters automatically map to appropriate UI controls
  4. Fallback Support: Generic fallback panel for unsupported devices
  5. Extensible Architecture: Easy to add new device types and parameters
  6. ESP SDK Integration: All device operations use ESP RainMaker SDK
  7. Type Safety: Strong typing for device configurations and parameters
  8. Modular Design: Control panels and parameter controls are modular components

Common Patterns

Device Type Detection Pattern:

const deviceType = extractDeviceType(device.type);
const deviceConfig = findDeviceConfig(deviceType);

if (deviceConfig?.controlPanel) {
const ControlPanel = CONTROL_PANELS[deviceConfig.controlPanel];
return <ControlPanel node={node} device={device} />;
} else {
return <Fallback node={node} device={device} />;
}

Parameter Control Pattern:

const param = device.params?.find(p => p.type === paramType);
if (param) {
const control = findParamControl(param.uiType || param.type);
return (
<control.control
value={param.value}
onValueChange={(value) => param.setValue(value)}
meta={param.bounds}
/>
);
}

Device Configuration Pattern:

// Add new device type
{
label: "New Device",
type: ["newdevicetype"],
controlPanel: "newdevice",
icon: { newdevicetype: { icon: "new-icon" } },
defaultIcon: "new-icon",
disabled: false,
}

// Add new parameter control
{
name: "New Parameter",
types: ["esp.ui.new"],
control: NewControl,
dataTypes: [DATA_TYPE_INT],
requirements: "specific requirements",
}

Error Handling Pattern:

try {
const result = await operation();
if (result?.status === SUCESS) {
toast.showSuccess("Operation successful");
} else {
toast.showError("Operation failed");
}
} catch (error) {
console.error("Error:", error);
toast.showError("Operation failed");
}

Assisted Claiming

Overview

Assisted claiming is a feature that allows devices to be claimed to a user account before provisioning. This is automatically detected and handled during the device provisioning flow. Devices that require claiming must be associated with a user account before they can be configured with WiFi credentials and added to the home.

When is Claiming Required?

Assisted claiming is required when:

  • Device's rmaker.cap array contains "claim" capability
  • Detected automatically after device connection and capability parsing
  • Required for devices that need to be claimed to a user account before provisioning

Note: Assisted claiming is NOT supported for SoftAP transport. Only BLE and QR Code provisioning methods support assisted claiming.

How Claiming is Detected

  1. After Device Connection: Once a device is connected (via BLE or QR Code), the app fetches:

    • Device version info (getDeviceVersionInfo())
    • Provisioning capabilities (getDeviceCapabilities())
  2. Capability Parsing: The app parses RMaker capabilities using parseRMakerCapabilities():

import { parseRMakerCapabilities } from "@/utils/rmakerCapabilities";

const versionInfo = await device.getDeviceVersionInfo();
const provCapabilities = await device.getDeviceCapabilities();
const rmakerCaps = parseRMakerCapabilities(versionInfo, provCapabilities);
  1. Claiming Detection: The function checks the rmaker.cap array in the version info:
if (rmakerCaps.hasClaim) {
// Navigate to Claiming screen
}

Claiming Flow Integration

The claiming step is integrated into BLE and QR Code provisioning methods with the following order:

BLE Scanning (ScanBLE.tsx):

  • If POP required: Navigate to POP → After POP verification → Claiming (if supported) → WiFi
  • If no POP: Initialize session → Claiming (if supported) → WiFi

QR Code Scanning (ScanQR.tsx):

  • If POP in QR: Set POP → Initialize session → Claiming (if supported) → WiFi
  • If POP required but not in QR: Initialize session → Claiming (if supported) → WiFi
  • If no POP: Initialize session → Claiming (if supported) → WiFi

SoftAP (ScanSoftAP.tsx):

Assisted claiming is NOT supported for SoftAP transport

POP Screen (POP.tsx):

After POP verification, navigates to Claiming (if supported) or WiFi

Claiming Screen Implementation (Claiming.tsx)

The Claiming screen handles the assisted claiming process:

Key Features:

  • Automatic claiming process initiation on screen mount
  • Real-time progress updates with animated loading indicator
  • Success/failure state handling with visual feedback
  • Automatic navigation to WiFi setup on success (with 1.5 second delay)
  • Error handling with user-friendly messages
  • Device disconnection on error before navigating back

Claiming Process:

  1. Automatic Start: The Claiming screen automatically starts the claiming process on mount
  2. Progress Updates: Real-time progress messages from the SDK are displayed
  3. Success: Automatically navigates to WiFi setup after successful claiming
  4. Failure: Displays error message with OK button to go back

SDK Integration:

The claiming process uses the SDK's startAssistedClaiming() method:

// Get connected device from store
const device: ESPDevice = store.nodeStore.connectedDevice;

// Start assisted claiming with progress callback
await device.startAssistedClaiming((response: ESPClaimResponse) => {
setStatus(response.status);
setProgressMessage(response.message);

if (response.status === ESPClaimStatus.success) {
// Navigate to WiFi setup
} else if (response.status === ESPClaimStatus.failed) {
// Show error message
}
});

Claiming Status Types:

enum ESPClaimStatus {
inProgress = "in_progress",
success = "success",
failed = "failed"
}

Component Structure:

const ClaimingScreen = () => {
const device: ESPDevice = store.nodeStore.connectedDevice;
const [status, setStatus] = useState<ESPClaimStatus>(ESPClaimStatus.inProgress);
const [progressMessage, setProgressMessage] = useState("Starting assisted claiming...");
const [errorMessage, setErrorMessage] = useState("");

// Start claiming on mount
useEffect(() => {
if (device) {
startClaiming();
}
}, [device]);

const startClaiming = async () => {
await device.startAssistedClaiming(handleClaimProgress);
};

const handleClaimProgress = (response: ESPClaimResponse) => {
setStatus(response.status);
setProgressMessage(response.message);

if (response.status === ESPClaimStatus.failed && response.error) {
setErrorMessage(response.error);
}
};

// Navigate to WiFi on success (with 1.5 second delay)
useEffect(() => {
if (status === ESPClaimStatus.success) {
const timer = setTimeout(() => {
router.push({
pathname: "/(device)/Wifi",
});
}, 1500);
return () => clearTimeout(timer);
}
}, [status]);

/**
* Handle OK button press (on error)
* Disconnects device before navigating back
*/
const handleOkPress = () => {
if (device) {
// Disconnect device on error
try {
device.disconnect?.();
} catch (e) {
console.error("Error disconnecting:", e);
}
}
router.back();
};
};

Provisioning Flow with Claiming

The complete provisioning flow including claiming:

Flow Order for BLE:

Connect → Check capabilities → POP (if required) → Claiming (if supported) → WiFi → Provision

Flow Order for QR:

Connect → Check capabilities → Set POP (if in QR) → Initialize session → Claiming (if supported) → WiFi → Provision

Flow Order for SoftAP:

Connect → Check capabilities → POP (if required) → WiFi → Provision
(Note: Claiming is NOT supported for SoftAP)

Error Handling

try {
await device.startAssistedClaiming(handleClaimProgress);
} catch (error) {
setStatus(ESPClaimStatus.failed);
setErrorMessage(
(error as Error).message || "Claiming failed"
);
}

SDK Documentation

For more details, see the ESPDevice.startAssistedClaiming SDK documentation.

On this page