Custom-page SDK for project pages

The SDK gives a custom HTML page controlled access to runtime data capabilities. It provides realtime tag snapshots, tag subscription, writes, history query, alarm query, alarm subscription, language helpers, and table helpers. The SDK provides data capabilities and permission boundaries without prescribing a page shape; the project page can be organized as a report, dashboard, diagnosis page, quality page, energy page, or customer-specific view.

SDK
Page locationProject HTML page deployed in HQControl and run inside a sandbox iframe
Minimum accessDeclare tag scope -> call ready() -> read, subscribe, query history or alarms
Use casesShift reports, energy dashboards, device diagnostics, and customer-specific pages

SDK: Custom-page SDK for project pages

The custom-page SDK is for project HTML pages deployed in HQControl runtime. Pages run in a sandbox iframe and call `HQVISU_CUSTOM_PAGE.ready()` to obtain controlled host APIs. Within the declared scope, a page can read, subscribe, write tags, and query history, trend, alarm, and language data. Projects can use it for shift reports, energy summaries, equipment diagnostics, customer dashboards, and other custom runtime pages. UI, layout, calculations, and interactions are implemented by the project HTML page.

1. What the SDK owns

The SDK gives a custom HTML page controlled access to runtime data capabilities. It provides realtime tag snapshots, tag subscription, writes, history query, alarm query, alarm subscription, language helpers, and table helpers. The SDK provides data capabilities and permission boundaries without prescribing a page shape; the project page can be organized as a report, dashboard, diagnosis page, quality page, energy page, or customer-specific view.

  • Good fits: shift production reports, daily energy reports, equipment health pages, quality trace pages, work-order boards, and customer views.
  • Page format: ordinary HTML with project CSS, JavaScript, chart libraries, and component logic.
  • Data sources: declared tags, history data, alarm data, and language resources.
  • Responsibility boundary: HQControl provides data and permission boundaries; the project page owns presentation and business calculations.
  • SDK version: this manual describes the SDK 1.3.0 capability in hq-custom-page.js.

2. Runtime model and safety boundary

HQControl loads a custom page in a sandbox iframe. The page does not directly inherit internal objects from the main app and does not bypass login, permissions, tag scope, or server validation. It waits for the host to inject HQVISU_HOST, then obtains SDK APIs through HQVISU_CUSTOM_PAGE.ready().

  • Declared scope is the safety boundary: undeclared tags are not normal read or write targets.
  • Reads, history, and alarms are limited by readableTags.
  • Writes require both writableTags declaration and current-user tag:write permission.
  • Do not document trusted built-in runtime features, such as internal navigation or popups, as custom-page SDK promises.
  • If the page is opened directly in a normal browser file context, there is no host bridge and ready() times out.

3. Minimal integration steps

Declare scope first, include the SDK, then access data only after ready() resolves. Do not read HQVISU_HOST before ready(); the host bridge may not be injected yet.

  • Place a type="application/json" block with id="hqvisu-custom-page" in the HTML.
  • Declare readableTags and the required writableTags; include only tags the page really needs.
  • Include /runtime/hqVisuDesigner/hq-custom-page.js.
  • Call HQVISU_CUSTOM_PAGE.ready(), then use SDK APIs in the callback or after await.
  • Build realtime value and quality display first, then add history, alarms, writes, and tables.
  • Validate inside HQControl runtime; opening the local HTML file directly is not a delivery check.
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Line Report</title></head>
<body>
  <section id="app">Connecting to 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 =
      "Current temperature: " + api.readNumber("Line1.Temperature", 0).toFixed(1) + " C";
  }).catch((err) => {
    document.getElementById("app").textContent = "SDK initialization failed: " + err.message;
  });
  </script>
</body>
</html>

4. How to declare tag scope

The server parses JSON declaration blocks in the custom page HTML and mints the page capability from them. Tags must use full dotted code such as Line1.Temperature. Invalid declarations are ignored.

  • Supported config block ids: hqvisu-custom-page, hqvisu-custom-page-config, hqvisu-page-config, and hqvisu-dependencies.
  • Readable aliases: readableTags, tags, tagNames, readTags, and watchTags.
  • Writable aliases: writableTags, writeTags, and outputs.
  • Values may be arrays or strings separated by newline, comma, or semicolon.
  • Since SDK 1.2.0, readable tags declared in the config block are automatically tracked; ready({ tags: [...] }) remains available for compatibility or extra tracking.
