目录
- 车间装了镜头,安监群还是听不见
- 这篇会用到什么
- [为什么轮询、只靠 App、一上来写原生都走不通](#为什么轮询、只靠 App、一上来写原生都走不通 "#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BD%AE%E8%AF%A2%E5%8F%AA%E9%9D%A0-app%E4%B8%80%E4%B8%8A%E6%9D%A5%E5%86%99%E5%8E%9F%E7%94%9F%E9%83%BD%E8%B5%B0%E4%B8%8D%E9%80%9A")
- 动手:从开应用到工装检测出第一条结果
- 工装结果出来之后:把烟火推进值班群
- 联调会踩的坑
- 真正省下的不是再买一台相机
车间装了镜头,安监群还是听不见
老陈管一家金属加工厂的安监信息化。冲压区、焊接工位、危化品库门口都挂着网络摄像机,回放能翻一周。夜班有人摘了安全帽进冲压区,焊接工位起了一小柱烟------第二天巡检才在硬盘里对上画面。乐橙 App 红点停在班长私人手机里,安监值班群是静的。
问题不在「没拍到」。缺的不是再买一台相机,而是把厂规着装和烟火事件接进自己的安监通道:未规范着装先出一条,烟火再单独叫醒人。
现行文档没有单独的「安全帽接口名」。安全帽走行业 AI 的工装检测:把厂规安全帽、反光衣样本注册进人形工装库,对抓图调 workwearDetect,看 detectResult。烟火不走抽烟检测接口,走设备告警大类里已经列出的 smokeAlarm / fireAlarm / heatImagingTemper,用 setMessageCallback 订 alarm 推到 HTTPS。
读完按这个顺序交差:
- 在开放平台创建应用,把车间枪机绑进开发者资产。
- 签名自测通过,能拿到
accessToken。 listDeviceDetailsByPage能看到版本号和在线状态。- 建人形工装库,注册厂规安全帽 / 工装样本。
- 对一张车间图调
workwearDetect,控制台打出detectResult;未规范着装时安监群进一条。 - 第一闭环成立后,再订
alarm,把烟火推进同一套值班群。
金属加工车间:镜头会录,App 有红点,安监值班群没有人。本文用现行 OpenAPI 的
workwearDetect验厂规工装(安全帽注册进工装库),再用setMessageCallback订alarm收smokeAlarm/fireAlarm,带可运行 Node.js,验到群里响一条为止。
这篇会用到什么
只列本篇会碰到的,不是开放平台功能全集。工装接口见工装检测,AI 模块说明见智能AI服务,事件类型见事件消息类型定义。
workwearDetect:上传一张图,识别员工是否规范着装。detectResult=true符合着装,false不符合。安全帽作为厂规工装样本进库,不要另编一个头盔接口。createAiDetectRepository(repositoryType=human)+addAiDetectTarget:建人形工装库,把安全帽、反光衣样本注册进去。检测时把repositoryId传给workwearDetect。- 订
alarm才能收到smokeAlarm(设备烟感)、fireAlarm(消防网关配件火警)、heatImagingTemper(温度报警)。smokingDetect是抽烟检测,不是火情。 - 同时订
deviceStatus。车间掉线出offline,别和烟火捆成同一条规则。 - 能力集有
MsgSW,PaaS 机用setDeviceCameraStatus把enableType写成msgSW,打开报警上报。headerDetect是人头检测,不要当成安全帽开关。 - AI 模块是增值服务。工单或商务没开通试用,工装接口调不通。回调必须先回 HTTP 200,多次无响应平台停推。
你负责绑设备和安监值班群,开放平台负责对图验工装、把烟火推到你的 HTTPS。
text
车间枪机 / 焊接工位
├─ 抓图或告警图 → workwearDetect(+工装库) → detectResult
├─ 烟火 / 热成像 → alarm → smokeAlarm / fireAlarm / heatImagingTemper
└─ 上下线 → deviceStatus → offline / online
│
▼
你的 HTTPS(立刻 200)
│
▼
按 deviceId 找到工段 → 安监值班群
为什么轮询、只靠 App、一上来写原生都走不通
轮询报警列表适合对账,不适合当安监主路径。扫得再勤也补不上推送那几秒,更补不上「这张图合不合格工装」------那是对图检测,不是再扫一遍列表。
只靠官方 App,红点停在班长私人手机里。他在另一个工段、通知被系统折叠,值班群不知道有人裸头进冲压区。白班动检把通知刷到「关闭」,夜班更没人看。
一上来写原生客户端没必要。安监已经在企业微信或钉钉,缺的是桥,不是第三个看监控的 App。厂门口也没法让人把镜头擦干净再重新扫码。
动手:从开应用到工装检测出第一条结果
先不要做烟火回调和回放取证。对一张车间图跑通 workwearDetect,未规范着装能进群,再谈第二层。不要在车间点火验收。
第一步:创建应用,填齐车间台账
打开 乐橙开放平台 注册并创建应用。控制台能看到 appId / appSecret 再往下做。把冲压区、焊接工位、危化品库门口的枪机绑进这个开发者资产,不要停在私人 App 号下。绑定用 bindDevice,code 是机身 8 位安全码或设备密码。准备一张能公网访问的车间抓图 URL(联调用对象存储,不要用内网路径)。AI 接口先走工单申请试用。
text
IMOU_APP_ID=lcdxxxxxxxxx
IMOU_APP_SECRET=你的密钥
DEVICE_PRESS=冲压区序列号
DEVICE_WELD=焊接工位序列号
CHANNEL_ID=0
WORKWEAR_REPO_NAME=厂规工装库
HELMET_SAMPLE_URL=https://img.example.com/helmet-ok.jpg
WORKSHOP_SHOT_URL=https://img.example.com/press-line.jpg
THRESHOLD=0.8
HOOK_EHS=安监值班群机器人地址
成功:控制台能看到应用,设备在资产里;样本图和车间图都能用浏览器打开。
兜底:设备还在私人 App 下,先做绑定。工装接口报没权限,先把试用开下来,不要怀疑签名。
第二步:签名壳,先和文档案例对齐
请求走 https://openapi.lechange.cn/openapi/{method},壳子是 system + params + id。签名按开发规范把 time:{time},nonce:{nonce},appSecret:{appSecret} 拼成原始串,再做 MD5 小写 32 位。time 与服务器误差不能超过 5 分钟,否则 SN1002;nonce 5 分钟内不能重复,否则 SN1005。
javascript
// 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。看四样:deviceId 在不在、deviceVersion 有没有值、deviceStatus 是不是 online、accessType 是不是 PaaS。accessType 不是 PaaS 时,后面使能不要照抄。
text
token=上一步拿到的 accessToken
page=1
pageSize=10
source=bindAndShare
成功:终端能打印出冲压区、焊接工位序列号和版本号。
兜底:列表为空,设备绑在私人号下。没有烟感 / 火警 / 热成像相关能力,不要硬开不存在的使能类型。
第四步:建人形工装库,注册厂规安全帽
创建库用 createAiDetectRepository。repositoryType 只能填文档给的值:face 是人脸库,工装必须用 human。注册样本用 addAiDetectTarget:type=0 走图片 URL,type=1 走 Base64。样本用厂里真正要求的安全帽、反光衣,正脸、光线够,不要用网图。
text
# 建库
token=管理员 accessToken
repositoryName=厂规工装库
repositoryType=human
# 注册安全帽样本(可再注册一件反光衣)
token=管理员 accessToken
repositoryId=上一步返回的 repositoryId
type=0
content=https://img.example.com/helmet-ok.jpg
成功:建库返回 repositoryId;注册返回 targetKey。把这两个写进环境变量。
兜底:repositoryType 写成别的值会建错库。样本打不开或是内网地址,注册失败,先换成公网 URL。
第五步:对车间图跑 workwearDetect,群进未着装
这篇必须先跑通的接口是 workwearDetect。type 与 content 对齐:0 配 URL,1 配 Base64。threshold 范围 (0,1],联调先用 0.8。detectRegions 最多 3 组,每组 3 到 6 个点,平台会转成最大外接矩形;第一闭环可以不传,整图检测。返回 targets[],每人一个框和 detectResult。
text
token=管理员 accessToken
type=0
content=https://img.example.com/press-line.jpg
repositoryId=第四步的工装库 ID
threshold=0.8
javascript
// workwear-detect.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;
const data = await callOpenApi('workwearDetect', process.env.IMOU_APP_ID, process.env.IMOU_APP_SECRET, {
token,
type: '0',
content: process.env.WORKSHOP_SHOT_URL,
repositoryId: process.env.WORKWEAR_REPO_ID,
threshold: Number(process.env.THRESHOLD || 0.8),
});
console.log(JSON.stringify(data, null, 2));
const bad = (data.targets || []).filter((t) => t.detectResult === false);
if (bad.length) {
const text = `【着装待确认】冲压区\n未规范着装 ${bad.length} 人\n先看图,不要直接罚单`;
console.log('[安监群]', text, process.env.HOOK_EHS ? '→ hook' : '→ 仅日志');
}
})();
成功:控制台出现 targets,至少一个人有 detectResult;故意用一张未戴帽的车间图,日志打出「着装待确认」,安监群进一条。这就是第一闭环。
兜底:OP1002 / OP1003 先核对 type、content、threshold。返回空 targets,换一张人更大、更清楚的图,或把检测框缩到通道口。阈值太高会全员不合格,先固定 0.8 再调。
工装结果出来之后:把烟火推进值班群
最小闭环已经成立,才加第二层。仍用同一套现行 OpenAPI,不要外挂无关产品。
打开上报:能力集有 MsgSW,用 setDeviceCameraStatus 把 enableType 写成 msgSW(首字母小写),enable=true。文档没有单独的「烟火使能」字段,不要编 aiFire。
登记回调:这篇第二层必须跑通的是 setMessageCallback。status=on 时 callbackUrl 必填,必须公网可达。callbackFlag 写成 alarm,deviceStatus。联调 basePush 用 "2"。
text
token=管理员 accessToken
status=on
callbackUrl=https://bridge.example.com/imou/callback
callbackFlag=alarm,deviceStatus
basePush=2
成功:getMessageCallback 读回的地址和 callbackFlag 与上面填写项一致。
兜底:localhost 登记成功也收不到。只写出 alarm,掉线你永远收不到。
接收端同步路径零阻塞。文档写明:多次不回 200,平台停推。普通报警体见事件消息格式定义:id、did、cid、msgType、time。烟火三类进群;videoMotion 只记日志。不要在车间点火,按机型说明书的测试方式触发。
javascript
// bridge-server.js
require('dotenv').config();
const express = require('express');
const ZONES = {
[process.env.DEVICE_PRESS]: { zone: '冲压区', hook: process.env.HOOK_EHS },
[process.env.DEVICE_WELD]: { zone: '焊接工位', hook: process.env.HOOK_EHS },
};
const FIRE = new Set(['smokeAlarm', 'fireAlarm', 'heatImagingTemper']);
const seen = new Set();
function zoneOf(did) {
return ZONES[did] || { zone: did, hook: process.env.HOOK_EHS };
}
async function toEhsGroup(ev) {
const did = ev.did || ev.deviceId;
if (ev.msgType === 'offline' || ev.id === -1) {
const { zone } = zoneOf(did);
console.log('[安监群]', `【值守 掉线】${zone} ${did} 先看供电,不要当火情`);
return;
}
if (!FIRE.has(ev.msgType)) {
console.log('[忽略]', ev.msgType, did);
return;
}
const key = String(ev.id);
if (seen.has(key)) return;
seen.add(key);
const { zone } = zoneOf(did);
const when = new Date(ev.time * 1000).toLocaleString('zh-CN', { hour12: false });
console.log('[安监群]', `【火情待确认】${zone} ${ev.msgType}\n${did} ${when}\n先看图,不要直接报火`);
}
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(() => toEhsGroup(ev).catch((e) => console.error(e)));
});
app.get('/health', (_req, res) => res.status(200).send('ok'));
app.listen(process.env.PORT || 8080);
成功:/health 返回 ok;按说明书触发一次,日志出现 smokeAlarm 或 fireAlarm,安监群进一条「火情待确认」。
兜底:App 有告警、服务安静,回头看地址和 callbackFlag。日志只有 videoMotion,不要把动检当烟火。smokingDetect 是对图验抽烟,和这条推送不是同一条管子。
确认抓图:用 getAlarmMessageById 把回调里的 id 当成 alarmId。返回里的 picurlArray 尽快落到自己的对象存储,文档写明图片保存有时限。值班员看图:焊烟、镜头脏污,停在待确认;真烟真火,才升「已确认」。着装那条也可以拿告警图再跑一遍 workwearDetect,不要每秒对直播流狂打。
取证:叫醒走回调,回放走 queryLocalRecords 或 getCloudRecords。上下线体的 id 固定是 -1,不能拿去查告警详情。生产把内存 seen 换成 Redis。派单 SLA、跨车间值班表是下一篇的事。
联调会踩的坑
| 现象 | 多半是什么 | 先做什么 |
|---|---|---|
| 工装接口调不通 | 增值服务未开通试用 | 工单或商务申请,不要改签名 |
targets 为空 |
人太小、图太暗、样本不是厂规帽 | 换近景图;样本用本厂安全帽 |
全员 detectResult=false |
阈值偏高,或库里样本和现场帽不一致 | 先固定 0.8;重新注册本厂样本 |
把 headerDetect 当安全帽 |
那是人头检测使能 | 着装只走 workwearDetect + 工装库 |
把 smokingDetect 当火情 |
那是抽烟检测 | 火情只认 smokeAlarm / fireAlarm / heatImagingTemper |
| 登记成功但一条都没有 | URL 不是公网 / 证书 / 路径 404 | curl -X POST 打自己的回调 |
| 收过一阵突然没了 | 同步路径堵了,多次没回 200 | 修好后重新 setMessageCallback |
只有 videoMotion |
机型出不了烟火类型 | 查能力集,换机或加配件,不要编使能名 |
拿上下线 id 去查抓图 |
文档写明固定为 -1 |
只用烟火事件的 id 调 getAlarmMessageById |
真正省下的不是再买一台相机
这篇省下的不是再挂一路镜头,而是安监员不用第二天翻回放,才在群里问「夜班那顶帽子谁看过」。电视墙已经够亮了,缺的是能进业务的那一条。
只做值班大屏预览、不接群的,不必在这里把工装检测和回调搭起来。现场有人值守、违章当场能喊停的工位,也没必要上这一套。
appSecret、机器人地址只放环境变量。公网回调建议加自有校验。车间抓图按安监留存周期转存,不要把现场图长期堆在聊天记录里。人脸不要进工装库。接口只走现行 OpenAPI,不要混已标注不再维护的旧协议栏目。
++++++更多有关工厂车间安防 相关的监控开发信息请点击云开放平台进行了解++++++
→→ 云开放平台
更多信息: