# 告警通知和设备控制 (/zh-CN/solutions/smart-doorbell/reference/advanced-feature)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

本文介绍如何实现智能门铃场景的进阶功能：

- 告警通知：以人形检测为例。当智能门铃检测识别到有人后，会主动推送告警事件至应用端。
- 设备控制：客户端可以调节设备音量等。

## 告警通知 [#告警通知]

设备端检测到预定义的异常事件后，会将事件通知推送到客户端。如果客户端处于后台运行或关闭状态，你还需要集成离线推送服务，确保通知可以及时到达。你可以参考[第三方推送集成](https://doc.easemob.com/document/android/push/push_overview.html)集成环信的离线推送服务。

## 设备控制 [#设备控制]

设备控制通过设备端与客户端之间的信令交互实现。设备端使用 RTSA Lite SDK 内置的 RTM 接口收发信令，无需额外集成独立 RTM SDK；客户端集成 RTM SDK，向设备端发送控制指令并接收响应。

:::info[客户端示例版本]
以下客户端代码示例使用 RTM 1.x 接口。使用 RTM 2 时，请参考[迁移指南](/zh-CN/realtime-media/rtm/reference/migration-guide)调整初始化、登录和消息收发代码，并确认所选客户端 RTM 版本与设备端 RTSA 版本的互通支持。
:::

### 1. 生成 RTM Token [#1-生成-rtm-token]

使用信令接口需要通过 RTM Token 鉴权。你需要自行在业务后台生成 [RTM Token](/zh-CN/realtime-media/rtm/build/authentication-and-connection/token-generation)。业务后台会分配此次通话的主叫用户 ID、被叫用户 ID 及对应的 RTM Token，并分别下发至客户端和设备端。这些参数都是后续客户端使用的 RTM SDK 和设备端使用的 RTSA Lite SDK 登录 RTM 系统所需要的。

### 2. 设备端和客户端登录 RTM 系统 [#2-设备端和客户端登录-rtm-系统]

设备端通过 RTSA Lite SDK 的 `agora_rtm_login` 方法登录 RTM 系统。

```c
rval = agora_rtm_login(p_config->p_rtm_uid, p_config->p_token, &rtm_handler);
if (rval < 0) {
    printf("login rtm failed\n");
    goto EXIT;
}
```

客户端通过 RTM SDK 的 `login` 方法登录 RTM 系统。

```java
mRtmClient.login(token, uid, new ResultCallback<Void>() {
    @Override
    public void onSuccess(Void responseInfo) {
    }

    @Override
    public void onFailure(ErrorInfo errorInfo) {
        CharSequence text = "User: " + uid + " failed to log in to the RTM system!" + errorInfo.toString();
        int duration = Toast.LENGTH_SHORT;
        runOnUiThread(new Runnable() {
            public void run() {
                Toast toast = Toast.makeText(getApplicationContext(), text, duration);
                toast.show();
            }
        });

    }
});
```

### 3. 设备端发送和接收信令 [#3-设备端发送和接收信令]

设备端通过 RTSA Lite SDK 的 `agora_rtm_send_data` 方法和 `on_rtm_data` 回调发送和接收信令。

:::caution[注意]
只有收到 `RTM_EVENT_TYPE_LOGIN` 且 `ERR_RTM_OK` 的成功回调后，才能调用 `agora_rtm_send_data`。
:::

```c
// 接收你登录的 RTM 系统中对端发送信令
static void __on_rtm_data(const char *user_id, const void *data, size_t data_len,
                          rtm_message_type_e msg_type, const char *custom_type) {
    app_t *p_app = app_get_instance();
    app_config_t *p_config = &p_app->config;

    if (msg_type == RTM_MESSAGE_TYPE_STRING) {
        if (p_config->rtm_role == 3) {
            printf("Receive data[%.*s] length[%zu]\n", (int)data_len, (const char *)data, data_len);
        } else {
            printf("data_callback %s data[], length[%zu]\n", user_id, data_len);
        }
    } else {
        printf("data_callback %s data[], length[%zu]\n", user_id, data_len);
    }

    if (p_app->config.rtm_recv_dump_flag && p_app->rtm_recv_file_fd != INVALID_FD) {
        if (write(p_app->rtm_recv_file_fd, data, data_len) != data_len) {
            printf("write error\n");
            return;
        }
    }
}
// 监听本地用户状态
static void __on_rtm_event(const char *user_id, rtm_event_type_e event_type, rtm_err_code_e err_code) {
    LOGD("%s event type[%d], err code[%d]", user_id, event_type, err_code);
    if (event_type == RTM_EVENT_TYPE_LOGIN && err_code == ERR_RTM_OK) {
        app_t *p_app = app_get_instance();
        p_app->b_rtm_login_success_flag = 1;
    }
}
// 监听本地信令发送结果
static void __on_rtm_send_data_result(const char *rtm_uid, uint32_t msg_id, rtm_msg_state_e state) {
    LOGD("user [%s] msg id[%u], state[%d]", rtm_uid, msg_id, state);
}
// 向指定 RTM 用户发送信令
app_t *p_app = app_get_instance();
static uint32_t message_id = 0;
const char buffer[] = "device control";
const size_t buffer_len = sizeof(buffer) - 1;
if (!p_app->b_rtm_login_success_flag) {
    LOGE("%s RTM login not successful, skip sending data", TAG_API);
    goto EXIT;
}
rval = agora_rtm_send_data(p_app->config.p_peer_uid, buffer, buffer_len, ++message_id,
                           RTM_MESSAGE_TYPE_STRING, NULL);
if (rval < 0) {
    LOGE("%s send data failed, rval=%d", TAG_API, rval);
    goto EXIT;
}
```

### 4. 客户端发送和接收信令 [#4-客户端发送和接收信令]

客户端通过 RTM SDK 的 `sendMessageToPeer` 方法和 `onMessageReceived` 回调发送和接收信令。

```java
// 发送信令
mRtmClient.sendMessageToPeer(peer_id, message, option, new ResultCallback<Void>() {

    @Override
    public void onSuccess(Void aVoid) {
        String text = "Message sent from " + uid + " To " + peer_id + " ： " + message.getText() + "\n";
        writeToMessageHistory(text);
    }

    @Override
    public void onFailure(ErrorInfo errorInfo) {
        String text = "Message fails to send from " + uid + " To " + peer_id + " Error ： " + errorInfo + "\n";
        writeToMessageHistory(text);

    }
});

// 接收信令
@Override
public void onMessageReceived(RtmMessage rtmMessage, String peerId) {
    String text = "Message received from " + peerId + " Message: " + rtmMessage.getText() + "\n";
    writeToMessageHistory(text);
}
```

## 开发注意事项 [#开发注意事项]

在开发过程中，你需要注意如下事项：

- RTC SDK、RTM SDK、RTSA Lite SDK 和你的 Token 生成器必须使用相同的 [App ID](/zh-CN/introduction/glossary#appid)，否则无法互通。
- RTC SDK 和 RTSA Lite SDK 的音视频接口使用 [RTC Token](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication) 进行鉴权，RTM SDK 和 RTSA Lite SDK 的云信令接口使用 [RTM Token](/zh-CN/realtime-media/rtm/build/authentication-and-connection/token-generation) 进行鉴权。两者生成方式不同且不可混用。
