Skip to content

Show live values in a dashboard

This guide shows parameter readings in a dashboard you develop locally. The hooks and components it uses are described in Live data in dashboards. For writes, see Write parameter values from a dashboard.

These two files form a complete dashboard that shows one live reading.

  1. Declare the binding in config.ts. parameterId is the UUID of an existing parameter, and units its engineering unit, such as °C:

    import Type from "typebox";
    export const dashboardConfigType = Type.Object(
    {
    parameterId: Type.Optional(Type.String({ format: "uuid" })),
    units: Type.Optional(Type.String()),
    },
    { additionalProperties: false },
    );
  2. Read the value in index.tsx:

    import { formatAppDateTime } from "@overvis/hmi-shared";
    import {
    parseDashboardConfiguration,
    type DashboardRootProps,
    } from "@overvis/hmi-shared/dashboard";
    import {
    DashboardConfigurationRequired,
    ParameterDiagnostics,
    ParameterValueDisplay,
    useParameterValue,
    useParameterValues,
    } from "@overvis/hmi-workspace-ui";
    import { useTranslation } from "@overvis/hmi-workspace-ui/i18n";
    import type { ReactElement } from "react";
    import { dashboardConfigType } from "./config.ts";
    export { dashboardConfigType } from "./config.ts";
    export const supportedLocales = ["en", "pl", "de", "uk", "ru"] as const;
    export default function ReadingDashboard(props: DashboardRootProps): ReactElement {
    const config = parseDashboardConfiguration(dashboardConfigType, props.configuration);
    const id = config.parameterId;
    const demand = useParameterValues({
    parameterIds: id == null ? [] : [id],
    intervalMs: 5_000,
    });
    const state = useParameterValue(id ?? "");
    const t = useTranslation();
    if (id == null) {
    return (
    <DashboardConfigurationRequired
    workspaceId={props.workspaceId}
    dashboardId={props.dashboardId}
    title="Reading"
    description="Choose the parameter this dashboard shows."
    missing={[{ path: "configuration", fields: ["parameterId"] }]}
    exampleConfiguration={{ parameterId: "<parameter-uuid>", units: "°C" }}
    />
    );
    }
    const receivedAt =
    state.status === "value" ? formatAppDateTime(state.snapshot.receivedAt) : null;
    return (
    <main className="grid gap-3 p-4">
    <div className="flex items-baseline gap-2 text-2xl">
    <ParameterValueDisplay state={state} locale={props.locale} />
    {state.status === "value" ? <span>{config.units}</span> : null}
    </div>
    {state.status === "value" && receivedAt != null ? (
    <p className="text-sm text-muted-foreground">
    {state.freshness === "stale"
    ? t("parameter_value_stale_since", { when: receivedAt })
    : receivedAt}
    </p>
    ) : null}
    <ParameterDiagnostics
    diagnostics={demand.diagnostics}
    onRetry={demand.retry}
    workspaceId={props.workspaceId}
    dashboardId={props.dashboardId}
    />
    </main>
    );
    }
  3. Push the dashboard and set parameterId in its settings.

Until parameterId is set, the dashboard registers no demand, and DashboardConfigurationRequired lists the missing key with an example configuration. It offers Open dashboard settings only to members who can open Setup. ParameterDiagnostics shows permission failures and owns Retry, so the dashboard needs no Retry button of its own. The example passes a defined callback to onRetry, which is compatible with exactOptionalPropertyTypes.

  1. Bind parameters through a strict TypeBox dashboardConfigType, not through UUIDs in the source. The same source then works in another workspace after the configuration is changed.
  2. Use named keys grouped by topic, such as parameters.readings, parameters.thresholds, and parameters.commands. Use a generic list of UUIDs only when the UI itself is generic.
  3. While a required binding is missing, render a setup state, such as DashboardConfigurationRequired, and register no demand.
  4. Take units from parameter metadata or from an explicit configuration key. Never infer a unit from a parameter name.
  5. Take workspaceId and dashboardId from the props. Do not copy them into the configuration.
  1. Call useParameterValues once per cohort, not once per tile. Do not write your own polling effect or a second tRPC client.
  2. Put quickly changing visible values in a fast cohort, and settings or identity values in a slow cohort. Pick each cadence by how fast the process changes; typical plant views use 1 to 5 seconds.
  3. Read each value with useParameterValue(id) where it is shown. It adds no network demand. Add useParameterValueHistory or useParameterValueActions only where a view needs samples or writes.
  4. To combine the cohorts for ParameterLiveControls and ParameterDiagnostics, pass their results to combineParameterValuesDemands.
  5. To pause reading, pass enabled: false. Values already read stay on screen.

