IoT 物模型 + 客户端 SDK:乐橙摄像头、门锁、传感器统一上云管控
IoT 物模型 + 客户端 SDK:乐橙摄像头、门锁、传感器统一上云管控
去年给硬件商做全屋 IoT 管控平台:乐橙摄像机、指纹锁、门窗传感器,合同写「统一 App、统一云端」。
交付时却是:摄像机走bindDeviceLive拉 HLS;门锁同事另写 MQTT;传感器还在轮询私有 HTTP。三个品类、三套 Cron——运维每周十几条「App 离线、乐橙 App 正常」。
复盘后收成一条台账 + 两条能力轨:listDeviceDetailsByPage看productId分 IoT 物模型与视频 OpenAPI;OpenSDK统一配网添加入口。
乐橙全屋场景:两类云协议,一套 App
乐橙开放平台 智能家居方案覆盖智能摄像机、指纹锁、报警传感器(案例:鸿雁智+)。对接时别按品类写三套后台——云侧实际是两种协议:
| 品类 | 云侧路径 | 列表线索 |
|---|---|---|
| 乐橙 IPC | 视频 OpenAPI + OpenSDK Media | 常无 productId;deviceAbility含AlarmMD、AudioTalk |
| 锁 / 传感器 | IoT 物模型 | productId有值 |
文档约定:productId有值 = IoT 设备,可调:
| 接口 | 用途 |
|---|---|
getProductModel | 拉 Property / Service / Event 模板 |
getIotDeviceProperties | 读电量、门状态等 |
setIotDeviceProperties | 写属性 |
iotDeviceControl | 远程开锁等 Service |
摄像机动检仍走setMessageCallback,与物模型并行。
边界:物模型不能替代 IPC 的 HLS / 对讲;OpenSDK 配网,物模型读写只在 BFF;勿用停维护的deviceList方法。
Step 0 · 调用壳(国内网关)
// lib/platform-call.jsimportcryptofrom'node:crypto';import{randomUUID}from'node:crypto';functioncalcSign(time,nonce,secret){constraw=`time:${time},nonce:${nonce},appSecret:${secret}`;returncrypto.createHash('md5').update(raw,'utf8').digest('hex');}exportasyncfunctionplatformCall(method,params={}){consttime=Math.floor(Date.now()/1000);constnonce=randomUUID();constbody={system:{ver:'1.0',appId:process.env.APP_ID,sign:calcSign(time,nonce,process.env.APP_SECRET),time,nonce,},id:randomUUID(),params,};constres=awaitfetch(`${process.env.OPENAPI_BASE}/${method}`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body),});constjson=awaitres.json();if(json.result?.code!=='0')thrownewError(`[${json.result.code}]${json.result.msg}`);returnjson.result.data;}exportasyncfunctionadminToken(){const{accessToken}=awaitplatformCall('accessToken',{});returnaccessToken;}APP_ID=lcdxxxxxxxxxxxxAPP_SECRET=your_secretOPENAPI_BASE=https://openapi.lechange.cn/openapi签名:开发规范 · 先在 open.imou.com 创建应用。
Step 1 · 统一台账 + productId 分轨
// jobs/sync-devices.jsimport'dotenv/config';import{platformCall,adminToken}from'../lib/platform-call.js';functionclassify(dev){constisIot=Boolean(dev.productId);constability=dev.channelList?.[0]?.channelAbility??dev.deviceAbility??'';letcategory='unknown';if(isIot){category=/lock|锁/i.test(dev.deviceModel??'')?'lock':'sensor';}elseif(/AlarmMD|AudioTalk|PTZ/.test(ability)){category='camera';}return{deviceId:dev.deviceId,deviceName:dev.deviceName,productId:dev.productId??null,category,track:isIot?'iot':'video',online:dev.deviceStatus==='online',ability,};}consttoken=awaitadminToken();constpage=awaitplatformCall('listDeviceDetailsByPage',{token,pageSize:50,page:1,source:'bindAndShare',});console.log(JSON.stringify((page.deviceList??[]).map(classify),null,2));踩坑 1:全是摄像机、无 productId ——正常,别调getProductModel。
踩坑 2:锁与传感器productId 不同,getProductModel按 productId 分别缓存。
踩坑 3:乐橙 App 能看、列表 0 → 设备未绑 开发者主账号。
Step 2 · IoT 轨:模板 + 读属性
// services/iot-thing.jsconstmodelCache=newMap();exportasyncfunctionloadProductModel(productId){if(modelCache.has(productId))returnmodelCache.get(productId);constmodel=awaitplatformCall('getProductModel',{token:awaitadminToken(),productId,});modelCache.set(productId,model);returnmodel;}exportasyncfunctionreadProperties(deviceId,productId,propertyRefs){returnplatformCall('getIotDeviceProperties',{token:awaitadminToken(),deviceId,productId,properties:propertyRefs,});}constmodel=awaitloadProductModel('Z8vW2yWE');constbatteryRef=model.properties?.find(p=>p.name?.includes('电量'))?.ref??'3301';conststate=awaitreadProperties('LOCK_SN_001','Z8vW2yWE',[batteryRef]);console.log(state.properties,state.status);踩坑 4:properties传ref 数字字符串(如"3301"),不是 identifier 英文名——必须先拉模板。
物模型三维(见 概述):
Property — 读/写状态(电量、开关) Service — 方法(远程开锁) Event — 上报(可经 setMessageCallback 进告警中心)Step 3 · IoT 轨:远程开锁 Service
awaitplatformCall('iotDeviceControl',{token:awaitadminToken(),deviceId:'LOCK_SN_001',productId:'Z8vW2yWE',ref:'3010',content:{},});ref/content以getProductModel返回的 services[]为准;无入参时content: {}。
踩坑 5:方案写「一键开锁」→ BFF 必须二次确认 + 审计日志,物模型只提供能力。
Step 4 · 视频轨:乐橙 IPC
constlive=awaitplatformCall('bindDeviceLive',{token:awaitadminToken(),deviceId:'CAM_SN_001',channelId:'0',streamId:1,liveMode:'proxy',});awaitplatformCall('setMessageCallback',{token:awaitadminToken(),status:'on',callbackUrl:process.env.CALLBACK_URL,callbackFlag:'alarm,deviceStatus',basePush:'2',});告警统一入库:
functionnormalizeAlarm(raw,row){if(row.track==='video'){return{source:'video',type:raw.msgType,deviceId:raw.did,ts:raw.time};}return{source:'iot',deviceId:raw.did,payload:raw};}Step 5 · OpenSDK:统一添加入口
GET /api/v1/rooms/{id}/devices → BFF 统一 REST camera → { preview, talk } # OpenSDK MediaModule lock → { unlock, readState } # iotDeviceControl + getIotDeviceProperties sensor → { readProperties }Stringhost="openapi.lechange.cn:443";LCOpenSDK_Api.initOpenApi(newInitParams(context,host,bff.fetchAccessToken()));// DeviceAddModule — 摄像机/锁/传感器统一添加入口详见 Android 标准化 Demo · 资源下载。
Checklist
□ listDeviceDetailsByPage 全量,classify 打印 track □ 摄像机:bindDeviceLive + setMessageCallback □ 锁/传感器:getProductModel + getIotDeviceProperties □ 开锁:iotDeviceControl + 鉴权 □ OpenSDK DeviceAdd 统一入口物模型缓存与多租户
getProductModel按 productId Redis 缓存,固件升级清缓存。- 前端用ref→中文名映射,勿硬编码
"3301"。 - 多租户按 deviceId 鉴权,共用 productId 模板。
选型速查
| 需求 | 乐橙路径 |
|---|---|
| 门口直播 + 对讲 | OpenSDK + bindDeviceLive |
| 锁电量 / 门磁 | IoT Property |
| 远程开锁 | iotDeviceControl |
| 摄像机动检 | setMessageCallback |
| 7 天 H5 PoC | 轻应用 |
排错
| 现象 | 处理 |
|---|---|
| getProductModel 失败 | 无 productId,误分类 |
| properties 空 | ref 错 / offline |
| 三类三个 Cron | 合并 sync-devices |
| IPC 走 iot 轨 | 无 productId → video |
安全
appSecret仅 BFF;开锁 Service 强鉴权。- 回调 HTTPS + 200;我的资源 关注配额。
小结
乐橙摄像头、门锁、传感器不必三套后台:
OpenSDK DeviceAdd 统一配网 → listDeviceDetailsByPage 台账(productId 分轨) → iot 轨:getProductModel / Property / Service → video 轨:bindDeviceLive / setMessageCallback → BFF 一套 REST 给 App客户验收:一个 App、三个房间 Tab、锁能开、摄像机能看、传感器刷新——底层两条轨,用户无感。
延伸阅读
- IoT 物模型概述
- 分页查询设备详情
- OpenSDK 组件
- 同系列:白牌 App 配网/预览/对讲 · 告警 3 分钟验收
开始接入
在乐橙开放平台 open.imou.com注册并创建应用。平台以视频技术与安全为核心,提供OpenAPI、IoT 物模型、OpenSDK、轻应用,帮助集成商一站式落地摄像机 + 锁 + 传感器混合全屋方案。
建议先跑sync-devices打印 classify,看清哪些设备有productId,再写 UI——比先画原型后补协议省一半返工。
你的项目里是锁先接入还是 IPC 先接入?评论区说说。