<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() and lifecycle

ready() waits for the host bridge. The default timeout is 5 seconds. It returns a Promise and also supports callback style. When the page is not opened from HQControl runtime, the common error is HQVISU_HOST is not available. Open this page from the HQ runtime shell..

  • ready(options): returns Promise<api>.
  • ready(callback): runs the callback when the host is ready.
  • timeoutMs: host-bridge wait timeout, default 5000 ms.
  • writeTimeoutMs, historyTimeoutMs, and alarmsTimeoutMs: write, history, and alarm request timeouts.
  • destroy(): cancels SDK subscriptions when the page or component is destroyed.
const api = await HQVISU_CUSTOM_PAGE.ready({
  timeoutMs: 5000,
  historyTimeoutMs: 15000,
  alarmsTimeoutMs: 15000
});

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

6. Read realtime values and quality

Read methods are synchronous and return the latest snapshot pushed to the page. They do not make a server request on each call. Always pass a fallback so initial state, disconnection, or undeclared tags do not show undefined.

  • read(tag, fallback): raw value.
  • readNumber / readTagDouble: numeric value, fallback when conversion fails.
  • readTagInteger: integer value.
  • readBoolean / readTagBoolean: boolean value, accepting true/false, 1/0, yes/no, on/off.
  • readString, readJson, readTagDateTime: string, JSON, and datetime.
  • readTagQuality, readTagQualityGood: quality check; display an exception state when quality is not good.
  • getMeta(tag): metadata such as quality, display name, and data type.
const temp = api.readNumber("Line1.Temperature", 0);
const running = api.readBoolean("Line1.RunState", false);
const qualityGood = api.readTagQualityGood("Line1.Temperature");

renderKpi({
  title: "Current temperature",
  value: temp.toFixed(1),
  unit: "C",
  state: qualityGood ? "normal" : "bad-quality",
  subtitle: running ? "Line running" : "Line stopped"
});

7. Subscribe to realtime changes

subscribe(tags, callback) subscribes to declared tag changes and returns an unsubscribe function. The callback receives changed items, each carrying fields such as tagName, fullCode, value, and quality. Use track(tags) when the page only needs snapshots and no callback yet.

  • Subscribed tags should be inside the page declaration scope.
  • Inside the callback, read the current snapshot with methods such as readNumber and readBoolean.
  • Call the unsubscribe function when leaving the page, switching report sections, or destroying a component.
  • For high-frequency pages, subscribe to one tag group and refresh UI in one pass instead of subscribing per widget.
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. Write tags

Write methods return Promise. The host validates the request on the server side: the tag must be in writableTags, the current user must have write permission, and the tag itself must allow control. Custom-page writes are programmatic, so the page should implement confirmation, anti-misclick behavior, and failure messages.

  • writeTag(tag, value, options): raw write; options.valueType can specify the type.
  • writeTagBoolean, writeTagInteger, writeTagDouble, writeTagString, writeTagJson, and writeTagDateTime: typed writes.
  • Common failures: undeclared tag, missing permission, tag not found, invalid payload, too many requests, unsupported host, or timeout.
  • For setpoints, reset, acknowledge, and similar actions, confirm with the user before calling write.
async function increaseSetpoint() {
  const current = api.readNumber("Line1.Setpoint", 0);
  const next = current + 1;
  if (!confirm(`Write setpoint ${next.toFixed(1)}?`)) return;

  try {
    const result = await api.writeTagDouble("Line1.Setpoint", next, {
      reason: "custom page setpoint adjustment"
    });
    showToast("Write submitted: " + (result.status || "accepted"));
  } catch (err) {
    showToast("Write failed: " + err.message, "error");
  }
}

9. Query history and trends

history.query() fetches historical data by time range, interval, and aggregate. The API returns historical data results; the project page decides how to render trends, energy reports, shift statistics, or quality analysis.

  • tags supports up to 16 tags, all within readableTags.
  • start and end use parseable timestamps, with a range up to 366 days.
  • interval may use values such as 15m, 1h, 1d, or 1w, with 1 minute as the minimum.
  • aggregate supports avg, min, max, sum, count, first, last, delta, and raw.
  • delta fits meters and accumulating counters such as energy, water, or production total.
  • The point budget is default/max 20000. If the query is too dense, the host may coarsen the interval and return effectiveInterval and truncated.
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. Query and subscribe to alarms

