刻锐智能的WIFI门铃(WM5221)发生按下事件后,是先发向涂鸦服务器。自个app若想得到这些事件,需要向涂鸦服务器获取。具体到读取操作,很容易想到两种。第一是订阅,流程是自个app向涂鸦发出个订阅请求,有按下事件发生了,你作为一个订阅客户端会立即收到这事件。第二是轮询,流程是自个app不作为订阅客户端,而是每隔一段时间向涂鸦发一个http请求,请求中指定某个时间段,涂鸦服务器则返回http应答,应答中包含指定时间段内产生的按下事件。以效率、即时性比较,第一种要优于第二种,但本文只说第二种,即以着轮询方式向涂鸦获取按下事件。
一、涂鸦平台上操作
为让开发者能在自个app获取事件,涂鸦提供个叫“云开发”方案。进入链接可阅读详细文档,这里只说操作中几个注意点。
1.1 购买“IoT Core 连接服务”

必须购买“IoT Core 连接服务”,有三种类型。对只是测试,会购买“体验版(仅供测试)”。要注意,这个版本使用期只有一个月。一个月到期后,之前那账号就不能再购买该体验版了。但可以延期,不过延期时,到期到有效中间似乎有1天多时间不能用(这个须更多测试)。另外两种类型,价格对普通开发者来说,不一是般的高。所以要测试,如果不想延期的,那一个月内必须出结果,否则不要过早购买体验版。
1.2 消息订阅

这是个人账号下的“消息订阅”界面。打勾底部的“测试通道”。遇到过一次在自个app收不到门铃事件,然后在这里打勾“测试通道”,就可以了。
1.3 向云项目增加设备
创建项目后,首先要向项目增加你要接收事件的门铃设备。文档有几处写的是下载个“智能行业 app”,用那app添加。试下来没成功,后来向涂鸦发工单,回答是下载“智能生活 APP”,然后将设备绑定到该APP中,再通过平台“云开发”-“设备”-“关联APP账号”,通过扫描授权进行绑定,具体可查看此文档:方式三:关联涂鸦 App 账号。

以上操作要用到界面中“关联App账号”按钮。但发现那个按钮是灰的。

解决办法是参考上图位置“关闭引导模式”,“关联App账号”就可以按了。
1.4 服务API
增加设备后,就要尽快测试接收门铃按下事件。为立即得到这结果,可通过涂鸦平台提供的“服务API”。
点击“服务API”——“IoT Core连接服务”那一行的“去调式”。进入“API 调试界面”后,选“设备管理”——“获取设备状态上报日志”。

- device_id:你要获取按下事件门铃设备ID。
- codes:填写“alarm_msg”。
- start_time、end_time:填写事件发生的时间段。单位毫秒。
填对相应参数,那边按下门铃,这里“发起调用”,右侧响应结果就该能看到发生该按下事件了。
二、Postman
虽然最终目标是要在自个app从涂鸦得到门铃事件,但还是建议先用Postman,调通在Postman收到事件。原因么,在涂鸦平台操作时,好多操作存在黑箱,像如何生成请求需要的“sign”字段值。通过Postman,就可看到如何生成“sign”值,虽然那里用脚本语言写,但很容易能就“翻译”成C++代码。
2.1 导入接口包和环境包,缺一不可
阅读“设置Postman环境并调用API”,下载并导入接口包和环境包。

