---
title: SGM 智能群组模型使用指南 - 兼容性处理与最佳实践
summary: SGM 智能群组模型使用指南 - 兼容性处理与最佳实践, 本文介绍了如何在面板小程序中使用 SGM（Smart Group Model）能力，包括兼容性处理、最佳实践等。通过本文的介绍，开发者可以快速掌握如何在面板小程序中使用 SGM 能力，实现智能群组的管理和控制。
questions:
  - SGM 智能群组模型与 SDM 智能设备模型在 API 兼容性上有什么关系？
  - SGM 不支持哪些通用能力（tapToRun 一键执行、alarm 告警推送）？
  - useBuiltInAlarm 和 useCustomAlarm 在群组环境下如何做兼容处理（不能放在条件语句中）？
  - useDevice 在群组环境下返回的是 GroupInfo 而非 DeviceInfo，如何安全访问 groupId 和 devId？
  - 群组环境下的在线状态判断与单设备有何不同（deviceList.some vs isCloudOnline）？
  - 群组操作可能触发对多个设备的并发请求，如何实现带指数退避的重试机制（withRetry）？
  - 蓝牙本地群组可能存在丢包现象，如何通过 failedDevices 检测失败设备并重试？
  - useProps、useActions、useStructuredProps、useStructuredActions 在 SGM 下是否可直接使用？
  - 如何通过 getLaunchOptionsSync().query.groupId 判断当前是否为群组环境？
  - 群组环境下 API 调用不可用时（如 tapToRun），如何通过可选链和 try-catch 做降级处理？
---

# 使用

**智能群组模型（SGM）** 是在群组环境下运行的对 **智能设备模型（SDM）** 的扩展。设计目标是尽量保证与 SDM 的 API 兼容，从而减少业务层的改动。大多数 SDM 的 Hooks 与 API 在 SGM 下可直接使用，但部分能力在群组场景下无法实现或语义不同，需要开发者注意并进行兼容处理。

<Alert type="warning">
  智能群组模型目前暂不支持以下能力：
  - 通用能力中的一键执行（tapToRun）
  - 通用能力中的告警推送（alarm）
</Alert>

在开始使用智能群组模型之前，建议先阅读 [智能设备的使用文档](/cn/miniapp/solution-panel/ability/common/sdm/usage)，SDM 的大部分示例与模式在群组场景下仍然适用：

## 差异说明

总体上 SGM 与 SDM 保持兼容，但在群组场景存在若干不可对齐的能力与行为差异，需在业务层予以关注：

- **功能限制**：通用能力中的一键执行（tapToRun）与告警推送（alarm）在群组场景不可用，因此相关 Hooks（如 useBuiltInAlarm、useCustomAlarm）也不可用或返回空结果。
- **语义/行为差异**：群组的设备信息结构、状态同步在细节上可能与单设备不同。
- **性能与并发**：群组操作通常会触发对多个设备的并发请求，针对不同设备协议类型，需注意节流与容错策略。

## 最佳实践

下面给出兼容性处理建议、Hooks 兼容实践，便于确保在群组环境下的稳定运行：

### 功能限制的兼容处理

大部分 Hooks（例如 `useProps`、`useActions`、`useStructuredProps`、`useStructuredActions`）在 SGM 下仍然可用。针对受限能力的 API 或 Hooks，建议采用以下兼容策略：

<Callout type="warning" emoji="⚠️">
  重要：根据 [React Hooks 规则](https://react.dev/reference/rules/rules-of-hooks)，Hooks 不能在条件语句中调用。对于可能不可用的 Hooks（如 useBuiltInAlarm、useCustomAlarm），应始终调用但处理返回的数据兼容性。
</Callout>

**正确的 Hooks 兼容方式**：

```typescript
import { getLaunchOptionsSync } from '@ray-js/ray';
import { useBuiltInAlarm, useCustomAlarm } from '@ray-js/panel-sdk';

const isGroupDevice = !!getLaunchOptionsSync()?.query?.groupId;

// ✅ 正确：始终调用 Hooks，处理返回数据的兼容性
const builtInAlarmData = useBuiltInAlarm();
const customAlarmData = useCustomAlarm();

// 在群组环境下，这些 Hooks 可能返回空或无效数据，需要做兼容处理
const safeBuiltInAlarms = isGroupDevice ? [] : (builtInAlarmData?.alarms || []);
const safeCustomAlarms = isGroupDevice ? [] : (customAlarmData?.alarms || []);
```

**API 调用的兼容处理**：

```typescript
import { useActions } from '@ray-js/panel-sdk';

const actions = useActions();

// 对于可能不存在的 API 方法，使用容错调用
const handleTapToRunTrigger = async () => {
  try {
    await actions.tapToRun?.trigger?.();
  } catch (e) {
    console.warn('一键执行功能在群组环境下不可用:', e);
    // 提供降级方案或用户提示
  }
};
```

### 语义/行为差异的兼容处理

群组环境下的设备信息、状态同步可能与单设备有所不同，特别需要注意 `useDevice` 在群组环境下返回的是 `GroupInfo` 而非 `DeviceInfo`，因此可能缺少某些设备特有的字段。

```typescript
import { getLaunchOptionsSync } from '@ray-js/ray';
import { useDevice, useProps } from '@ray-js/panel-sdk';

const isGroupDevice = !!getLaunchOptionsSync()?.query?.groupId;

// useDevice 在群组环境下返回 GroupInfo，需要做字段兼容处理
const entityInfo = useDevice(device => device.devInfo);

// ✅ 安全的字段访问方式
const id = isGroupDevice 
  ? entityInfo.groupId // 群组设备
  : entityInfo.devId // 单设备

// ✅ 在线状态判断差异 - 群组的在线状态语义不同
const isOnline = isGroupDevice
  ? entityInfo?.deviceList?.some(dev => dev.isOnline) // 群组：只要有一个设备在线就认为在线
  : entityInfo?.isCloudOnline === true; // 单设备：必须明确在线
```

### 性能与并发的兼容处理

在某些协议（如蓝牙本地群组）下，可能存在丢包现象，需要实现重试机制来保证操作的可靠性：

```typescript
import { getLaunchOptionsSync } from '@ray-js/ray';
import { useActions } from '@ray-js/panel-sdk';

const isGroupDevice = !!getLaunchOptionsSync()?.query?.groupId;
const actions = useActions();

// 重试机制实现
const withRetry = async (actionFn, maxRetries = 3, delay = 1000) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const result = await actionFn();
      
      // 对于群组操作，检查是否所有设备都响应成功
      if (isGroupDevice && result?.failedDevices?.length > 0) {
        console.warn(`第 ${attempt} 次尝试，${result.failedDevices.length} 个设备响应失败`);
        if (attempt === maxRetries) {
          throw new Error(`重试 ${maxRetries} 次后仍有设备失败`);
        }
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      return result;
    } catch (error) {
      console.warn(`第 ${attempt} 次尝试失败:`, error);
      if (attempt === maxRetries) {
        throw error;
      }
      // 指数退避策略
      await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
    }
  }
};

// 使用重试机制的操作示例
const handlePowerToggle = async () => {
  try {
    await withRetry(async () => {
      return await actions.power.toggle();
    });
    console.log('电源状态切换成功');
  } catch (error) {
    console.error('电源状态切换失败:', error);
    // 提供用户友好的错误提示
    showToast('操作失败，请检查设备连接状态');
  }
};
```