Alarm APIs query the active-alarm snapshot, page through alarm history, and subscribe to alarm changes. Alarm scope is the whole readableTags scope for the page. If no readable tags are declared, the alarm query should return an empty scope.

  • alarms.getActive(): current active-alarm snapshot.
  • alarms.queryHistory(payload): historical alarms by time, severity, state, source, and pagination.
  • alarms.subscribe(callback): alarm change subscription; returns an unsubscribe function.
  • Historical alarm queries default to recent time windows and host-side page limits.
  • Typical severities are critical, high, medium, low, and info; typical states are active and recovered.
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. Organize data for report pages

A project report page is implemented by the custom HTML page on top of SDK data capabilities. The page organizes realtime values, history results, alarm lists, and project styles into one business view. Define the report calculation first, then encapsulate data functions, then implement the UI so every metric has a clear source and calculation basis.

  • Realtime KPIs: use readNumber / subscribe for current output, temperature, running state, and quality.
  • Trend charts: use history.query for hourly, daily, or weekly data, then render with the page chart library.
  • Energy or production reports: use aggregate: "delta" for accumulated meters; use avg/min/max for analog values.
  • Alarm summary: use alarms.getActive() and queryHistory() to summarize current risk and history.
  • Data tables: use renderTable for simple tables; use project components for complex tables.
  • Exception states: handle empty data, denied scope, bad/stale/pending quality, and query timeout.
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: "Severity" },
      { key: "messageText", label: "Alarm" },
      { key: "activeTs", label: "Active time" }
    ]
  });
}

12. renderTable and bindJsonTable

renderTable(target, rows, options) is a lightweight table helper, not a report system. It renders arrays, { rows: [...] }, or { items: [...] } into an HTML table. bindJsonTable(target, tagName, options) reads a JSON tag and rerenders the table when that tag updates.

  • target may be a CSS selector or DOM element.
  • options.columns can be an array of strings or { key, label } objects.
  • When columns are omitted, the SDK infers columns from the first 25 rows.
  • options.className sets the table class; the page owns the CSS.
  • Complex sorting, pagination, frozen columns, and Excel export should be implemented by the project page or a table component.
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: "Tag" },
    { key: "value", label: "Current value" },
    { key: "quality", label: "Quality" }
  ]
});

const stopTable = api.bindJsonTable("#event-table", "Line1.EventRows", {
  columns: ["time", "type", "message"]
});

13. Language

A custom page can follow the current HQControl language. Use language.translate() for system translation keys, and use language.current() to switch project-owned text in the page.

  • language.current() / getLocale(): current locale.
  • language.translate(key, fallback) / t(key, fallback): translate from the system bundle.
  • language.onChange(callback): rerender when language changes; returns an unsubscribe function.
  • language.setLocale(locale): changes runtime language and affects the whole runtime.
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. Error handling and delivery checks

Synchronous reads use fallback values. Writes, history, and alarms return Promise and must be handled with try/catch or .catch(). Delivery checks should verify permissions, quality, empty data, timeouts, and console errors.

  • Open the page as administrator and as an ordinary user to verify the permission boundary.
  • Remove or deny one undeclared tag deliberately and confirm the page shows a clear empty/error state instead of a blank page.
  • Simulate bad or stale quality and confirm the page does not display it as a normal value.
  • Verify history range, aggregate meaning, units, decimals, and chart explanations.
  • Verify writes include confirmation, failure messages, and an audit reason.
  • Open the browser console and confirm there are no SDK initialization failures, 404s, JSON parse errors, or unhandled Promises.
try {
  const history = await api.history.query({
    tags: ["Line1.Output"],
    start,
    end,
    interval: "1h",
    aggregate: "sum"
  });
  renderTrend(history.items || []);
} catch (err) {
  renderEmptyState("History query failed: " + err.message);
}
HQControl custom-page SDK report example
Figure 1: Custom-page SDK report example

This illustrative image shows how custom HTML can organize realtime snapshots, history queries, alarm summaries, and table helpers into a project report page. Page styling and business calculations are implemented by project code.