自写页 SDK:在受控范围内开发项目页面

SDK 负责把宿主机中的受控数据能力交给自写 HTML 页面。它提供实时 Tag 快照、Tag 订阅、写入、历史查询、报警查询、报警订阅、多语言和表格辅助方法。SDK 提供数据能力和权限边界,不限定页面形态;项目页面可以按现场需要组织为报表、看板、诊断页、质量页、能耗页或客户专用页面。

SDK
页面位置部署在 HQControl 的项目 HTML 页面,运行在 sandbox iframe 中
最小接入声明 Tag 范围 -> ready() 获取 API -> 读取、订阅、查询历史或报警
适用页面班次报表、能耗看板、设备诊断和客户专用页面

SDK: 自写页 SDK:在受控范围内开发项目页面

自写页 SDK 面向部署在 HQControl 运行端的项目 HTML 页面。页面在 sandbox iframe 中运行,通过 `HQVISU_CUSTOM_PAGE.ready()` 获取宿主桥 API,在声明范围内读取、订阅、写入 Tag,并查询历史、趋势、报警和语言数据。项目可以用它开发班次报表、能耗汇总、设备诊断、客户看板等自定义页面;页面 UI、布局、计算口径和交互由项目 HTML 自行实现。

1. 自写页 SDK 负责什么

SDK 负责把宿主机中的受控数据能力交给自写 HTML 页面。它提供实时 Tag 快照、Tag 订阅、写入、历史查询、报警查询、报警订阅、多语言和表格辅助方法。SDK 提供数据能力和权限边界,不限定页面形态;项目页面可以按现场需要组织为报表、看板、诊断页、质量页、能耗页或客户专用页面。

  • 适合:班次产量报表、能耗日报、设备健康页、质量追溯页、工单看板、客户展示页。
  • 页面形式:普通 HTML,可包含独立 CSS、JavaScript、图表库和组件逻辑。
  • 数据来源:声明范围内的 Tag、历史数据、报警数据和语言资源。
  • 责任边界:HQControl 提供数据和权限边界,页面展示和业务计算由项目代码负责。
  • SDK 版本:当前手册按 hq-custom-page.js 的 SDK 1.3.0 能力编写。

2. 运行模型和安全边界

自写页由 HQControl 以 sandbox iframe 方式加载。页面不直接继承主应用内部对象,也不绕过登录、权限、Tag 范围和服务端校验。页面只能等待宿主注入 HQVISU_HOST,再通过 HQVISU_CUSTOM_PAGE.ready() 拿到 SDK API。

  • 声明范围是安全边界:未在页面声明的 Tag 不能作为正常读写目标。
  • 读取、历史、报警都受 readableTags 范围限制。
  • 写入必须同时满足 writableTags 声明和当前用户 tag:write 权限。
  • 自写页没有可信内建画面的全部内部能力;导航、弹窗等主应用内部能力不应写成 SDK 承诺。
  • 在普通浏览器直接打开自写页文件时没有宿主桥,ready() 会超时失败。

3. 最小接入步骤

开发自写页时先写声明,再引入 SDK,最后在 ready() 之后访问数据。不要在 ready() 解析前读取 HQVISU_HOST,此时宿主桥可能还没有注入。

  • 在 HTML 中放置 type="application/json"id="hqvisu-custom-page" 的声明块。
  • 声明 readableTags 和需要写入的 writableTags,只写页面真实需要的点位。
  • 引入 /runtime/hqVisuDesigner/hq-custom-page.js
  • 调用 HQVISU_CUSTOM_PAGE.ready(),在回调或 await 之后使用 SDK API。
  • 先做实时值和质量显示,再增加历史、报警、写入和表格。
  • 上线前在 HQControl 运行端打开页面,不以本地文件直接打开作为验收结果。
<!doctype html>
<html>
<head><meta charset="utf-8"><title>产线报表</title></head>
<body>
  <section id="app">正在连接 HQControl...</section>

  <script type="application/json" id="hqvisu-custom-page">
  {
    "readableTags": ["Line1.Temperature", "Line1.RunState", "Line1.Output", "Line1.Energy"],
    "writableTags": ["Line1.Setpoint"]
  }
  </script>
  <script src="/runtime/hqVisuDesigner/hq-custom-page.js"></script>
  <script>
  HQVISU_CUSTOM_PAGE.ready().then((api) => {
    document.getElementById("app").textContent =
      "当前温度:" + api.readNumber("Line1.Temperature", 0).toFixed(1) + " ℃";
  }).catch((err) => {
    document.getElementById("app").textContent = "SDK 初始化失败:" + err.message;
  });
  </script>
