Skip to content

Page SDK: quick start

Upload HTML to Screen Files and open it from a runtime menu. Runtime loads the SDK; no npm package or extra script is needed. Call ready() once and reuse api.

Developer guide

Developer guide: Page SDK: quick start

Use the Page SDK to read Tags, write setpoints, and query alarms and trends. The HTML and Vue examples require project Tag codes and permission checks before publishing.

How the SDK loads

Upload HTML to Screen Files and open it from a runtime menu. Runtime loads the SDK; no npm package or extra script is needed. Call ready() once and reuse api.

JavaScriptJavaScript
HQVISU_CUSTOM_PAGE.ready().then(async (api) => {
  // Use the examples below here. Reuse this api object.
}).catch(error => console.error(error.message));

Expected result In plain HTML, place the script at the end of body, after the page elements. Add selected snippets to the same async callback and reuse api. Standalone Vite servers, file://, and other sites do not provide this runtime.

Declare the Tags your page uses

Put this declaration in the entry HTML head. Replace <device-code>.<tag-code> with the actual full code. Use writableTags: [] for a read-only page. For Vue, put it in index.html.

HTMLHTML
<script>window.HQVISU_PAGE_META = { layoutMode: "native" };</script>
<script type="application/json" id="hqvisu-custom-page">
{
  "readableTags": ["<device-code>.<tag-code>"],
  "writableTags": ["<device-code>.<tag-code>"]
}
</script>

Expected result layoutMode: "native" lets plain HTML/Vue use a responsive viewport layout. readableTags declares reads; writableTags declares writes. A declaration does not grant control permissions to the account.

Read a Tag

Add <output id="value"></output>. Read numbers with ReadTagDouble and use null for unavailable data. Use ReadTagInteger, ReadTagBoolean, or ReadTagString for other types.

JavaScriptJavaScript
const value = api.ReadTagDouble("<device-code>.<tag-code>", null);
document.getElementById("value").textContent = value ?? "—";

Expected result The call synchronously reads the latest collected value without polling the PLC. Display null as “—”, not a real zero.

Update when the value changes

Add <output id="value"></output> to the HTML and use subscribe to refresh it. No setInterval polling is needed.

JavaScriptJavaScript
const tag = "<device-code>.<tag-code>";
function renderValue() {
  const value = api.ReadTagDouble(tag, null);
  document.getElementById("value").textContent = value ?? "—";
}
renderValue();
const stop = api.subscribe([tag], renderValue);
// Call stop() when this view is removed.

Expected result The value renders immediately and updates on change. Call stop() when the view is removed.

Write a Tag once from a button

Add <button id="write" disabled>Write</button> and <pre id="result"></pre>. This writes 12.5 once; replace it with an allowed value. Declare the writable Tag, enable its control flag and use an account with tag:write permission.

JavaScriptJavaScript
const button = document.getElementById("write");
const output = document.getElementById("result");
button.disabled = !(api.supports("tag.write") && await api.auth.hasPermission("tag:write"));
button.onclick = async () => {
  if (button.disabled) return;
  const tag = "<device-code>.<tag-code>";
  if (!api.ReadTagQualityGood(tag)) { output.textContent = "Tag unavailable"; return; }
  if (!confirm("Write 12.5?")) return;
  button.disabled = true;
  try {
    const result = await api.WriteTagDouble(tag, 12.5, { reason: "Operator setpoint update" });
    const done = result?.accepted === true && result?.status === "done"
      && result?.outcomeUncertain !== true && result?.operatorVerificationRequired !== true
      && result?.transportOnly !== true;
    output.textContent = (done ? "Command completed; verify collected value: " : "Result unconfirmed; verify before another write: ")
      + JSON.stringify(result ?? null);
  } catch (error) {
    output.textContent = "Result unconfirmed: " + error.message + " " + (error.commandId || error.result?.commandId || "");
  }
  // Reconcile the result before another write. Never retry automatically.
};

Expected result Use WriteTagInteger or WriteTagBoolean for those types. This example submits once per page instance, then disables the button; done still requires later normal collection. Reloading resets the button. Reconcile the prior result before another action; persist pending-verification state if blocking must survive reloads.

Query active alarms once

Add <div id="alarms"></div> and <p id="alarm-status"></p>. getActive queries active alarms and renderTable displays items.

JavaScriptJavaScript
const alarms = await api.alarms.getActive({ limit: 100 });
api.renderTable("#alarms", alarms.items);
document.getElementById("alarm-status").textContent = alarms.hasMore ? "More records available" : "";

Expected result This queries once. For live lists, use alarms.subscribe to trigger a new query, without appending callback data; unsubscribe on exit. Empty items means no alarms in scope; hasMore=true means more records. Configure scope for other devices or groups.

Read the last hour of alarm history

Add <div id="alarm-history"></div> and <p id="history-status"></p>. Use alarms.queryHistory to display historical events in a separate table.

JavaScriptJavaScript
const alarmEnd = new Date();
const alarmHistory = await api.alarms.queryHistory({
  start: new Date(alarmEnd.getTime() - 3600000).toISOString(),
  end: alarmEnd.toISOString(),
  limit: 100
});
api.renderTable("#alarm-history", alarmHistory.items);
document.getElementById("history-status").textContent = alarmHistory.hasMore ? "More records available" : "";

Query trend data

Enable Tag archiving and add <div id="trend"></div> and <p id="trend-status"></p>. This displays minute averages for the last hour as a table; pass the same points to your chart component to draw a curve.

