目录
- 关店后 App 会亮,值班通道为什么是聋的
- 这篇会用到什么
- 为什么轮询、只靠官方 App、一上来写原生都走不通
- 动手:从开应用到值班群响第一声
- 群响了之后:夜间窗口和抓图取证
- 联调会踩的坑
- 真正省下的不是多买一台相机
关店后 App 会亮,值班通道为什么是聋的
无人便利店、社区小店,镜头早就挂了。白天店里有人,晚上把乐橙 App 通知打开,以为关店等于布防。第二天翻回放才发现:货架前走过一个人,红点停在私人手机里,值班群是静的。
白天客流本身就会出人形。全天往群里灌,值守的人会关通知;关掉之后,真正该响的夜里也没了。缺的不是再买一台相机,而是把人形检测接到自己的业务通道,并且只在关店后叫醒人。
开放平台这边,人形事件的类型值是 human,落在 alarm 大类里。这篇用现行 OpenAPI 的 setMessageCallback 把告警推到你的 HTTPS,再用 setDeviceCameraStatus 把通道级 aiHuman 打开。
读完按这个顺序交差:
- 在开放平台创建应用,把门店设备绑进开发者资产。
- 签名自测通过,能拿到
accessToken。 listDeviceDetailsByPage能看到版本号,能力集里有AiHuman,设备online。setDeviceCameraStatus打开aiHuman,modifyDeviceAlarmStatus开动检。setMessageCallback订alarm,getMessageCallback读回同一条地址。- 人在镜头前走一圈,桥接日志出现
human,值班群进一条。
无人店关店后的闯入:镜头会出
human,App 有红点,值班群没有人。本文用现行 OpenAPI 的setMessageCallback订alarm,用setDeviceCameraStatus开aiHuman,带可运行 Node.js,验到群里响一条为止。
这篇会用到什么
- 订
alarm才能收到human。human是人形检测报警,videoMotion是动态检测,白天客流别和夜里闯入混成同一条规则。 - 通道能力集要有
AiHuman,再用setDeviceCameraStatus把enableType写成aiHuman(首字母小写)。备注写明:仅支持设备详细信息中accessType=PaaS的设备。 - 动检总开关仍要开:
modifyDeviceAlarmStatus的enable=true。人形使能挂在动检之上,只开一边,现场走一圈也没事件。 - 回调必须先回 HTTP 200。多次无响应,平台停推,夜里店是聋的。
- 一个开发者账号一条
callbackUrl。多店按推送体里的did找群,不要每家店起一套服务。 - 抓图用
getAlarmMessageById换picurlArray。普通报警体里不一定带图,收到要尽快转存。
你负责绑设备和值班群,开放平台负责把 human 推到你的 HTTPS。
门店 IPC
├─ 人形智能 → setDeviceCameraStatus / aiHuman
└─ 动检开关 → modifyDeviceAlarmStatus
│
▼
alarm → msgType = human
│
▼
你的 HTTPS(立刻 200)
│
▼
按 did 找到门店 → 值班群
为什么轮询、只靠官方 App、一上来写原生都走不通
轮询报警列表适合对账,不适合当值守主路径。扫得再勤也补不上推送那几秒。关店后的三分钟,比第二天回放值钱。
只靠官方 App,红点停在私人手机里。人在睡觉、在别的店、通知被系统折叠,值班群都不知道有人进门。白天客流把通知刷到「关闭」,夜里更没人看。
一上来写原生客户端没必要。值守已经在企微或钉钉,缺的是桥,不是第三个看监控的 App。
动手:从开应用到值班群响第一声
先不要做夜间计划。白天走一圈,群里能进一条 human,再谈关店窗口。
第一步:创建应用,填齐门店台账
打开 乐橙开放平台 注册并创建应用。控制台能看到 appId / appSecret 再往下做。把门店枪机绑进这个开发者资产,不要停在私人 App 号下。准备一个外网能 POST 进来的 HTTPS(联调用穿透,生产用正式证书)。
IMOU_APP_ID=lcdxxxxxxxxx
IMOU_APP_SECRET=你的密钥
CALLBACK_URL=https://bridge.example.com/imou/callback
DEVICE_STORE=门店序列号
CHANNEL_ID=0
HOOK_STORE=值班群机器人地址
成功:控制台能看到应用,设备在资产里显示在线。
兜底:设备还在私人 App 下,先做绑定,再谈回调。
第二步:签名壳,先和文档案例对齐
请求走 https://openapi.lechange.cn/openapi/{method},壳子是 system + params + id。签名按开发规范把 time:{time},nonce:{nonce},appSecret:{appSecret} 拼成原始串,再做 MD5 小写 32 位。time 与服务器误差不能超过 5 分钟,否则 SN1002;nonce 5 分钟内不能重复,否则 SN1005。
// openapi-client.js
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const OPENAPI_BASE = 'https://openapi.lechange.cn/openapi';
function calcSign(time, nonce, appSecret) {
const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
return crypto.createHash('md5').update(raw, 'utf8').digest('hex');
}
async function callOpenApi(method, appId, appSecret, params = {}) {
const time = Math.floor(Date.now() / 1000);
const nonce = uuidv4();
const body = {
system: { ver: '1.0', appId, time, nonce, sign: calcSign(time, nonce, appSecret) },
id: uuidv4(),
params,
};
const res = await fetch(`${OPENAPI_BASE}/${method}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const json = await res.json();
if (!json.result || json.result.code !== '0') {
const msg = json.result ? `${json.result.code} ${json.result.msg}` : JSON.stringify(json);
throw new Error(`OpenAPI ${method} failed: ${msg}`);
}
return json.result.data;
}
module.exports = { callOpenApi, calcSign };
成功:calcSign(1706511734, 'f5a1ae2d-c09c-4d39-a744-83a5c2c653c2', 'test123456789test123456789') 得到 fd37b62889e4757c58b8f3bf05fb9976;再调 accessToken 能打印 token,expireTime 单位是秒。
兜底:对不上标准案例,先别查业务接口,多半是字符串拼错。TK1002 再刷 token,不要每个请求都申。管理员 token 有效期 3 天,超过 2 天再申会拿到新的,新旧各自能用。
第三步:列表里先确认这台机能出人形
查设备用 listDeviceDetailsByPage。看四样:deviceStatus 是不是 online,deviceVersion 有没有值,accessType 是不是 PaaS,deviceAbility / channelAbility 里有没有 AiHuman。能力开关写法见设备能力开关:列表里是首字母大写,后面使能接口传小写。
token=上一步拿到的 accessToken
page=1
pageSize=10
source=bindAndShare
成功:终端能打印出门店序列号、deviceVersion、deviceStatus=online,能力串里出现 AiHuman。
兜底:列表为空,设备绑在私人号下。没有 AiHuman,不要硬开人形,换支持该能力的机型,或先只做动检(videoMotion)——那是另一条规则,这篇不凑合。accessType 不是 PaaS 时,使能接口以现行文档备注为准,不要照抄下一步。
第四步:开人形使能,先让镜头会报
PaaS 设备用 setDeviceCameraStatus 开通道级 aiHuman。动检总开关用 modifyDeviceAlarmStatus,enable=true。两个都开,现场走一圈才有机会出 human。货架不会自己逛街,这一步只负责让镜头开口。
# 开人形(通道级)
token=管理员 accessToken
deviceId=门店序列号
channelId=0
enableType=aiHuman
enable=true
# 开动检
token=管理员 accessToken
deviceId=门店序列号
channelId=0
enable=true
成功:两个接口都回 code=0。
兜底:使能失败先回头看能力集和 accessType,不要当成签名错了。这一步还不会进群,群响是第六步的事。
第五步:登记回调,只订 alarm
这篇第一闭环必须跑通的接口是 setMessageCallback。status=on 时 callbackUrl 必填,必须公网可达。callbackFlag 先写 alarm,人形在这个大类里。联调 basePush 用 "2",以桥接日志为准,少和 App 推送抢排查。
// set-callback.js
require('dotenv').config();
const { callOpenApi } = require('./openapi-client');
(async () => {
const token = (
await callOpenApi('accessToken', process.env.IMOU_APP_ID, process.env.IMOU_APP_SECRET, {})
).accessToken;
await callOpenApi('setMessageCallback', process.env.IMOU_APP_ID, process.env.IMOU_APP_SECRET, {
token,
status: 'on',
callbackUrl: process.env.CALLBACK_URL,
callbackFlag: 'alarm',
basePush: '2',
});
const current = await callOpenApi(
'getMessageCallback',
process.env.IMOU_APP_ID,
process.env.IMOU_APP_SECRET,
{ token }
);
console.log(current);
})();
成功:getMessageCallback 读回的 status=on、callbackUrl、callbackFlag=alarm 与上面填写项一致。
兜底:localhost 登记成功也收不到。先让公网 curl -X POST 打到自己的路径。漏写 alarm,后面走一圈永远安静。
第六步:接收端先回 200,人形进值班群
同步路径零阻塞。文档写明:多次不回 200,平台停推。普通报警体见事件消息格式定义:id、did、cid、msgType、time。第一闭环只认 human,白天走一圈也要进群,用来证明链路是通的。
// bridge-server.js
require('dotenv').config();
const express = require('express');
const STORES = {
[process.env.DEVICE_STORE]: { name: '本店', hook: process.env.HOOK_STORE },
};
const seen = new Set();
function storeOf(did) {
return STORES[did] || { name: did, hook: process.env.HOOK_STORE };
}
async function toDutyGroup(ev) {
if (ev.msgType !== 'human') {
console.log('[忽略]', ev.msgType, ev.did);
return;
}
const key = String(ev.id);
if (seen.has(key)) return;
seen.add(key);
const { name, hook } = storeOf(ev.did);
const when = new Date(ev.time * 1000).toLocaleString('zh-CN', { hour12: false });
const text = `【无人店闯入】${name}\nmsgType:${ev.msgType}\n设备:${ev.did} 通道:${ev.cid}\n${when}\n先看门口,再回群`;
console.log('[值班群]', text, hook ? '→ hook' : '→ 仅日志');
// 这里按你们现有机器人协议 POST hook,本文不绑具体厂商
}
const app = express();
app.use(express.json({ limit: '1mb' }));
app.post('/imou/callback', (req, res) => {
res.status(200).json({ code: '0', msg: 'ok' });
const body = req.body || {};
const ev = body.msgType ? body : body.data || body;
setImmediate(() => toDutyGroup(ev).catch((e) => console.error(e)));
});
app.get('/health', (_req, res) => res.status(200).send('ok'));
app.listen(process.env.PORT || 8080);
成功:/health 返回 ok;人在镜头前走一圈,日志出现 human,值班群进一条。这就是第一闭环。
兜底:App 有告警、服务安静,回到第五步看地址和 callbackFlag。日志有 videoMotion 没有 human,回头看第四步 aiHuman 是否真开了。把短信、查库写进同步路径,过不了这一步——先 200,再 setImmediate。
群响了之后:夜间窗口和抓图取证
最小闭环已经成立,才加第二层。仍用同一套现行 OpenAPI。
关店后货架过道不该有人,human 这时才值钱。白天客流继续出事件,不要再进值班群。两条线一起做:
设备侧用 modifyDeviceAlarmPlan 把动检计划收成夜间。beginTime / endTime 是当天的 HH:mm:ss,跨午夜拆成两段,每天两行。改完用 deviceAlarmPlan 读回,看每条规则的 enable 和起止时间。
token=管理员 accessToken
deviceId=门店序列号
channelId=0
rules=
period=Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
beginTime=22:00:00
endTime=23:59:59
period=Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
beginTime=00:00:00
endTime=08:00:00
接收侧再加一层营业时间窗,按你们门店的关店钟点滤。设备计划漏配时,群不会被白天客流刷死。时间以推送体里的 time(Unix 秒)换成本地小时。
取证走 getAlarmMessageById。把回调里的 id 当成 alarmId,带上 deviceId、channelId、msgType=human。返回里的 picurlArray 尽快落到自己的对象存储。aiTag / aiCopyWriting 只在通道开了智见云存储或 AI 智见套餐时才有值,没有就当普通抓图,不要当接口坏了。
夜里镜头掉线也该叫醒人。第二层再把 callbackFlag 改成 alarm,deviceStatus,认 offline。上下线体的 id 固定是 -1,不能拿去查告警详情。生产把内存 seen 换成 Redis。派单、SLA、多店值班表是下一篇的事,这篇停在「关店后群里有该去的人看到」。
联调会踩的坑
| 现象 | 多半是什么 | 先做什么 |
|---|---|---|
| 登记成功但一条都没有 | URL 不是公网 / 证书 / 路径 404 | curl -X POST 打自己的回调 |
| 收过一阵突然没了 | 同步路径堵了,多次没回 200,平台停推 | 修好后重新 setMessageCallback |
| App 有告警、桥接安静 | 没订 alarm,或设备不在这个开发者账号下 | getMessageCallback 读回;列表核对 deviceId |
只有 videoMotion,没有 human | 没开 aiHuman,或机型没有 AiHuman | 查能力集,再跑第四步 |
| 使能接口失败 | accessType 不是 PaaS,或通道号没传 | 列表看 accessType 和 channelId |
| 白天群被客流刷爆 | 第一闭环规则原样上了生产 | 上夜间计划,接收侧再滤营业时间 |
拿上下线 id 去查抓图 | 文档写明固定为 -1 | 只用 human 的 id 调 getAlarmMessageById |
真正省下的不是多买一台相机
这篇省下的不是再挂一路镜头,而是值守不用第二天翻回放,才在群里问「昨晚谁进过门」。
只做店内大屏预览、不接值班群的,不必在这里把回调搭起来。有人值守、出事了有人盯 App 的柜台店,也没必要上这一套。
appSecret、机器人地址只放环境变量。公网回调建议加自有校验。抓图按门店留存周期转存,不要把顾客人脸长期堆在聊天记录里。接口以现行文档为准。