</body>
</html>

4. 点位声明写法

服务端会解析自写页 HTML 中的 JSON 声明块,并据此签发页面能力。点位必须使用 设备.点位 形式的完整点分编码,例如 Line1.Temperature。不符合格式的声明会被忽略。

  • 可用配置块 ID:hqvisu-custom-pagehqvisu-custom-page-confighqvisu-page-confighqvisu-dependencies
  • 可读字段别名:readableTagstagstagNamesreadTagswatchTags
  • 可写字段别名:writableTagswriteTagsoutputs
  • 字段值可以是数组,也可以是用换行、逗号或分号分隔的字符串。
  • SDK 1.2.0 之后,配置块中声明的可读点会自动纳入订阅范围;ready({ tags: [...] }) 可用于额外兼容或追加订阅。
<script type="application/json" id="hqvisu-custom-page">
{
  "readableTags": [
    "Line1.Temperature",
    "Line1.Pressure",
    "Line1.RunState",
    "Line1.Output",
    "Line1.Energy"
  ],
  "writableTags": ["Line1.Setpoint"]
}
</script>

5. ready() 与生命周期

ready() 等待宿主桥就绪,默认超时 5 秒。它返回 Promise,也支持传回调。页面不在 HQControl 运行端打开时,常见错误是 HQVISU_HOST is not available. Open this page from the HQ runtime shell.

  • ready(options):返回 Promise<api>
  • ready(callback):宿主就绪后执行回调。
  • timeoutMs:等待宿主桥就绪超时,默认 5000ms。
  • writeTimeoutMshistoryTimeoutMsalarmsTimeoutMs:写入、历史和报警请求超时配置。
  • destroy():页面卸载或重建时取消 SDK 订阅,避免旧回调继续运行。
const api = await HQVISU_CUSTOM_PAGE.ready({
  timeoutMs: 5000,
  historyTimeoutMs: 15000,
  alarmsTimeoutMs: 15000
});

window.addEventListener("beforeunload", () => {
  api.destroy();
});

6. 读取实时值和质量

读取方法是同步的,返回宿主机推送到页面的最近一次快照,不会每次调用都向服务器发请求。所有读取都建议传 fallback,避免页面初始态、断线或未声明点位导致显示 undefined

  • read(tag, fallback):读取原始值。
  • readNumber / readTagDouble:读取数字,无法转换时返回 fallback。
  • readTagInteger:读取整数。
  • readBoolean / readTagBoolean:读取布尔,支持 true/false、1/0、yes/no、on/off。
  • readStringreadJsonreadTagDateTime:读取字符串、JSON 和时间。
  • readTagQualityreadTagQualityGood:读取质量,质量不是 good 时应显示异常提示。
  • getMeta(tag):读取点位元信息,例如质量、显示名、数据类型等。
const temp = api.readNumber("Line1.Temperature", 0);
const running = api.readBoolean("Line1.RunState", false);
const qualityGood = api.readTagQualityGood("Line1.Temperature");

renderKpi({
  title: "当前温度",
  value: temp.toFixed(1),
  unit: "℃",
  state: qualityGood ? "normal" : "bad-quality",
  subtitle: running ? "产线运行中" : "产线停止"
});

7. 订阅实时变化

subscribe(tags, callback) 会订阅声明范围内的 Tag 变化,并返回取消订阅函数。回调参数是变化项数组,每个 item 包含 tagNamefullCodevaluequality 等字段。只想让快照开始进入页面、暂时不需要回调时,可使用 track(tags)

  • 订阅点位应来自页面声明范围。
  • 回调里可以重新调用 readNumberreadBoolean 等方法读取当前快照。
  • 离开页面、切换报表或销毁组件时要调用取消订阅函数。
  • 高频页面应集中订阅一组 Tag,再统一刷新 UI,避免每个组件重复订阅。
const stopRealtime = api.subscribe([
  "Line1.Temperature",
  "Line1.RunState",
  "Line1.Output"
], (items, host) => {
  updateRealtimeCards({
    temperature: host.readNumber("Line1.Temperature", 0),
    running: host.readBoolean("Line1.RunState", false),
    output: host.readNumber("Line1.Output", 0)
  });
});