JavaScriptJavaScript
const trendEnd = new Date();
const trend = await api.history.query({
  tags: ["<device-code>.<tag-code>"],
  start: new Date(trendEnd.getTime() - 3600000).toISOString(),
  end: trendEnd.toISOString(), interval: "1m", aggregate: "avg"
});
const points = (trend.items[0]?.points || []).map(point => ({
  time: point.t, value: point.quality === "good" ? point.value : null
}));
api.renderTable("#trend", points);
document.getElementById("trend-status").textContent = trend.effectiveInterval;

Expected result No archive produces empty data. Keep gaps for bad-quality points using null. effectiveInterval reports the actual interval.

Open an equipment popup

Save a template in Popup Templates, add <button id="open-popup">Open equipment</button>, then bind its click. Replace the device, template and declared Tag codes.

JavaScriptJavaScript
document.getElementById("open-popup").onclick = () => {
  try {
    api.dialog.openDevicePopup({
      plc: "<device-code>",
      templateCode: "<popup-template-code>",
      focusTagNames: ["<device-code>.<tag-code>"]
    });
  } catch (error) { console.error(error.message); }
};

Expected result The template defines displayed values, controls, alarms and trends. Dispatching the popup is not a write receipt; verify the visible popup and target values.

Navigate to another page

Choose one call for your button click handler. Relative paths start from the current screen; parent, child and other folders are allowed within the assets root.

JavaScriptJavaScript
// Use one of these calls in your button's click handler:
api.navigation.gotoPage("details/equipment.html");   // Child page
api.navigation.gotoPage("../overview.html");         // Parent page
api.navigation.gotoPage("/assets/<folder>/index.html"); // Another folder
api.navigation.openUrl("https://<server-host>/manual", { openInNewWindow: true });

Follow the system language

The top-bar LANG control already switches languages. Give your title id="title", read the current language and subscribe to changes.

JavaScriptJavaScript
function renderText() {
  const zh = api.language.current().startsWith("zh");
  document.getElementById("title").textContent = zh ? "设备总览" : "Equipment overview";
}
renderText();
const stopLanguage = api.language.onChange(renderText);
// Call stopLanguage() when this view is removed.

Use the same API in Vue

In Vue 3, add layout and Tag declarations to index.html, then save and mount this complete read-only component. Obtain api in onMounted. Runtime loads the SDK after publishing.

VueVue
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const value = ref(null), error = ref("");
let stop, disposed = false;
onMounted(async () => {
  try {
    const api = await window.HQVISU_CUSTOM_PAGE.ready();
    if (disposed) return;
    const tag = "<device-code>.<tag-code>";
    const refresh = () => { value.value = api.ReadTagDouble(tag, null); };
    refresh();
    stop = api.subscribe([tag], refresh);
  } catch (err) { if (!disposed) error.value = err.message; }
});
onUnmounted(() => { disposed = true; stop?.(); });
</script>
<template>
  <p v-if="error">{{ error }}</p>
  <output v-else>{{ value ?? "—" }}</output>
</template>

Expected result Set Vite base: "./" and upload dist contents with their folder structure. On unmount, cancel only component subscriptions; do not call api.destroy(). Here api is local to onMounted; retain it in component scope for buttons, as in the full Vue write example.

Complete HTML: replace Tags, then publish

Start from this complete read-only page and replace both occurrences of the Tag code. It uses the SDK supplied by Runtime.

View complete source
HTMLHTML
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mingcedar live value</title>
<script type="application/json" id="hqvisu-custom-page">
{"readableTags":["<device-code>.<process-tag-code>"],"writableTags":[]}
</script>
<script>window.HQVISU_PAGE_META = { layoutMode: "native" };</script>
<h1>Live value</h1>
<p id="status" role="status">Connecting…</p>
<output id="value">Waiting for data</output>
<script>
(async () => {
  const tag = "<device-code>.<process-tag-code>";
  const status = document.getElementById("status");
  const output = document.getElementById("value");
  const api = await HQVISU_CUSTOM_PAGE.ready({timeoutMs: 10000});
  function refresh() {
    const quality = api.ReadTagQuality(tag);
    const value = api.ReadTagDouble(tag, null);
    status.textContent = "Connected — quality: " + quality;
    output.textContent = quality === "good" && value !== null
      ? String(value) : "Unavailable";
  }
  const stop = api.subscribe([tag], refresh);
  refresh();
  window.addEventListener("pagehide", event => {
    if (!event.persisted) { stop(); api.destroy(); }
  });
})().catch(error => {
  document.getElementById("status").textContent = error.message;
});
</script>
</html>

Publish it to the runtime

  1. Configuration → Screen Files: upload HTML, or the Vue dist contents with folders intact.
  2. Configuration → Menus: choose Page (open HTML directly) and select the uploaded HTML.
  3. Open it from the runtime menu. Verify the Tag value, then the required actions. Republish and refresh after edits.

Troubleshooting

  • HQVISU_CUSTOM_PAGE is undefined: open the page from the current Runtime menu; standalone websites do not provide the SDK.
  • A read returns null: check the declaration, full Tag code and quality.
  • Writes are unavailable: check writableTags, account permission and the Tag control flag. Reconcile unknown outcomes before another operation.
  • For more types and options, use the API reference.

Page delivery information

For development and acceptance, provide actual Tag codes and types, allowed write values, required page behavior, and the target runtime menu. The entry HTML declarations must match the page code.

  • List the Tag, alarm, and history scopes. For each write target, specify account permission, control access, and the equipment's allowed values.
  • Open the page from the runtime menu and check SDK initialization, GOOD quality, system LANG changes, and subscription cleanup on component unmount.
  • Check write receipts against subsequent normal collected values. Preserve command identity for uncertain outcomes and reconcile before the next operation.
  • Complete missing codes, types, or authorization scopes before publishing.

Click the image again or press Esc to close