# @vention/machine-logic-ui-sdk 中文文档

规范地址：https://npmcn.com/package/%40vention/machine-logic-ui-sdk/

版本：0\.47\.6
npm 元数据更新时间：2026-11-02T19:44:51.196Z
中文译文更新时间：2026-11-02T20:00:56.200Z
软件包许可：MIT

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40vention/machine-logic-ui-sdk
原文对照：https://npmcn.com/package/%40vention/machine-logic-ui-sdk/?lang=original
源码仓库：https://github.com/VentionCo/machine-cloud

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：254 kB，14 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：3 个
- peer 依赖：react ^17\.0\.2 \|\| ^19\.3\.0，@mui/material ^5\.16\.7
- 首次发布：2024-08-26T18:47:59.670Z
- 最新版本发布：2026-11-02T19:44:51.004Z
- 已发布版本数：230（近 12 个月 186 个）

## 中文简介

暂无中文简介

## 安装

```sh
npm install @vention/machine-logic-ui-sdk
```

也可以使用：`pnpm add @vention/machine-logic-ui-sdk`、`yarn add @vention/machine-logic-ui-sdk`、`bun add @vention/machine-logic-ui-sdk`

## README

# @vention / machine-logic-ui-sdk

用于MachineLogic应用程序的自定义HMI的浏览器助手，应用程序中的React应用程序 `customui/` 挂件和控制中心在iframe中加载的文件夹。它为HMI提供了来自机器的MQTT代理的实时数据、执行引擎和应用程序自己的后端进程的url、用于触摸挂件的屏幕上键盘，以及关闭HMI或将控制中心移动到另一个页面的消息。将它安装在应用程序的确切版本上 `customui/package.json`：

```bash
npm install @vention/machine-logic-ui-sdk@<pinned-version>
```

人机界面需要什么？

-   反应17或19。包的 `peerDependencies` 接受 `react` `^17.0.2 || ^19.3.0`。SDK针对两个版本进行类型检查 `typecheck-react19` 目标);它的单元测试运行在React 17上。
-   `@mui/material` 和 `@emotion/react`。包条目导入 `tss-react/mui` (用 `VirtualKeyboard`)，因此导入任何导出都会将其拉入。
-   一个处理CSS导入的打包器，比如Vite。条目导入 `react-simple-keyboard/build/css/index.css`.

在平台-产品单线程中，也可以通过 `@ventionco/machine-logic-ui-sdk` 通过TypeScript路径别名。应用程序安装已发布的 `@vention/machine-logic-ui-sdk`.

每个导出的参数、prop和返回类型都在包的类型声明中，您的编辑器可以使用这些类型声明 `tsc` 阅读。在那里寻找签名；这个自述文件只涵盖了类型不能显示的行为。

## MQTT上的实时数据

将应用程序包装一次 `MqttProvider`。它打开一个MQTT-over-websocket连接 `useMqttSubscription` 呼叫下面它共享，并重新连接自己。

-   `version` 默认为 `"v2"` 并选择哪个代理地址派生，通过 `getMqttWsUrl`。在边缘，两者都使用未加密的websocket： `"v2"` 连接到 `192.168.5.2:9001` 和 `"v3"` 连接端口 `9001` 在服务HMI的主机上。在云框架中，两者使用相同的 `wss://` URL: `wss://<the HMI's host><mqttPath>` 当主机页面给出 `mqttPath` 查询以。开头的参数 `/`，否则从iframe URL派生的URL。
-   `url` 自己设置代理websocket URL。如果设置了, `version` 和URL检测被跳过。例如，将其用于独立开发 `ws://localhost:9001`.

从 `projects/machine-code/apps/ai-operator/frontend/src/main.tsx`修剪:

```tsx
import { StrictMode } from "react"
import ReactDOM from "react-dom"
import { MqttProvider } from "@vention/machine-logic-ui-sdk"

import App from "./app/app"

ReactDOM.render(
  <StrictMode>
    <MqttProvider version="v3">
      <App />
    </MqttProvider>
  </StrictMode>,
  document.getElementById("root")
)
```

对于iframe之外的开发，您可以自己传递代理，例如使用 `VITE_MQTT_WS_URL=ws://localhost:9001`。如果不设置，提供者将返回到检测：

