【4G IPC 上云】临时点位怎么免布线上云?listDeviceDetailsByPage + 辅码流预览|智慧工地实战

项目经理周五下午四点发来一张工地图:塔吊基础刚开槽,围挡还在拼,光缆路由「下周一再谈」。安监科长的消息紧跟其后------「周一早会要在值班大屏上看塔吊与出入口,别跟我说布线。」

你站在空地上环顾:没有弱电井,没有机房,连临时用电都是配电箱挂表。传统「枪机 + NVR + 专网」在这里不是方案,是工期笑话。

真正能周一交差的路径只有一条:4G IPC 插卡上电 → 设备进开发者资产池 → 后台用现行 OpenAPI 出可播地址 → 安监页嵌预览。 布线可以慢慢做,预览不能等。

下面这篇是智慧工地安监场景的免布线预览落地笔记:可运行 Node.js、真实接口参数、以及把踩坑写进正文的联调过程。


一、「免布线」不是偷懒,是验收时间表

工地安监的第一场仗,很少输在算法,更多输在现场还没网、领导已经要画面

典型反差是:

  • 合同写着「可视化监管」,实施清单却从「挖沟穿管」开始;
  • 设备到场了,SIM 卡没开;卡开了,设备却绑在私人 App 号下;
  • 后端接口调通了,值班墙用了主码流,流量池两小时见底。

钩子只有一句话:4G 解决「连得上」,开放平台解决「进你的系统、按你的权限播」。 两者缺一,周一早会都会难看。


二、为什么工地安监必须认真对待「4G + 云预览」

2.1 场景真相:临时性、流动性、弱基础设施

维度 工地现实 对视频方案的约束
工期 点位随施工阶段迁移 不能按永久机房设计
网络 早期常无光纤 / Wi-Fi 优先 4G / 物联网卡
角色 总包、分包、安监、业主多方看 权限必须在业务侧裁决
成本 流量与带宽敏感 默认辅码流做值班预览
验收 「大屏能看」先于「全套安防」 先最小闭环,再加回放 / 告警

开放平台侧与本文相关的能力边界(对照官网「4G 物联网卡」「云直播」「轻应用」等现行说明):

  1. 4G 物联网卡:插卡即联网,适合户外 / 临时点位上云,不必先等有线。
  2. 设备资产 OpenAPI :绑定后用 listDeviceDetailsByPage 同步在线状态与通道。
  3. 云直播bindDeviceLive 签发 HLS;getLiveStreamInfo 查全量地址;createDeviceFlvLive 给 Web 低延迟一点的路径。
  4. 轻应用getKitToken + imouPlayer,PC 网页值班墙延迟更友好,对接周期通常按「天」计。

2.2 解决思路:把「卡、设备、流、人」拆成四层

text 复制代码
┌──────────────────┐    ┌────────────────┐    ┌──────────────────────┐
│ 4G IPC + 物联网卡  │ → │ 开发者资产池     │ → │ 现行 OpenAPI 出流      │
│ 上电 / 插卡       │    │ bind / 列表     │    │ HLS / FLV / kitToken  │
└──────────────────┘    └────────────────┘    └──────────┬───────────┘
                                                         │
                                                         ▼
                                               ┌──────────────────────┐
                                               │ 安监业务后台(BFF)     │
                                               │ 工地↔设备映射 + 鉴权    │
                                               │ 值班墙 / H5 / 大屏     │
                                               └──────────────────────┘

业务同学不该关心这路是 4G 还是将来换成光纤------他们只该知道:

  • 这路在不在线(deviceStatus / channelStatus);
  • 怎么拿到可播地址(优先辅码流);
  • 谁有权看这一路(你们自己的 JWT / 角色,不是把 m3u8 写死在前端)。

2.3 选型对照:三条出流路径怎么配工地