// 不再需要实时刷新时
// stopRealtime();

8. 写入 Tag

写入方法是异步 Promise。写入会走宿主机服务端校验:点位必须在 writableTags 内,当前用户必须具备写入权限,Tag 自身也要允许控制。自写页写入是程序化动作,页面应自行做好确认、防误触和失败提示。

  • writeTag(tag, value, options):按原值写入,可用 options.valueType 指定类型。
  • writeTagBooleanwriteTagIntegerwriteTagDoublewriteTagStringwriteTagJsonwriteTagDateTime:类型化写入。
  • 常见失败:点位未声明、用户无权限、点位不存在、参数非法、写入太频繁、宿主不支持或请求超时。
  • 涉及设定值、复位、确认类操作时,页面必须先让用户确认,再调用写入。
async function increaseSetpoint() {
  const current = api.readNumber("Line1.Setpoint", 0);
  const next = current + 1;
  if (!confirm(`确认把设定值写为 ${next.toFixed(1)} ?`)) return;

  try {
    const result = await api.writeTagDouble("Line1.Setpoint", next, {
      reason: "custom page setpoint adjustment"
    });
    showToast("写入已提交:" + (result.status || "accepted"));
  } catch (err) {
    showToast("写入失败:" + err.message, "error");
  }
}

9. 查询历史和趋势

history.query() 用于按时间范围、间隔和聚合方式取历史数据。接口返回历史数据结果,页面可用返回结果绘制趋势图、能耗报表、班次统计或质量分析。

  • tags 最多 16 个,且必须在 readableTags 范围内。
  • startend 使用可解析时间,范围最多 366 天。
  • interval 可使用 15m1h1d1w 等,最小 1 分钟。
  • aggregate 支持 avgminmaxsumcountfirstlastdeltaraw
  • delta 适合电表、水表、产量累积量等报表口径。
  • 返回点数默认/最大 20000;超出预算时宿主可能放粗间隔,并通过 effectiveIntervaltruncated 表示实际结果。
const weekEnergy = await api.history.query({
  tags: ["Line1.Energy", "Line2.Energy"],
  start: "2026-07-01T00:00:00+08:00",
  end: "2026-07-08T00:00:00+08:00",
  interval: "1d",
  aggregate: "delta",
  points: 20000
});

renderEnergyReport(weekEnergy.items, {
  interval: weekEnergy.effectiveInterval,
  truncated: weekEnergy.truncated
});

10. 查询和订阅报警

报警接口用于查询当前活动报警、分页查询历史报警,以及订阅报警变化。报警范围按页面 readableTags 的整体范围确定;如果页面没有声明可读 Tag,报警查询应返回空范围。

  • alarms.getActive():获取当前活动报警快照。
  • alarms.queryHistory(payload):按时间、等级、状态、来源、分页查询历史报警。
  • alarms.subscribe(callback):订阅报警变化,返回取消订阅函数。
  • 历史报警默认可按最近 24 小时查询,单次跨度和分页大小受宿主限制。
  • 报警等级通常按 criticalhighmediumlowinfo 展示;状态通常是 activerecovered
const active = await api.alarms.getActive();
renderAlarmSummary(active.items || []);

const history = await api.alarms.queryHistory({
  start: "2026-07-07T00:00:00+08:00",
  end: "2026-07-08T00:00:00+08:00",
  severity: ["critical", "high"],
  state: ["active", "recovered"],
  limit: 200,
  offset: 0
});

const stopAlarms = api.alarms.subscribe((items) => {
  renderActiveAlarmList(items);
});

11. 报表页的数据组织方式

项目报表页由自写 HTML 页面基于 SDK 数据能力实现。页面把实时值、历史查询、报警列表和自有样式组织在同一业务视图中;建议先确定报表口径,再封装数据函数,最后实现 UI,使每个指标都有明确来源和计算口径。

  • 实时 KPI:用 readNumber / subscribe 展示当前产量、温度、运行状态、质量。
  • 趋势曲线:用 history.query 获取小时、天、周维度的数据,再交给页面图表库渲染。
  • 能耗或产量报表:累积表使用 aggregate: "delta",普通模拟量使用 avg/min/max
  • 报警摘要:用 alarms.getActive()queryHistory() 汇总当前风险和历史记录。
  • 数据表格:简单表格可用 renderTable,复杂表格可由项目组件渲染。
  • 异常态:空数据、越权、质量 bad/stale/pending、查询超时都要有页面提示。