```tsx
<MqttProvider version="v3" url={import.meta.env.VITE_MQTT_WS_URL}>
  <App />
</MqttProvider>
```

阅读一个主题 `useMqttSubscription`。它返回关于该主题的最新消息，或者 `undefined` 直到第一个人到来。对每个主题进行一次代理订阅，因此只有第一个读取主题的组件才能获得保留的消息；稍后为同一主题挂载的组件将返回 `undefined` 直到下次出版。

-   `topic` 必须是一个准确的主题。钩子将每个传入主题与此字符串进行比较 `===`，所以通配符过滤器如 `operation/bins/+` 订阅代理，但从不更新返回值。使用 `MqttClient` 通配符。
-   `unsubscribeOnUnmount`，默认关闭，在组件卸载时在代理上取消订阅。代理订阅是跨提供者为每个主题共享的，因此这也会为仍在读取该主题的任何其他组件停止该主题。每当连接状态改变时，清理也会运行，并且钩子在连接后再次订阅。
-   每个有效载荷都通过 `JSON.parse`。在HMI读取的主题上发布JSON；无效JSON的有效负载会在消息处理程序中抛出。type参数是一个断言，而不是运行时检查。
-   没有订阅，直到提供者的 `connectionStatus` 是 `"connected"`。在重新连接之后，钩子再次订阅。

从 `projects/machine-code/apps/ai-operator/frontend/src/app/contexts/operation-context.tsx`修剪:

```tsx
import { useEffect, useState } from "react"
import { useMqttSubscription } from "@vention/machine-logic-ui-sdk"

interface BinStatus {
  cardState: "normal" | "missing"
  pickedCount?: number
  averageCycleTime?: number
}

export const OperationStatus = () => {
  const [isOperationRunning, setIsOperationRunning] = useState(false)

  const operationActive = useMqttSubscription<boolean>({
    topic: "operation/operationActive",
    unsubscribeOnUnmount: true,
  })

  const bin1Status = useMqttSubscription<BinStatus>({
    topic: "operation/bins/1",
    unsubscribeOnUnmount: true,
  })

  useEffect(
    function handleOperationActiveFromMqtt() {
      if (operationActive === undefined) return
      setIsOperationRunning(operationActive)
    },
    [operationActive]
  )

  return (
    <p>
      {isOperationRunning ? "Running" : "Stopped"}, bin 1 picked {bin1Status?.pickedCount ?? 0}
    </p>
  )
}
```

`mqttContext` 是提供程序填充的React上下文。阅读它 `useContext(mqttContext)`：

-   `client`：供应商的 [mqtt.js](https://github.com/mqttjs/MQTT.js) `MqttClient` (不是这个包裹 `MqttClient` 类),或 `null` 在第一次连接之前。用它来发布。
-   `connectionStatus`：提供者的连接状态。
-   `subscribedTopics`， `addSubscribedTopic`， `removeSubscribedTopic`：供应商的记账 `useMqttSubscription`。应用程序不需要它们。

```tsx
import { useContext } from "react"
import { mqttContext } from "@vention/machine-logic-ui-sdk"

export const StopButton = () => {
  const { client, connectionStatus } = useContext(mqttContext)

  return (
    <button disabled={connectionStatus !== "connected"} onClick={() => client?.publish("operation/command", JSON.stringify({ action: "stop" }))}>
      Stop
    </button>
  )
}
```

## 输入主题

这个 `MqttPayloadByTopic` Type在主题映射中查找主题的有效负载类型。将应用程序的主题声明为该映射：

```ts
import { MqttPayloadByTopic, useMqttSubscription } from "@vention/machine-logic-ui-sdk"

type OperationTopics = {
  "operation/operationActive": boolean
  "operation/bins/1": { cardState: "normal" | "missing"; pickedCount?: number }
}

type BinStatus = MqttPayloadByTopic<OperationTopics, "operation/bins/1">

export function useOperationTopic<Topic extends keyof OperationTopics & string>(topic: Topic) {
  return useMqttSubscription<MqttPayloadByTopic<OperationTopics, Topic>>({ topic, unsubscribeOnUnmount: true })
}

// useOperationTopic("operation/operationActive") returns boolean | undefined
```

## 原始客户端

`MqttClient` 是mqtt.js的一个小包装器，用于React之外的代码或通配符订阅。

-   构造函数立即连接。它的可选回调在每个成功的连接上运行，包括在重新连接之后。
-   `connect` 是构造函数调用的。再次调用它会打开第二个连接；应用程序不需要这样做。
-   `subscribe(topic, regex, callback)` 订阅 `topic` 在代理（MQTT通配符） `+` 和 `#` 是允许的)和呼叫 `callback` 对于主题匹配的每个传入消息 `regex`.
-   `unsubscribe(topic, regex, callback)` 移除那个回调，并取消订阅 `topic` 在代理上，一旦没有剩余的回调使用相同的 `regex`.
-   `publish` 发布字符串，除非传递，否则不保留 `retain`。序列化对象 `JSON.stringify` 你自己。
-   一、 `Callback` 以字符串形式接收主题和原始有效负载；负载没有被解析。