路径 关键接口 / 组件 延迟体感 对接成本 工地建议
云直播 HLS bindDeviceLive 较高(秒级偏上) 极低 业主 / 分包「看一眼」、外链受限场景
云直播 FLV createDeviceFlvLive 通常优于 HLS Web 值班墙第二选择
轻应用 getKitToken + imouPlayer PC 约 2~3s 量级 低~中 安监值班室主路径
移动 OpenSDK 客户端 SDK 更低 要做原生安监 App 再上

本文主线:4G 接入 + 列表验收 online + 辅码流 HLS 最小闭环,再加一条轻应用值班墙进阶。对讲、云台、复杂告警流水线另文展开,避免第一周范围膨胀。


三、架构、可运行代码与实操步骤

3.1 端到端主流程

flowchart TB subgraph A[现场接入] S1[4G IPC 插物联网卡上电] S2[确认设备联网 / App 可看] S3[开发者账号绑定设备] end subgraph B[云端资产] T[accessToken] L[listDeviceDetailsByPage] M[(本地表: siteId / deviceId / channelId)] end subgraph C[出流消费] H[bindDeviceLive streamId=1] F[createDeviceFlvLive type=realTime] K[getKitToken + imouPlayer] W[安监值班墙 / H5] end S1 --> S2 --> S3 --> L T --> L --> M M --> H --> W M --> F --> W M --> K --> W

3.2 前置:签名自检(不过这一关别写业务)