A static dashboard, or a one-time report-like page, calls no useParameterValues; the host’s provider then stays inert.

  1. Until a value or a structured failure arrives for a parameter, show a skeleton. ParameterValueDisplay does this; in custom markup, test isParameterValuePending(state). While device availability is not confirmed, keep showing it as loading, not as missing or offline.
  2. After a successful value, keep it visible during refreshes.
  3. When a later read fails, mark the value stale at once, with its unchanged received-at time. parameterStatesStaleSince(states) gives the time for one “stale since” note.
  4. Render the current diagnosis once, in ParameterDiagnostics. Do not repeat a grouped failure beside every stale value.
  5. Build your own error text from code, reason, and the structured params. Never display or parse English error.message or params.detail.
  6. When a reading times out, tell the operator to check the device’s power, wiring, unit ID, serial settings, and response. Do not say that the device is offline.

A value kept from an earlier read may no longer be on the device.

  1. Decide whether to offer a write, color a tile, or count an alarm only on a current value: isCurrentParameterValue(state), or a reader with { current: true } such as parameterValueNumber(state, { current: true }).

For custom controls and SVG bindings, read typed values with parameterValueNumber, parameterValueBoolean, parameterValueFlag, or parameterValueTime. Do not parse the displayed text.

  1. Load the closed buckets with useStoredParameterHistory, using a window from storedHistoryWindows or your own. Pass enabled: false while the trend is hidden.
  2. To reach the current time, collect live values with useParameterValueHistory, sized with liveTailPoints, and append them with withLiveTail(points, live, cadenceMs).
  3. Draw the points with ParameterTrendChart or Sparkline. Set the gap threshold to about twice the bucket length.
  4. When recorded is false, say that the parameter’s readings are not stored, instead of showing an empty chart.

When a parameter has no live value yet, for example while its device answers intermittently, show the last reading the server stored.

  1. Query readings.segments through trpc with segmentCount: 1 and a window that ends now. Take the point with the greatest time, and keep that time with the value. Trend points from useStoredParameterHistory are bucket averages, so they do not serve here.
  2. Skip a reading older than the dashboard allows; the result can hold one from before from. Show nothing when no reading is left.
  3. Mark the value as last stored and show its time. It is not current: offer writes, color tiles, and count alarms only from live values.

Subscribe to endpoint.watchStatus for connection indicators. Do not infer connectivity from parameter failures.

  1. Call other procedures through trpc from @overvis/hmi-workspace-ui, such as parameter.listInventory for a wiring picker.
  2. Do not poll catalog queries or subscribe to workspace.watchChanges: the host refreshes setup queries, including the dashboard’s own configuration, when the setup changes.
  3. Do not call dashboard.get to read the configuration. It is already in the props, and the workspace is available through useWorkspace().

The host’s provider serves ordinary plant dashboards. When a subsystem needs its own demand or lifecycle, mount a nested ParameterValuesProvider around it.

Call tRPC directly only when the coordinator cannot express the workflow. Keep the coordinator’s behavior then: progressive results, no overlapping reads, cancellation, a pause while the page is hidden, structured diagnoses, stale values kept, and batched writes.