在第一个连接打开之前进行的调用将排队，并在第一个连接打开后发送。关闭后，客户端在重新连接之前被断开约一秒钟；在这段时间里 `publish` 无声地落下 `subscribe` 永远不会到达代理，并且一旦重新连接，客户端不会重新发送在旧连接上进行的代理订阅。对于长期使用的React HMI，首选 `MqttProvider` 和 `useMqttSubscription`，重新连接后重新订阅。

```ts
import { Callback, MqttClient, getMqttWsUrl } from "@vention/machine-logic-ui-sdk"

const BIN_TOPIC_FILTER = "operation/bins/+"
const BIN_TOPIC_PATTERN = /^operation\/bins\/\d+$/

const onBinStatus: Callback = (topic, message) => {
  const status = JSON.parse(message)
  console.log(topic, status.pickedCount)
}

export async function watchBins(): Promise<MqttClient> {
  const client = new MqttClient(await getMqttWsUrl("v3"), () => console.log("MQTT connected"))
  client.subscribe(BIN_TOPIC_FILTER, BIN_TOPIC_PATTERN, onBinStatus)
  client.publish("operation/command", JSON.stringify({ action: "start" }))
  return client
}

// Later: client.unsubscribe(BIN_TOPIC_FILTER, BIN_TOPIC_PATTERN, onBinStatus)
```

URL助手:

-   `connectToMqttAndGetClient` 构建一个客户端 `await getMqttWsUrl()`，也就是 `"v2"` 代理地址。它不需要争论；为 `"v3"` 构造 `new MqttClient(await getMqttWsUrl("v3"))` 代替。
-   `getMqttWsUrl` 返回代理websocket URL `version`，默认为 `"v2"`。当 `isOnEdge()` 为true，则返回一个未加密的websocket URL： `192.168.5.2:9001` 用于 `"v2"` 和端口 `9001` 的当前主机名 `"v3"`。否则，当HMI的URL有 `mqttPath` 以开头的查询参数 `/` 当SDK加载时，由主机页面设置，它返回 `wss://<the HMI's host><mqttPath>`。否则它会返回 `getMqttWsUrlGivenUiIframeUrl(window.location.href)`.
-   `getMqttWsUrlGivenUiIframeUrl` 从远程iframe URL派生云代理URL。它将URL保持到并包括 `digital-twin` 路径段、开关 `https:` 来 `wss:`，然后追加后面的路径段 `passthrough` 和 `/mqtt`： `wss://<host>/.../digital-twin/<token>/mqtt`.

## 与应用程序的后端对话

它们返回要传递的普通字符串 `fetch` 或RPC传输，例如 `createConnectTransport({ baseUrl })` 从 `@connectrpc/connect-web`.

-   `getExecutionEngineHttpUrl` 返回执行引擎的基础URL： `http://<current hostname>:3100` 在边上，否则一切都在 `window.location.href` 之前 `/machineCodeUi` （如果该段不存在，则为整个URL）。
-   `getMachineCodeProcessHttpUrl` 接受进程名并返回 `<execution engine URL>/v1/machineCode/services/<processName>`，执行引擎的一个应用程序进程的代理。
-   `getMachineCodeApiBaseUrl` 返回 `getMachineCodeProcessHttpUrl(processName)` 紧随其后的是 `/api`.