导入两个包。在“Environments”界面修改参数配置,记得“Save”。单击“Set active”,确认右上角“No Environments”处变更为“云云对接API环境”,否则会报类似以下错误。
There was an error in evaluating the Pre-request Script:TypeError: Cannot read properties of undefined (reading 'sigBytes')
2.2 增加“获取设备状态上报日志”请求
新建的请求要从已有的请求复制一个,依着涂鸦“服务API”中的“获取设备状态上报日志”,在那基础上更改相关参数,改出此条请求。
如果“新建”一个,那单击“Send”后,得到的应答中会出现类似错误:“msg: sign invalid”、“msg: request time is invalid”。
2.3 查看如何生成“sign”字段值
上面为什么必须用复制,不能用新建?——请求中需要填写一个“sign”字段,它来自环境变量“easy sign”。这是一个每次“Send”都会变动的值,而接口包中写的每条请求,在“Send”时会额外执行一段如何得到该的值代码。“复制”使得这段代码被新请求继承了,“新建”则由于没有那段代码,得不到正确的“easy sign”,导致发出的“sign”字段总会出错。
打开“云云对接API.postman_collection.json”接口包。定位到“获取设备信息”请求。

在按下Postman“Send”按钮后,会执行图中高亮行所在函数。该函数结尾会调用calcSign()得到sign,再把sign赋值到环境变量“easy_sign”。上图也显示了calcSign函数体,主要操作是调用CryptoJS.HmacSHA256,即使用SHA-256生成哈希值的HMAC算法,由明文“str”和密钥“secret”生成一个密文。明文“str”则和clientId、 accessToken、timestamp、nonce、signStr有关。
三、aplt.leaogr.basic小程序中的iot驱动
目前iot驱动可“同时”接两个涂鸦门铃事件,并上报到launcher。device_id1_是当中一个门铃的设备ID,device_id2_是另外一个。
3.1 (worker线程)start_iot
launcher加载basic的iot驱动后,会新建worker线程,线程函数是start_iot。
{libleagor_basic}/iot/leagor_iot.cpp ------ void tleagor_iot::start_iot(bool& exit) { while (!exit) { if (access_token_.empty()) { xmit_tuya(null_str, cmd_get_access_token); } if (!access_token_.empty() && next_get_doorbell_event_ticks_ != 0 && SDL_GetTicks() >= next_get_doorbell_event_ticks_) { xmit_tuya(device_id1_, cmd_get_doorbell_event); if (!access_token_.empty()) { // below cmd_get_doorbell_event maybe fail. // for example: Your subscription to cloud development plan has expired. xmit_tuya(device_id2_, cmd_get_doorbell_event); } next_get_doorbell_event_ticks_ = SDL_GetTicks() + 8 * 1000; } } }
iot驱动就执行两种请求,一是简单模式获取access_token(cmd_get_access_token),二是获取设备状态上报日志(cmd_get_doorbell_event)。在worker线程,iot驱动发现token是空,就先得到一个非空access_token。有了token后,隔8秒轮询涂鸦服务器。
3.2 (worker线程)did_post_tuya收到按下事件
xmit_tuya复杂向涂鸦发送请求,并同步等待应答。在这当中,收到应答时会调用did_post_tuya。
{libleagor_basic}/iot/leagor_iot.cpp ------ bool tleagor_iot::did_post_tuya(net::thttp_api& net_api, int status, const std::string& data_received, const std::string& device_id, int cmd) ... VALIDATE(cmd == cmd_get_doorbell_event, null_str); Json::Value& json_logs = result["logs"]; std::set<int64_t> logs; if (json_logs.isArray()) { int log_count = json_logs.size(); for (int at = 0; at < log_count; at ++) { const Json::Value& json_log = json_logs[at]; logs.insert(json_log["event_time"].asInt64()); } } if (!logs.empty()) { threading::lock lock(event_result_mutex_); event_result_dirty_ = true; for (std::set<int64_t>::const_iterator it = logs.begin(); it != logs.end(); ++ it) { int64_t t = *it; event_result_.insert(tiot_event(t, iot_src_doorbell, iot_evt_pressdown, device_id, "misc/doorbell.png"));
event_result_类型是std::set,insert时自动按发生时间(t)进行排序。最晚发生的按下事件放在第一条。
} // @logs is sorted in ascending order base @t. not event_result_. last_start_time_ = *logs.rbegin() + 1;
last_start_time_是下次请求时的“start_time”。logs和event_result_不一样,它按的是t升序,即最晚发生的按下事件放在最后一条。last_start_time_值是最后一条的“t”加1。
} ... }
以上代码是如何处理“获取设备状态上报日志”应答。一旦发现该门铃有按下事件,即log_count不是零,就把事件放到变量event_result_。event_result_须要和主线程同步,修改它时须要加event_result_mutex_锁。
3.3 (主线程)把事件上报到launcher
launcher在主线程时间片会不断调用tleagor_iot::slice,后者发现有收到事件了,执行上报。
{libleagor_basic}/iot/leagor_iot.cpp ------ void tleagor_iot::slice() { ... std::set<aplt::tiot_event> result; if (event_result_dirty_) { threading::lock lock(event_result_mutex_); result = event_result_; event_result_dirty_ = false; event_result_.clear();
这时是在主线程执行,访问event_result_需加event_result_mutex_锁。
} if (result.empty()) { return; } subscriber.iot_did_events(result);
调用subscriber的iot_did_events方法上报新收到的按下事件。
}
在launcher的iod_did_events,会做相应处理。处理包括向“中心”增加日志,修改bg_task中的iot_devices_、更新kLink界面,以及可能的触发相应任务。
四、设备启动时时间,需要和真实时间偏差不多
url = "/v2.0/cloud/thing/70482087ec94cb835c89/report-logs?codes=alarm_msg&end_time=1721146197336&size=20&start_time=1721090943613"
这是查询事件时要使用的部分url,[start_time, end_time]指示了要得到哪段时间内的事件。这两个时间往往会和设备当前时间有关。很显然,设备时间和真实时间(涂鸭云时间)偏差很大,那这命令返回内容无疑会出错。设备制造商会保证设备运行时时间正确,但不要忘了,在启动阶段也要尽可能正确。
一旦主板没带RTC电池,android系统启动时间会不准,但因为设置了“自动设置时间”,很快,等设备联上网后,会读到一个正确时间,进而让自个时间也准了。但从启动、到联网读到时间,这中间有个过程。如果在这段时间,你的app用过time(nullptr)读时间,并和start_time、end_time有关,像直接做为start_time值,那第一条查询事件命令极可能得到错误结果。
2021-01-01 20:00:00.804 0-0/? I/: Booting Linux on physical CPU 0x0000000000 [0x412fd050] (android设备启动,由于没有RTC电池,虽然真实时间是2024-08-18 15:23,这里主观给出了个2021-01-01 20点) ... 2021-01-01 20:00:07.928 592-592/system_process I/AlarmManager: Current time only 1609502407928, advancing to build time 1723689741000 2024-08-15 10:42:21.004 592-592/system_process V/SystemServerTiming: StartAlarmManagerService took to complete: 6ms (这两条日志是挨着的,但修改了设备时间,设备时间一下到了2024-08-15,但这时间还是错的。) ....... 2024-08-15 10:42:25.669 1784-1784/com.kos.launcher V/SDL: Device: rk3588s_lubancat_4_v1_hdmi (在这个时刻,自个app(com.kos.launcher)启动,因为2024-08-15这时间是错的,取这个时间作为第一条查询事件的start_time,这个结果还出错) ........ 2024-08-15 10:42:29.226 592-609/system_process D/AlarmManagerService: Setting time of day to sec=1723965794 2024-08-18 15:23:14.774 592-669/system_process E/LazyAlarmStore: Removed TIME_TICK alarm (这两条日志是挨着的,从这里开始,时间正确)
2024-08-15 10:42:21,自个app(com.kos.launcher)启动,因为2024-08-15这时间是错的,取这个时间为第一条查询事件的start_time,返回将出错。由于这个时间比真实时间差不多早3天,在这3天极可能会按发生按下门铃事件,导致“应该过时”的事年也被返回了。
解决办法一般就是给主板加RTC电池。