域名与报文约定(开发规范):

  • POST:https://openapi.lechange.cn:443/openapi/[method]
  • system.ver = "1.0"time 为秒级时间戳(与真实时间误差 ≤ 5 分钟)
  • nonce 5 分钟内不可复用(否则 SN1005
  • 签名原始串:time:{time},nonce:{nonce},appSecret:{appSecret} → MD5 → 32 位小写

官方标准用例:

text 复制代码
time:1706511734,nonce:f5a1ae2d-c09c-4d39-a744-83a5c2c653c2,appSecret:test123456789test123456789
→ sign = fd37b62889e4757c58b8f3bf05fb9976
js 复制代码
// sign-selfcheck.js
const crypto = require('crypto');

const time = '1706511734';
const nonce = 'f5a1ae2d-c09c-4d39-a744-83a5c2c653c2';
const appSecret = 'test123456789test123456789';
const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
const sign = crypto.createHash('md5').update(raw, 'utf8').digest('hex');

console.log(sign);
// 期望: fd37b62889e4757c58b8f3bf05fb9976

封装可复用客户端:

js 复制代码
// openapi-client.js
const crypto = require('crypto');
const { randomUUID } = require('crypto');

const OPENAPI_BASE = process.env.OPENAPI_BASE || 'https://openapi.lechange.cn/openapi';
const APP_ID = process.env.APP_ID;       // 控制台 - 我的应用 - 应用信息
const APP_SECRET = process.env.APP_SECRET;

function buildSign(time, nonce, appSecret) {
  const raw = `time:${time},nonce:${nonce},appSecret:${appSecret}`;
  return crypto.createHash('md5').update(raw, 'utf8').digest('hex');
}

async function openapiCall(method, params = {}) {
  const time = Math.floor(Date.now() / 1000);
  const nonce = randomUUID();
  const body = {
    system: {
      ver: '1.0',
      appId: APP_ID,
      sign: buildSign(time, nonce, APP_SECRET),
      time,
      nonce,
    },
    id: randomUUID(),
    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 (String(json?.result?.code) !== '0') {
    throw new Error(`${method} failed: ${JSON.stringify(json)}`);
  }
  return json.result.data;
}

module.exports = { openapiCall };

3.3 Step 1:拿管理员 accessToken(约 3 天有效)

params 可为空对象。即将过期或报 TK1002 再刷;勿高频刷刷刷占配额。超过 2 天未满 3 天再调会返回新 token,新旧各自独立可用。

js 复制代码
// get-token.js
const { openapiCall } = require('./openapi-client');

async function main() {
  const data = await openapiCall('accessToken', {});
  console.log(data);
  // { accessToken: 'At_xxxx', expireTime: 259176 }
}

main().catch(console.error);

3.4 Step 2:现场 4G 上电 + 绑定进开发者资产

现场 SOP(可直接贴进实施手册):

text 复制代码
1. 物联网卡入网开通(控制台申请 / 运营商侧激活,以现场卡类型为准)
2. 卡插入 4G IPC,上电;等待指示灯进入可联网状态
3. 用开发者主账号在乐橙 App / 控制台确认设备可预览(排除「卡没流量」伪故障)
4. 将设备绑定到开放平台应用对应的开发者账号
   - 控制台绑定,或
   - HTTP:bindDevice(部分新设备可能需结合客户端 SDK 完成绑定)
5. listDeviceDetailsByPage 验收 deviceStatus === online

bindDevice 参数要点(对照文档):

参数 说明
token 管理员 accessToken
deviceId 设备序列号
code 未改密:标签 / 二维码 8 位安全码;已改密:新密码;无 8 位安全码且未改密:可传空
encryptCode 可选,与 code 二选一,安全要求高时用 AES 规则加密
js 复制代码
// bind-device.js
const { openapiCall } = require('./openapi-client');

async function main() {
  const { accessToken } = await openapiCall('accessToken', {});
  await openapiCall('bindDevice', {
    token: accessToken,
    deviceId: process.env.DEVICE_ID,
    code: process.env.DEVICE_CODE || '', // 按设备标签实际情况填写
  });
  console.log('bindDevice ok');
}

main().catch(console.error);

踩坑 ①(真实发生过) :监理用私人乐橙号在 App 里加设备,开发者侧 listDeviceDetailsByPage 永远 0 台,安监页写「暂无画面」。

处理:绑定必须落在公司开发者主账号(或明确托管 / 分享链路),再同步资产。

3.5 Step 3:分页台账------工地安监的「设备是否真在线」

js 复制代码
// list-devices.js
const { openapiCall } = require('./openapi-client');

async function listAllDevices(accessToken) {
  const pageSize = 50; // 文档范围 1~50
  let page = 1;
  const all = [];

  while (true) {
    const data = await openapiCall('listDeviceDetailsByPage', {
      token: accessToken,
      pageSize,
      page,
      source: 'bindAndShare', // bind | share | bindAndShare
    });
    const list = data.deviceList || [];
    all.push(...list);
    if (list.length < pageSize) break;
    page += 1;
  }
  return all;
}

async function main() {
  const { accessToken } = await openapiCall('accessToken', {});
  const devices = await listAllDevices(accessToken);

  const rows = devices.map((d) => ({
    deviceId: d.deviceId,
    name: d.deviceName,
    model: d.deviceModel,
    catalog: d.catalog, // IPC / NVR ...
    status: d.deviceStatus, // online | offline | sleep | upgrading
    channels: (d.channelList || []).map((c) => ({
      channelId: c.channelId,
      channelStatus: c.channelStatus,
      abilities: c.channelAbility,
    })),
  }));

  console.table(
    rows.map((r) => ({
      deviceId: r.deviceId,
      status: r.status,
      channel0: r.channels[0]?.channelStatus,
    })),
  );
}

main().catch(console.error);

业务库建议最小字段:

sql 复制代码
-- 示意:工地点位与云端设备映射
CREATE TABLE site_camera (
  id            BIGINT PRIMARY KEY,
  site_id       VARCHAR(64) NOT NULL,  -- 工地项目编号
  point_name    VARCHAR(128),          -- 塔吊 / 出入口 / 临边
  device_id     VARCHAR(64) NOT NULL,
  channel_id    VARCHAR(16) NOT NULL DEFAULT '0',
  prefer_stream INT NOT NULL DEFAULT 1, -- 工地默认辅码流
  UNIQUE (site_id, device_id, channel_id)
);

踩坑 ② :只看 deviceStatus=online,忽略通道 channelStatus。少数场景设备在线但通道休眠 / 异常,出流仍失败。值班墙过滤条件建议两者都过。

3.6 Step 4:最小出画------bindDeviceLive 辅码流

工地 4G 流量金贵:PoC 与多屏轮询一律 streamId: 1(标清辅码流) ;领导验收特写再临时切 0

接口要点:

  • URL:https://openapi.lechange.cn/openapi/bindDeviceLive
  • 必填:tokendeviceIdchannelIdstreamId(0 主 / 1 辅)
  • liveMode 可不填或固定 "proxy"
  • 本接口返回当前所选码流的 HTTP HLS ;全量(主/辅 × HTTP/HTTPS)用 getLiveStreamInfo
  • 直播地址公开即可看,切勿写进前端仓库或永久贴进群公告
js 复制代码
// smoke-live.js
const { openapiCall } = require('./openapi-client');

async function createHls({ accessToken, deviceId, channelId = '0', streamId = 1 }) {
  const data = await openapiCall('bindDeviceLive', {
    token: accessToken,
    deviceId,
    channelId: String(channelId),
    streamId,
    liveMode: 'proxy',
  });
  return {
    liveToken: data.liveToken,
    liveStatus: data.liveStatus, // 1 开启;2 暂停
    hls: data.streams?.[0]?.hls,
    coverUrl: data.streams?.[0]?.coverUrl,
  };
}

async function listAllHls({ accessToken, deviceId, channelId = '0' }) {
  // 需先 bindDeviceLive,否则可能查不到
  const data = await openapiCall('getLiveStreamInfo', {
    token: accessToken,
    deviceId,
    channelId: String(channelId),
  });
  return (data.streams || []).map((s) => ({
    streamId: s.streamId,
    liveToken: s.liveToken,
    hls: s.hls,
    status: s.status,
    coverUrl: s.coverUrl,
  }));
}

async function main() {
  const { accessToken } = await openapiCall('accessToken', {});
  const deviceId = process.env.DEVICE_ID;

  const live = await createHls({ accessToken, deviceId, streamId: 1 });
  console.log('辅码流 HLS:', live);

  const all = await listAllHls({ accessToken, deviceId });
  console.log('全量流:', all);
}

main().catch(console.error);

浏览器侧用 hls.js(或原生支持 HLS 的 Safari)播即可。安监 BFF 正确姿势 :前端只拿「短效播放票据」,由服务端校验「该用户能否看该 site_id」后,再返回当前 HTTPS / HTTP 可播地址。

js 复制代码
// express 示意:短效签发,勿把 appSecret 暴露到浏览器
app.get('/api/sites/:siteId/preview', authMiddleware, async (req, res) => {
  const mapping = await db.findCamera(req.params.siteId);
  if (!mapping || !canView(req.user, mapping)) {
    return res.status(403).json({ error: 'forbidden' });
  }
  const { accessToken } = await getCachedAdminToken(); // 服务端缓存,勿每次 accessToken
  const live = await createHls({
    accessToken,
    deviceId: mapping.device_id,
    channelId: mapping.channel_id,
    streamId: mapping.prefer_stream ?? 1,
  });
  res.json({ hls: live.hls, expireHintSec: 300 });
});

踩坑 ③ :把 bindDeviceLive 返回的 HTTP m3u8 硬塞进 HTTPS 页面,浏览器混合内容拦截。处理:优先取 HTTPS 地址(getLiveStreamInfo 全量里选),或页面与播放同策略降级,并在联调清单里单列一项。

踩坑 ④ :一创建直播就默认主码流 + 四宫格常开,流量池下午告警。处理:轮询预览强制辅码流;无人值守时暂停直播计划(modifyLivePlanStatus 等,见云直播文档),不要 24 小时裸奔拉流。

3.7 Step 5:值班墙要更跟手------轻应用

当安监科说「HLS 慢半拍,塔吊动作对不上口令」,把值班室主路径切到轻应用:

js 复制代码
// get-kit-token.js
const { openapiCall } = require('./openapi-client');

async function main() {
  const { accessToken } = await openapiCall('accessToken', {});
  const data = await openapiCall('getKitToken', {
    token: accessToken,
    deviceId: process.env.DEVICE_ID,
    channelId: '0',
    type: '1', // 0 全部;1 实时预览;2 录像回放;6 云台
  });
  console.log(data); // kitToken 有效期约 2 小时;建议服务端缓存约 1 小时
}

main().catch(console.error);

前端(资源从开放平台「资源下载 → 轻应用直播套件」获取,引入 css/js,并按文档放置 WasmLib):

html 复制代码
<link href="./imou-player.css" rel="stylesheet" />
<script src="./imou-player.js"></script>
<div id="root"></div>
<script>
  // kitToken 必须由你们后端签发,前端只消费
  const player = new imouPlayer({
    id: 'root',
    width: 960,
    height: 540,
    deviceId: 'YOUR_DEVICE_ID',
    channelId: 0,
    token: 'Kt_xxxx', // getKitToken 返回
    type: 1,          // 1 直播;2 录播
    streamId: 1,      // 工地默认标清
    templateMode: 'pc',
    WasmLibPath: '/', // 按项目 public 实际路径调整
    // 自定义加密密钥 / 设备密码场景按文档填 code
  });
</script>

多线程解码若启用 SharedArrayBuffer,需按文档配置:

nginx 复制代码
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";

踩坑 ⑤ :WasmLib 路径错导致 Unexpected token '<'。先独立打开 Wasm 资源 URL,确认不是 HTML 404 页,再调 WasmLibPath

3.8 Step 6:Web 要 FLV 时

js 复制代码
// flv-live.js
const { openapiCall } = require('./openapi-client');

async function main() {
  const { accessToken } = await openapiCall('accessToken', {});
  const data = await openapiCall('createDeviceFlvLive', {
    token: accessToken,
    deviceId: process.env.DEVICE_ID,
    channelId: '0',
    type: 'realTime', // realTime | playback
  });
  console.log({ flv: data.flv, flvHD: data.flvHD });
  // 工地默认播 flv(标清);特写再用 flvHD
}

main().catch(console.error);

回放场景再传 type: "playback" + beginTime / endTime(最大跨度 24 小时)+ recordTypelocalRecord / cloudRecord)。实时预览 PoC 先别搅和回放参数。

3.9 一页纸验收清单

text 复制代码
□ open.imou.com 应用已创建,拿到 appId / appSecret(仅服务端)
□ sign-selfcheck 与官方用例一致
□ 4G 卡已开通,设备指示灯 / App 可预览
□ 设备绑定在开发者主账号(非私人号)
□ listDeviceDetailsByPage:目标 deviceStatus / channelStatus = online
□ bindDeviceLive streamId=1:hls 非空,liveStatus=1
□ 安监页经 BFF 鉴权后出画(前端无 appSecret)
□ 四宫格压测 30 分钟,流量与卡顿可接受
□ (可选)轻应用 kitToken 出画;COOP/COEP 与 WasmLib 就绪

四、边界、性能与生产注意事项

4.1 边界:4G 预览解决什么、不解决什么

做得到 别指望这一篇闭环
临时点位免布线出画 替代永久专网的全部合规取证链路
多工地资产进同一开放平台账号 业务侧「谁能看哪路」自动完成------必须自建映射与鉴权
HLS / FLV / 轻应用三条 Web 路径 超低延迟对讲指挥(要走 SDK / 对应能力)
辅码流控流量 无视套餐的「全点位主码流常开」

光纤通了之后,同一套 deviceId + channelId 与 OpenAPI 不用推翻;变的是现场接入介质,不是你的 BFF 契约------这是选开放平台资产模型的隐性收益。

4.2 性能与流量:工地特供策略

  1. 默认辅码流 :列表页、轮询、四宫格一律 streamId: 1 / flv
  2. 按需出流 :进入详情再 bindDeviceLive / getKitToken;离开销毁播放器(轻应用 destroy())。
  3. token 缓存accessToken 按过期时间缓存;kitToken 建议缓存约 1 小时(文档有效期 2 小时)。
  4. 并发墙 :多路轻应用比纯 video+hls 更吃 CPU(解密 + canvas)。弱终端减少同屏路数,或降清晰度。
  5. 卡池监控:流量告警与「主码流误开」告警打到同一值班群,否则总在周末爆。

4.3 生产环境注意

  • 密钥appSecret 只活在服务端;CI 用环境变量,别进前端包。
  • 直播 URL:等同于公开摄像头;结合登录态、短效签发、审计日志(谁、何时、哪路、哪工地)。
  • 绑定策略 :新设备可能无法仅靠 HTTP bindDevice,预留 App / OpenSDK 配网绑定预案(见「应用开发」现行说明)。
  • 旧协议:文档树里的「旧版本协议(后续不再维护)」整栏跳过;列表、直播、轻应用都走现行章节。
  • 合规 :工地画面常含人员与作业过程,对外分享、分包账号要最小化权限,避免把永久 m3u8 丢进微信群。

4.4 扩展方向(本文不展开,可作下一篇)

  • 云录像 + createDeviceRecordHls:事故回溯;
  • 消息推送:周界 / 人形告警进工单;
  • 设备托管:总包统管、分包只读;
  • 国标利旧:后期固定点位混接 GB28181(与 4G 新点位同一资产池消费)。

五、小结与延伸

工地安监第一周的胜利条件,不是「上齐整套智慧工地平台」,而是:

  1. 现场:4G IPC + 物联网卡,免布线连上云;
  2. 资产 :开发者账号绑定,listDeviceDetailsByPage 能对上台账;
  3. 出画bindDeviceLive 辅码流跑通,BFF 鉴权后嵌进安监页;
  4. 值班 :需要更低延迟时,再上 getKitToken + 轻应用。

把「卡是否有流量」「设备绑在谁名下」「有没有误开主码流」这三件土事写进验收清单,比堆功能更能保住周一早会。


附录:关键返回字段速查

接口 关键字段 工地用法
accessToken accessToken / expireTime 服务端缓存
listDeviceDetailsByPage deviceStatus / channelList[].channelId 台账与上线验收
bindDeviceLive liveToken / streams[].hls / liveStatus 最小 HLS 闭环
getLiveStreamInfo streams[] 取 HTTPS / 主辅全量
createDeviceFlvLive flv / flvHD Web FLV
getKitToken kitToken(约 2h) 轻应用值班墙
相关推荐
铁皮饭盒3 小时前
DeepSeek V4 Pro 0813发布了, 也可以部署到 Codex 了
前端·javascript·后端
苏三说技术3 小时前
推荐一个牛逼的企业智能招聘系统
后端
达达尼昂3 小时前
Flutter AI Harness 如何让 Agent 参与软件开发全流程
android·人工智能·后端
k4m7v2pz4 小时前
Rust 长跑守护进程日志治理:切分、时区与结构化
开发语言·后端·rust·日志系统·日志轮转·ndjson
嘟嘟07175 小时前
Next.js 里 SSR、CSR 和水合到底差在哪?从一段待办代码说起
前端·后端·next.js
liuxiaocheng5 小时前
快速上手:5 分钟跑通第一个 AI SDK 例子
前端·人工智能·后端
数字新视界5 小时前
信创动环监控厂家深入剖析智能机房环境监控技术应用与挑战
服务器·数据库·物联网·芯片·动环监控系统
newerp5 小时前
Go net/http 标准库基础
后端·程序员·go
mONESY5 小时前
React 前端如何不傻等后端接口?
前端·javascript·后端