```ts
import { getMachineCodeApiBaseUrl } from "@vention/machine-logic-ui-sdk"

export async function fetchStatus(): Promise<unknown> {
  const response = await fetch(`${getMachineCodeApiBaseUrl("my-backend")}/status`)
  return response.json()
}
```

`"my-backend"` 表示注册应用程序后端进程的名称，和 `/status` 对于后端服务的路由。

## 触摸屏和控制中心

`VirtualKeyboard` 是一个没有道具的屏幕键盘组件。渲染一次，靠近布局的根。它监听文档中任何输入的焦点，并在150毫秒后显示出来；当用户按下提交键，或者在键盘和任何输入之外点击时，它隐藏起来。它在隐藏的时候什么也不呈现。可见时，它是 `position: sticky` 面板，视窗高度的45%，在其父面板的底部，它将聚焦的输入滚动到视图中。

-   文本布局包括 `text`， `password`， `email`， `search`， `url`， `month`， `datetime-local`， `time`, `<textarea>`； 数字布局包括 `tel`， `date`, `week`。其他输入类型没有键盘。
-   `type="number"` 不支持：键盘记录警告并将输入更改为 `type="tel"`。使用 `type="tel"` 用于数字字段。
-   值通过本机值设置器写入，后面跟着 `input` 事件，所以React `onChange` 处理程序。一个 `change` 如果值改变，则在模糊时分派事件。

`isTouchScreenDevice` 当 `"ontouchstart" in window` 或者 `navigator.maxTouchPoints > 0`。把键盘关在上面：

```tsx
import { ReactNode } from "react"
import { VirtualKeyboard, isTouchScreenDevice } from "@vention/machine-logic-ui-sdk"

export const AppLayout = ({ children }: { children: ReactNode }) => (
  <div style={{ display: "flex", flexDirection: "column", height: "100vh", overflow: "auto" }}>
    <main style={{ flexGrow: 1 }}>{children}</main>
    {isTouchScreenDevice() ? <VirtualKeyboard /> : null}
  </div>
)
```

`isOnEdge` 当 `window.location.hostname` 开始于 `192.168` 或者 `localhost`这意味着人机界面是通过机器的网络提供的，而不是通过云。上面的URL帮助器在它的基础上进行分支。一个开发服务器 `localhost` 这也是为什么独立开发会过时的原因 `MqttProvider` 一个显式的 `url`.

HMI在iframe中运行。这些帖子给 `window.parent` 主机页要执行的操作：

-   `closeCustomUi` 的帖子 `"closeCustomUi"`，要求主机关闭HMI。
-   `navigateControlCenter` 的帖子 `"navigateToRemoteSupport"` 用于 `"remoteSupport"` 或者 `"navigateToControlCenterHomepage"` 用于 `"controlCenterHomepage"`，将主机移至远程支持页面或控制中心首页。

```tsx
import { closeCustomUi, navigateControlCenter } from "@vention/machine-logic-ui-sdk"

export const ExitBar = () => (
  <nav>
    <button onClick={() => navigateControlCenter("remoteSupport")}>Get help</button>
    <button onClick={closeCustomUi}>Exit</button>
  </nav>
)
```

在框架之外， `window.parent` 是页面本身，因此这些消息不会到达任何地方，除非页面侦听它们。

## 常见问题

### 如何安装 @vention/machine-logic-ui-sdk？

在项目目录运行 npm install @vention/machine-logic-ui-sdk，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（^17\.0\.2 \|\| ^19\.3\.0）、@mui/material（^5\.16\.7）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @vention/machine-logic-ui-sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @vention/machine-logic-ui-sdk 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @vention/machine-logic-ui-sdk 可以商用吗？

@vention/machine-logic-ui-sdk 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @vention/machine-logic-ui-sdk 还在维护吗？

最新版本 0\.47\.6 发布于 2026/11/03（今天）；至今共发布 230 个版本，其中过去 12 个月发布了 186 个；首次发布于 2024/08/27。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