async function renderShiftReport(api) {
  const live = {
    output: api.readNumber("Line1.Output", 0),
    energy: api.readNumber("Line1.Energy", 0),
    running: api.readBoolean("Line1.RunState", false),
    quality: api.readTagQuality("Line1.Output", "pending")
  };

  const trend = await api.history.query({
    tags: ["Line1.Output"],
    start: "2026-07-08T08:00:00+08:00",
    end: "2026-07-08T20:00:00+08:00",
    interval: "1h",
    aggregate: "sum"
  });

  const alarms = await api.alarms.getActive();
  drawKpiCards(live);
  drawOutputTrend(trend.items || []);
  api.renderTable("#alarm-table", alarms.items || [], {
    columns: [
      { key: "severity", label: "等级" },
      { key: "messageText", label: "报警内容" },
      { key: "activeTs", label: "发生时间" }
    ]
  });
}

12. renderTable 与 bindJsonTable

renderTable(target, rows, options) 是轻量表格辅助,不是报表系统。它会把数组、{ rows: [...] }{ items: [...] } 渲染成 HTML table。bindJsonTable(target, tagName, options) 会读取 JSON Tag,并在 Tag 更新时自动重渲染表格。

  • target 可以是 CSS 选择器或 DOM 元素。
  • options.columns 可传字符串数组,也可传 { key, label } 对象数组。
  • 未提供 columns 时,SDK 会根据前 25 行数据推断列。
  • options.className 可指定表格 class,页面自行写 CSS。
  • 复杂排序、分页、冻结列和导出 Excel 应由项目页面或第三方表格组件实现。
api.renderTable("#quality-table", {
  rows: [
    { tag: "Line1.Temperature", value: "72.4", quality: "good" },
    { tag: "Line1.Pressure", value: "0.68", quality: "good" }
  ]
}, {
  className: "report-table",
  columns: [
    { key: "tag", label: "点位" },
    { key: "value", label: "当前值" },
    { key: "quality", label: "质量" }
  ]
});

// JSON Tag 内容变化时自动刷新表格
const stopTable = api.bindJsonTable("#event-table", "Line1.EventRows", {
  columns: ["time", "type", "message"]
});

13. 多语言

自写页可以跟随 HQControl 当前语言切换。系统翻译包中的 key 可通过 language.translate() 读取;页面内的业务文案,也可以根据 language.current() 自行切换。

  • language.current() / getLocale():读取当前语言码。
  • language.translate(key, fallback) / t(key, fallback):读取系统翻译包。
  • language.onChange(callback):语言变化时重渲染页面,返回取消函数。
  • language.setLocale(locale):切换运行时语言,会影响整个运行时。
function renderTitle() {
  const locale = api.language.current();
  document.getElementById("title").textContent =
    locale === "en" ? "Line A Shift Report" : "A 线班次报表";
}

renderTitle();
const stopLanguage = api.language.onChange(renderTitle);

14. 错误处理和交付检查

同步读取使用 fallback 兜底;写入、历史和报警是 Promise,必须用 try/catch.catch() 处理。交付时应同时验证权限、质量、空数据、超时和控制台错误。

  • 用管理员和普通用户各打开一次页面,确认权限边界符合预期。
  • 故意移除一个未声明 Tag,确认页面有清晰错误或空态,不出现白屏。
  • 断开或模拟 bad/stale 质量,确认页面不会把异常值当正常值展示。
  • 验证历史查询时间范围、聚合口径、单位、小数位和图表说明。
  • 验证写入动作有确认、失败提示和审计原因。
  • 打开浏览器控制台,确认没有 SDK 初始化失败、404、JSON 解析错误或未捕获 Promise。
try {
  const history = await api.history.query({
    tags: ["Line1.Output"],
    start,
    end,
    interval: "1h",
    aggregate: "sum"
  });
  renderTrend(history.items || []);
} catch (err) {
  renderEmptyState("历史查询失败:" + err.message);
}
HQControl 自写页 SDK 报表示例界面
图示 1: 自写页 SDK 报表示例

图示用于说明自写 HTML 页面如何把实时快照、历史查询、报警摘要和表格辅助组织为项目报表页;页面样式和业务计算由项目代码实现。