Перейти до вмісту

Show device state from the device API

Цей контент ще не доступний вашою мовою.

A dashboard can read the device API of an MC252 or MC261L on a PIN endpoint directly, without parameters in Setup. This guide builds a panel with the device’s network state and a clock synchronization button. The hooks are described in Dashboards: device API.

  1. Add the endpoint to the dashboard configuration in config.ts:

    import Type from "typebox";
    export const dashboardConfigType = Type.Object(
    { endpointId: Type.Optional(Type.String({ format: "uuid", title: "MC252 endpoint" })) },
    { additionalProperties: false },
    );
  2. Read the operation with useDeviceApiRead, not with trpc.device.api.invoke in an effect. Shape the answer with select. Set refreshMs only for values that change, and no shorter than the dashboard needs; each read counts toward the operation’s rate limit, which every viewer of the endpoint shares.

  3. Check the endpoint with useDeviceApi(endpointId). When its status is unsupported, hide the device panel. When a read is unavailable, show a message instead of the value.

  4. Show deviceApiErrorText(error) for a failed read.

  1. Call the operation with useDeviceApiAction, and show the button only while available is true. The viewer’s role decides which operations they get.
  2. Before a call that is hard to undo, ask for confirmation in the dashboard, for example with AlertDialog from @overvis/hmi-workspace-ui/ui.
  3. Show deviceApiErrorText(error) when the call fails.

This dashboard shows the Ethernet and VPN state of an MC252 every 30 seconds. Editors, admins and owners also get a button that resynchronizes the device clock; operators do not see it, because time.sync needs an editor. index.tsx:

import {
parseDashboardConfiguration,
type DashboardRootProps,
} from "@overvis/hmi-shared/dashboard";
import {
deviceApiErrorText,
useDeviceApiAction,
useDeviceApiRead,
} from "@overvis/hmi-workspace-ui";
import { Button, Card, CardContent, CardHeader, CardTitle } from "@overvis/hmi-workspace-ui/ui";
import type { ReactElement } from "react";
import { dashboardConfigType } from "./config.ts";
export { dashboardConfigType } from "./config.ts";
export const supportedLocales = ["en"] as const;
const member = (value: unknown, key: string): unknown =>
typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined;
const text = (value: unknown): string =>
typeof value === "string" || typeof value === "number" ? String(value) : "–";
/** The parts of the `state.get` answer this dashboard shows. */
function toState(response: unknown) {
const ethernet = member(response, "ethernet");
const vpn = member(response, "vpn");
return {
linkUp: member(ethernet, "linkUp") === true,
ip: text(member(ethernet, "ip")),
vpnState: text(member(vpn, "linkState")),
vpnIp: text(member(vpn, "netifIp")),
};
}
export default function Mc252StateDashboard(props: DashboardRootProps): ReactElement {
const { endpointId } = parseDashboardConfiguration(dashboardConfigType, props.configuration);
const state = useDeviceApiRead({
endpointId,
operation: "state.get",
input: { query: { ethernet: true, vpn: true } },
refreshMs: 30_000,
select: toState,
});
const sync = useDeviceApiAction({ endpointId, operation: "time.sync" });
if (state.status === "unavailable")
return <p className="p-4">No device state for this endpoint.</p>;
return (
<main className="grid gap-4 p-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Ethernet</CardTitle>
</CardHeader>
<CardContent>
{state.data?.linkUp === true ? "Link up" : "No link"} · {state.data?.ip}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>VPN</CardTitle>
</CardHeader>
<CardContent>
{state.data?.vpnState} · {state.data?.vpnIp}
</CardContent>
</Card>
{state.error != null ? (
<p className="text-sm text-destructive md:col-span-2">{deviceApiErrorText(state.error)}</p>
) : null}
{sync.available ? (
<div className="flex items-center gap-3 md:col-span-2">
<Button
disabled={sync.isPending}
onClick={() => void sync.call().catch(() => undefined)}
>
Synchronize clock
</Button>
{sync.error != null ? (
<span className="text-sm text-destructive">{deviceApiErrorText(sync.error)}</span>
) : null}
</div>
) : null}
</main>
);
}

To translate the labels, follow Localize a dashboard.

lorasens.journal.read on an MC261L returns up to five journal files of sensor records received after after: from, till, and records. To read the next page, read again from the returned till:

const [after, setAfter] = useState<string | null>(null);
const journal = useDeviceApiRead({
endpointId,
operation: "lorasens.journal.read",
input: after == null ? {} : { query: { after } },
select: (response) => {
const till = member(response, "till");
const records = member(response, "records");
return {
till: typeof till === "string" ? till : null,
records: Array.isArray(records) ? records : [],
};
},
});
// Show journal.data?.records; a "Newer records" button calls setAfter(journal.data.till).

Going back to an earlier page shows it without another call while it is fresh.

The assistant’s dashboard preview calls read operations only. A write there fails with push_agent_key, so test actions in the published dashboard.