node-red-contrib-room-tools 1.8.16 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTROL_PANEL_AI_GUIDE.md +250 -109
- package/README.md +3 -1
- package/control-catalog.js +5 -2
- package/control-panel.js +24 -4
- package/control-policy.js +8 -0
- package/control-type-contract.json +1 -0
- package/control-type-model.js +8 -3
- package/managed-native-bridge.js +2 -1
- package/native-binding-plan.js +61 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -2
- package/panel-binding-editor.js +193 -34
- package/panel-meter.js +2 -1
- package/panel-template-instrumentation.js +144 -33
- package/panel-visual-bindings.js +33 -9
- package/panel-xy-series-client.js +84 -0
- package/panel-xy-series.js +186 -0
- package/xy-series-frame.js +98 -0
- package/xy-series-route.js +11 -0
|
@@ -1,125 +1,201 @@
|
|
|
1
1
|
# ControlPanel 前端页面编写指南
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
适用基线:当前统一工程中的 `node-red-contrib-room-tools@1.9.1`、Dashboard 2 和 Vue Options API。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本文件是给页面编写 AI 的实现契约,不是完整的 Vue 或 Vuetify 手册。目标是:页面作者只写普通 HTML、CSS 和 Vue 页面;设备、驱动、映射表、反馈订阅和通信细节全部由 `ControlPanel` 节点处理。
|
|
6
6
|
|
|
7
|
-
## 1.
|
|
7
|
+
## 1. 最重要的规则
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
页面作者只需要:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- 按普通 H5/Vue 方式写布局、样式、导航、控件和页面本地状态。
|
|
12
|
+
- 为需要绑定的控件准备简单、可写、类型正确的 Vue 数据字段。
|
|
13
|
+
- 使用稳定的普通 HTML `id` 或 `name`,并为重复列表使用稳定的 `:key`。
|
|
14
|
+
- 用 `volume`、`muted`、`source`、`meter` 等字段表达页面语义。字段名只是页面模型名,不需要使用任何内部 Control ID。
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
页面作者不需要知道:
|
|
14
17
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
- 为修复反馈而添加设备轮询、强制回写滑杆、重复发送监听器或设备状态定时器。
|
|
18
|
+
- 驱动协议、设备路径、编号方式或 `program.json`。
|
|
19
|
+
- ControlPanel 的映射 JSON、桥接节点、stream、socket、SSE、帧格式或订阅主题。
|
|
20
|
+
- 内部 `data-cp-*` 标记、ControlPanel HTTP API 或设备发送接口。
|
|
21
|
+
- 量程换算、连续值节流、latest-wins、命令队列和反馈重连。
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
不要在页面里实现第二套设备通信。不要写设备轮询、定时回写滑杆、反馈回写命令、重复发送监听器或自定义发送函数。未绑定的控件不会自动控制设备。
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
页面里的 `volume` 和 `meter` 是两个不同语义:
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
- `volume` 等普通数值字段用于音量/增益等可写连续控制,并可接收普通真实值反馈。
|
|
28
|
+
- `meter` 等字段用于只读实时信号电平,只能接收驱动声明了 Meter 元数据的点位。
|
|
29
|
+
- `series`、`spectrum` 等数组字段用于只读实时二维点列,只能接收驱动声明了 XY-series 元数据的点位。
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
不要把同一个模型字段同时用于不同设备点位,尤其不要让 Meter 和 Volume 共用一个模型。
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
## 2. 页面格式与 Vue 约束
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
交付可以直接粘贴到节点 Template 的组件片段:一个外层 `<template>`,可选的 `<script>` 和 `<style>`。不要交付带 `<html>`、`<head>`、`<body>` 的完整文档,也不要交付需要外部构建才能运行的项目。
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
脚本使用 Vue Options API:
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
```js
|
|
40
|
+
export default {
|
|
41
|
+
data () {
|
|
42
|
+
return { volume: 0, muted: false, status: '待机' }
|
|
43
|
+
},
|
|
44
|
+
computed: {},
|
|
45
|
+
methods: {}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
| --- | --- | --- | --- |
|
|
39
|
-
| 连续数值 | `input[type=range]`、`input[type=number]` | `v-slider` | 数字;原生 input/change 发送,Vuetify 模型变化发送并在变化停止后提交最终值。 |
|
|
40
|
-
| 布尔开关 | `input[type=checkbox]`、语义开关按钮 | `v-switch`、`v-checkbox` | 布尔值;切换发送。语义按钮写法见下节。 |
|
|
41
|
-
| 单选枚举 | `select` | `v-select`、`v-radio-group` | 字符串或数字的单个值,必须与设备枚举值对应。 |
|
|
42
|
-
| 文本输入 | `input[type=text]`、`textarea` | `v-text-field`、`v-textarea` | 字符串;原生 change 提交,Vuetify 在 change 或失焦时提交,`v-text-field` 还支持 Enter。 |
|
|
43
|
-
| 普通只读数值 | `output`、文本展示 | 暂无 | 只绑定普通 Feedback point,不发送命令。使用简单顶层字段;不要把普通数值反馈写成 Meter。 |
|
|
44
|
-
| 实时信号电平 Meter | `meter`、`progress` | `v-progress-linear`、`v-progress-circular` | 只绑定驱动明确声明 Meter 元数据的 Feedback point,不发送命令。使用独立的 `meter`/`meter1` 字段;原生 `meter`/`progress` 可使用简单 `v-model` 作为只读别名。 |
|
|
45
|
-
| 只读文本 | `span`、`strong`、`b`、`output`、`h1`、`h2`、`h3` | 暂无 | 使用简单字段插值,如 `{{ status }}`,或由节点替换的纯文本;只绑定读点位。 |
|
|
49
|
+
不要使用 `<script setup>`、组件导入、自行创建 Vue 应用或外部运行时依赖。普通绑定模型应是 `data()` 中的顶层字段,或本指南 3.2 节规定的列表模型。计算属性、函数调用、取反、条件表达式和动态索引不能作为绑定模型。
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
不要手写任何 `data-cp-*` 属性。节点会在渲染副本中自动加入内部标记,页面源码不应依赖这些标记;手写此类属性会被拒绝。
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
不要给绑定控件添加 `@click`、`@input`、`@change`、`@update:model-value` 等事件指令,也不要额外绑定 `:value`、`:checked` 或 `:model-value`。节点需要独占这些事件和模型更新。页面导航、标签切换、纯视觉交互等事件可以放在不绑定设备的独立控件上。
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
- 交互输入控件不额外叠加 `:value`、`:checked`、`:model-value`。只读原生 meter/progress 的 `:value`、Vuetify 进度组件的 `:model-value` 和语义开关按钮的简单 ARIA 布尔绑定是例外。
|
|
53
|
-
- 只允许 range/select 的 `v-model.number`;不要用 `.lazy`、`.trim`、带参数的 `v-model` 或其他修饰符。
|
|
54
|
-
- 待绑定元素不能位于 `v-for` 中,即使写了 `:key`。相同模板会生成多个元素,当前不能按列表项分别绑定。
|
|
55
|
-
- 标签页、`v-if` 等正常布局可以保留。编辑该控件时先让它显示出来,并确保同一定位只出现一个元素。绑定提示会随元素重建恢复。
|
|
56
|
-
- 普通 Vuetify 布局组件可使用,但 `v-btn`、`v-range-slider`、`v-autocomplete`、`v-combobox`、原生 radio 等不在自动设备绑定清单中。不要假定外观相似就支持。
|
|
55
|
+
以下是两个例外:
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
- 只读原生 `<meter>`/`<progress>` 可以使用 `:value`。
|
|
58
|
+
- 只读 `v-progress-linear`/`v-progress-circular` 可以使用简单 `v-model` 或 `:model-value`。
|
|
59
|
+
- 语义开关按钮可以使用简单的 `:aria-pressed` 或 `:aria-checked`。
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
- 原生 range 默认 0–100;原生 meter/progress 默认 0–1。原生 number 没有缺省完整量程;需要自动缩放时明确写 min/max。不要把原生缺省量程误当成设备量程。
|
|
62
|
-
- 原生 `meter`/`progress` 可使用标准 `:value="meter1"`,也可使用简单 `v-model="meter1"` 作为只读别名;节点会在编译前将后者转换为 `:value`。不支持 `v-model.number`、动态表达式或同时写 `:value`。只有驱动点位带有 Meter 元数据时,`meter`/`progress` 才能建立绑定;它由独立的 Meter 批量帧通道更新,不能接收网页设置。音量滑杆的 `volume` 与 Meter 的 `meter` 必须是两个不同字段,不能把同一个字段绑定到两个不同设备点位。普通 volume/level 反馈不能选择为 Meter。Vuetify `progress` 组件仍使用简单 `:model-value`;未绑定的进度条可以作为普通前端视觉元素。
|
|
63
|
-
- Vuetify switch/checkbox 不支持 `value`、自定义 `true-value`/`false-value`、`multiple` 或数组模型。
|
|
64
|
-
- Vuetify select 不支持多选、`return-object`、`clearable` 或对象模型;选项可用标量数组,或普通 `{ title, value }` 选项但模型必须是标量。`v-radio-group` 使用标量模型,子项也不能自带事件处理器。
|
|
65
|
-
- Vuetify 文本控件只用字符串模型。`v-text-field` 使用文本类型,不用数字、密码等类型;不支持 `.number`。`v-textarea` 的 Enter 是换行,不是提交。
|
|
66
|
-
- `v-progress-linear` 从 0 到固定正数 max,默认 max=100;不支持 min、step、indeterminate、clickable。`v-progress-circular` 固定 0–100,不声明 min/max/step,也不使用 indeterminate。
|
|
67
|
-
- 文本展示推荐只放一个简单插值,如 `<span>{{ status }}</span>`,不使用 `status.toFixed()`、拼接表达式或多个插值。复杂展示可用另外的纯前端元素,不把它作为设备反馈绑定目标。
|
|
61
|
+
所有 Vuetify 组件标签都成对闭合,例如 `<v-icon></v-icon>`。Dashboard 的模板渲染可能吞掉自闭合组件后面的内容,节点会在部署前拒绝 `<v-slider />`、`<v-progress-linear />` 等写法。原生 HTML void 元素,如 `<input>` 和 `<img>`,不受此限制。
|
|
68
62
|
|
|
69
|
-
|
|
63
|
+
## 3. 可绑定控件
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
以下是当前适配器支持的范围,不代表任意 Vuetify 或第三方组件都支持。
|
|
72
66
|
|
|
73
|
-
|
|
67
|
+
| 用途 | 原生 HTML | Vuetify | 模型与行为 |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| 连续数值控制 | `input[type=range]`、`input[type=number]` | `v-slider` | 数字;range 在拖动时发送中间值,结束时发送最终值。 |
|
|
70
|
+
| 布尔控制 | `input[type=checkbox]`、带语义属性的 `button` | `v-switch`、`v-checkbox` | 布尔值;切换发送。 |
|
|
71
|
+
| 单选枚举 | `select` | `v-select`、`v-radio-group` | 单个字符串或数字,必须是设备允许的枚举值。 |
|
|
72
|
+
| 文本控制 | `input[type=text]`、`textarea` | `v-text-field`、`v-textarea` | 字符串;原生控件在 change 时提交,Vuetify 文本框在 change、失焦时提交,`v-text-field` 也支持 Enter 提交。 |
|
|
73
|
+
| 普通只读反馈 | `span`、`output` 等简单文本展示 | 暂无专用组件 | 只读普通反馈,不发送命令。 |
|
|
74
|
+
| 实时信号电平 Meter | `meter`、`progress` | `v-progress-linear`、`v-progress-circular` | 只读数值;必须绑定驱动的 Meter 点位,不发送命令。 |
|
|
75
|
+
| 实时二维点列 XY-series | `canvas` + `:data-points` 或 `:data-series`;地图容器 + `:data-coordinates` | 第三方图表暂不自动识别 | 只读 `[[x, y], ...]` 数组;不发送命令。 |
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
交互控件建议写简单顶层 `v-model`。原生 range/select 可以使用 `.number`;不要给布尔、文本或 Meter 加 `.number`。Vuetify 进度组件可以使用简单 `v-model`,也可以使用简单 `:model-value`。原生 Meter/Progress 可以使用简单顶层 `v-model`,节点会把它转换成只读 `:value`;在 `v-for` 列表中应直接写 `:value="item.meter"`,不要写嵌套字段的原生 `v-model`。
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
初始化值应匹配控件类型:数值为数字,开关为布尔值,文本和枚举为字符串。不要用数组、对象或 `null` 作为普通绑定控件的占位值。
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
XY-series 使用数组字段初始化,例如 `spectrum: []`。推荐使用标准 Canvas:
|
|
80
82
|
|
|
81
83
|
```html
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
+
<canvas id="room-spectrum" :data-points="spectrum" aria-label="实时频谱"></canvas>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
地图坐标使用普通 HTML 容器和标准地理坐标顺序 `[经度, 纬度]`:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<div id="room-map" class="room-map"
|
|
91
|
+
:data-coordinates="locations" aria-label="实时位置"></div>
|
|
84
92
|
```
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
`locations` 的值也是完整快照,例如 `[[116.397, 39.908], [121.473, 31.230]]`。节点只负责把驱动的二维数组更新到这个普通 Vue 数组;地图底图、投影、标记和缩放属于页面自己的显示逻辑。当前自动识别支持 `div`、`section`、`article`、`main`、`aside`、`svg` 和 `g` 上的 `:data-coordinates`,不要求页面引入地图 SDK,也不要求页面知道驱动或订阅机制。
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
节点会把每个完整帧更新到 `spectrum` 或 `locations`,页面自己的绘图逻辑可以观察这个普通 Vue 数组。`data-points`、`data-series` 和 `data-coordinates` 都是普通 HTML `data-*` 属性,不是 ControlPanel 私有属性。Canvas 的频谱点列与地图坐标只是页面呈现语义不同,底层使用同一个只读 XY-series 通道。当前自动识别只支持顶层简单字段,不支持 `v-for` 内的二维点列模型、函数调用、表达式、多个数据源合并或第三方图表组件的专用数据格式。
|
|
89
97
|
|
|
90
|
-
###
|
|
98
|
+
### 3.1 普通控件写法
|
|
91
99
|
|
|
92
100
|
```html
|
|
93
101
|
<template>
|
|
94
102
|
<section>
|
|
95
103
|
<label for="room-volume">音量</label>
|
|
96
104
|
<input id="room-volume" type="range" min="0" max="100" step="1" v-model.number="volume">
|
|
97
|
-
|
|
105
|
+
|
|
106
|
+
<label>
|
|
107
|
+
<input id="room-mute" type="checkbox" v-model="muted">
|
|
108
|
+
静音
|
|
109
|
+
</label>
|
|
110
|
+
|
|
98
111
|
<button id="room-power" type="button" :aria-pressed="powered">电源</button>
|
|
112
|
+
|
|
99
113
|
<select id="room-source" v-model="source">
|
|
100
114
|
<option value="A">输入 A</option>
|
|
101
115
|
<option value="B">输入 B</option>
|
|
102
116
|
</select>
|
|
117
|
+
|
|
103
118
|
<span id="room-status">{{ status }}</span>
|
|
104
119
|
<meter id="room-meter" :value="meter" min="0" max="100" aria-label="实时信号电平"></meter>
|
|
105
120
|
<progress id="room-progress" :value="progress" max="100" aria-label="进度"></progress>
|
|
106
121
|
</section>
|
|
107
122
|
</template>
|
|
123
|
+
|
|
108
124
|
<script>
|
|
109
125
|
export default {
|
|
110
126
|
data () {
|
|
111
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
volume: 0,
|
|
129
|
+
muted: false,
|
|
130
|
+
powered: false,
|
|
131
|
+
source: 'A',
|
|
132
|
+
status: '待机',
|
|
133
|
+
meter: 0,
|
|
134
|
+
progress: 0
|
|
135
|
+
}
|
|
112
136
|
}
|
|
113
137
|
}
|
|
114
138
|
</script>
|
|
115
139
|
```
|
|
116
140
|
|
|
117
|
-
###
|
|
141
|
+
### 3.2 `v-for` 列表绑定
|
|
142
|
+
|
|
143
|
+
当前支持一层、来源为 `data()` 顶层数组字段的列表。对象列表使用稳定对象 ID:
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<div v-for="item in items" :key="item.id">
|
|
147
|
+
<span>{{ item.name }}</span>
|
|
148
|
+
<input :id="'volume-' + item.id" type="range"
|
|
149
|
+
min="0" max="100" step="1" v-model.number="item.volume">
|
|
150
|
+
</div>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
也支持带索引别名的对象列表:
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<div v-for="(item, index) in items" :key="item.id">
|
|
157
|
+
<input id="row-volume" type="range" v-model.number="items[index].volume">
|
|
158
|
+
</div>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
数组列表使用索引作为 key:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<input v-for="(value, index) in values" :key="index"
|
|
165
|
+
:id="'value-' + index" type="range" v-model.number="values[index]">
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
对象项可以使用静态嵌套字段,例如 `item.audio.volume` 或 `items[index].audio.volume`。数组项可以使用 `values[index]`。模型路径必须是标识符和静态点路径,不能调用函数、做计算或使用动态属性。
|
|
169
|
+
|
|
170
|
+
一个列表项需要多个并列控件时,可使用内层 `<template v-for>`,但它必须是外层节点 Template 的子节点:
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<template>
|
|
174
|
+
<template v-for="item in items" :key="item.id">
|
|
175
|
+
<input id="row-volume" type="range" v-model.number="item.volume">
|
|
176
|
+
<input id="row-balance" type="range" v-model.number="item.balance">
|
|
177
|
+
</template>
|
|
178
|
+
</template>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
列表规则:
|
|
182
|
+
|
|
183
|
+
- 对象列表必须使用精确的 `:key="item.id"`;数组列表必须使用精确的 `:key="index"`。
|
|
184
|
+
- `item.id` 必须稳定且在当前列表中唯一。重复 key/ID 的实例不会被安全绑定;Vue 重渲染期间短暂出现的重复 DOM 会被节点暂时忽略,key 恢复唯一后自动重新关联,但页面不应依赖这种容错。
|
|
185
|
+
- 列表外的每个绑定控件都应有唯一的普通 `id` 或 `name`。列表内的重复实例可以使用同一个静态 `id`,节点通过列表 key 区分实例;不要为每一项拼接依赖运行时随机值的 ID。
|
|
186
|
+
- 列表控件在编辑页面中按实际渲染出的实例逐项选择。列表增加或删除后,应重新进入编辑器、补齐或移除对应映射,再 Apply。
|
|
187
|
+
- `v-for` 可以写在实际容器上,也可以写在内层 `<template>` 上。不要把 `v-for` 直接写在唯一的外层节点 Template 上。
|
|
188
|
+
- 不支持嵌套 `v-for`、动态列表来源、过滤或计算表达式、复杂 key、slot scope、自定义列表项事件和任意动态索引。
|
|
189
|
+
|
|
190
|
+
`v-if`、标签页切换、普通 DOM 重建和过渡造成的重新渲染可以使用。节点会在元素重新出现后恢复绑定;不要通过 `mounted`、`watch` 或定时器手动补绑或回写模型。
|
|
191
|
+
|
|
192
|
+
### 3.3 Vuetify 控件
|
|
118
193
|
|
|
119
194
|
```html
|
|
120
195
|
<template>
|
|
121
196
|
<section>
|
|
122
|
-
<v-slider id="room-volume" label="音量" v-model="volume"
|
|
197
|
+
<v-slider id="room-volume" label="音量" v-model="volume"
|
|
198
|
+
:min="0" :max="100" :step="1"></v-slider>
|
|
123
199
|
<v-switch id="room-mute" label="静音" v-model="muted"></v-switch>
|
|
124
200
|
<v-select id="room-source" label="输入源" v-model="source" :items="sources"></v-select>
|
|
125
201
|
<v-radio-group id="room-mode" label="模式" v-model="mode">
|
|
@@ -127,82 +203,147 @@ export default {
|
|
|
127
203
|
<v-radio label="模式 B" value="B"></v-radio>
|
|
128
204
|
</v-radio-group>
|
|
129
205
|
<v-text-field id="room-title" label="名称" v-model="title"></v-text-field>
|
|
130
|
-
<v-progress-linear id="room-meter"
|
|
206
|
+
<v-progress-linear id="room-meter" v-model="meter" :max="100"></v-progress-linear>
|
|
131
207
|
<span id="room-status">{{ status }}</span>
|
|
132
208
|
</section>
|
|
133
209
|
</template>
|
|
210
|
+
|
|
134
211
|
<script>
|
|
135
212
|
export default {
|
|
136
213
|
data () {
|
|
137
214
|
return {
|
|
138
|
-
volume: 0,
|
|
139
|
-
|
|
215
|
+
volume: 0,
|
|
216
|
+
muted: false,
|
|
217
|
+
sources: ['A', 'B'],
|
|
218
|
+
source: 'A',
|
|
219
|
+
mode: 'A',
|
|
220
|
+
title: '',
|
|
221
|
+
meter: 0,
|
|
222
|
+
status: '待机'
|
|
140
223
|
}
|
|
141
224
|
}
|
|
142
225
|
}
|
|
143
226
|
</script>
|
|
144
227
|
```
|
|
145
228
|
|
|
146
|
-
|
|
229
|
+
当前 Vuetify 适配器包括 `v-slider`、`v-switch`、`v-checkbox`、`v-select`、`v-radio-group`、`v-text-field`、`v-textarea`、`v-progress-linear` 和 `v-progress-circular`。普通布局组件可以使用,但布局组件本身不是绑定目标。
|
|
230
|
+
|
|
231
|
+
以下写法当前不支持:
|
|
232
|
+
|
|
233
|
+
- `v-btn` 作为自动设备按钮;需要布尔设备控制时使用语义按钮写法。
|
|
234
|
+
- `v-range-slider`、多选、`return-object`、对象模型、数组模型、自定义 `true-value`/`false-value`。
|
|
235
|
+
- `v-autocomplete`、`v-combobox`、原生 radio 和带独立事件处理器的子控件。
|
|
236
|
+
- 动态量程;range 的 `min`、`max`、`step` 必须是数字字面量或数字字面量绑定。`v-slider` 必须满足 min < max、step > 0。
|
|
237
|
+
- `v-progress-linear` 的 `min`/`step`、`indeterminate`、`clickable`;`v-progress-circular` 的 `min`/`max`/`step`、`indeterminate`。前者按 0 到 max 工作,后者固定按 0 到 100 工作。
|
|
238
|
+
|
|
239
|
+
下拉选项可以是标量数组或普通 `{ title, value }` 选项,但模型必须是单个标量,且值必须与设备枚举值一致。示例中的 A/B 只是页面占位数据,节点不会自动生成或修改下拉选项。
|
|
240
|
+
|
|
241
|
+
## 4. Meter 与普通数值反馈
|
|
242
|
+
|
|
243
|
+
Meter 是驱动端的特殊只读数据类型,不是“另一个滑杆”。页面作者只声明普通进度/电平控件;编辑人员把它绑定到带 Meter 元数据的 Feedback point 后,ControlPanel 自动完成批量订阅、同帧投影、页面侧量程换算、过期判断和重连。
|
|
244
|
+
|
|
245
|
+
- 一个 Meter 绑定只能使用 `read` 模式,不能配置 Write point。
|
|
246
|
+
- 只有驱动点位带有有效 `.metadata.meter`,并且是只读整数/浮点参数时,才会出现在 Meter 可选目标中。
|
|
247
|
+
- 普通 numeric `level`、`volume` 或其他连续参数不会因为名字像电平就自动变成 Meter。
|
|
248
|
+
- 一个面板最多支持 256 个 Meter 绑定。多个 Meter 会按流聚合,前端每帧一次性更新本页所有对应表头;页面不编写任何批处理或 socket 代码。
|
|
249
|
+
- Meter 需要独立字段,例如 `meter1`、`meter2`。不要与音量滑杆的 `volume` 共用字段,也不要让不同点位共用一个 Meter 模型。
|
|
250
|
+
- 断线、无效帧和过期帧由节点处理;页面看到的是最后值并带无效状态,页面作者不添加轮询或回写逻辑。
|
|
251
|
+
- 原生 `<meter>`/`<progress>` 默认页面范围是 0 到 1;Vuetify 进度组件分别使用固定的 0 到 max 或 0 到 100。应按页面显示范围声明固定量程。
|
|
252
|
+
|
|
253
|
+
量程适配发生在网页侧:页面量程与驱动量程之间做线性换算,反馈按相反方向换算,不修改驱动或协议。两端都必须是有限且 max > min 的数字;否则按原值传递,不能猜测量程。整数驱动值会取整,反馈会按页面 step 对齐。
|
|
254
|
+
|
|
255
|
+
普通只读反馈使用文本展示即可:
|
|
256
|
+
|
|
257
|
+
```html
|
|
258
|
+
<span id="room-status">{{ status }}</span>
|
|
259
|
+
<output id="room-value">{{ outputValue }}</output>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 4.1 XY-series 与普通实时数组反馈
|
|
147
263
|
|
|
148
|
-
|
|
264
|
+
XY-series 是 Meter 的独立数组版本,不是多个 Meter 的替代写法:
|
|
149
265
|
|
|
150
|
-
|
|
266
|
+
- 每个驱动帧是一个完整的 `[[x, y], ...]` 快照,第一层长度可以变化,也可以是空数组。
|
|
267
|
+
- 每个点必须恰好有两个有限数字;节点保留点的顺序,不自动排序、不自动缩放、不自动丢弃旧点。
|
|
268
|
+
- 数据只读,绑定只能使用 `read` 模式,没有 Write point;它与 Volume、Level 和 Meter 不能共用同一个模型字段。
|
|
269
|
+
- 多个页面绑定相同 stream 时只建立一个源订阅,页面帧使用 latest-wins;网络拥塞时允许丢弃旧帧,不补发历史帧。
|
|
270
|
+
- 当前每个 stream 默认最多 8192 个点,每个面板最多 256 个 XY-series 绑定;真实 MUSE 测试前仍需按点数和帧大小验证吞吐。
|
|
271
|
+
- 页面作者不写 socket、订阅、帧解析或重连代码,只维护普通数组。频谱坐标轴、Canvas 绘制、坐标变换和图表样式属于页面显示逻辑。
|
|
151
272
|
|
|
152
|
-
|
|
153
|
-
2. 重新打开节点,点击 `Open binding editor`,用编辑账号登录。Select 默认激活,可以点击、右键或触屏长按可绑定控件。`Open page` 打开普通控制页面,使用普通用户账号登录。
|
|
154
|
-
3. 选择在线设备、模式、Write point、Feedback point、编号方式,保存草稿。草稿和亮紫色边框不表示已部署;边框只在编辑模式显示。
|
|
155
|
-
4. 点 Apply。节点检查当前模板、目录参数和 Flow 版本,备份后替换该面板此前生成的桥接并保存。Apply 不替用户 Deploy;当 Automator 报告外部 Flow 变化时,编辑器只刷新一次 Flow 页面,之后由用户检查并手动 Deploy。不会无限追加旧桥接,也不会清除其他面板的绑定。Apply 成功后仍在编辑页,无需再次登录。
|
|
156
|
-
5. 点 Exit,回到普通页面登录页,再输入普通账号密码。普通账号不能进入编辑模式;不登录不能操作设备。进程重启或凭据变化可能要求重新登录。
|
|
273
|
+
文本展示只使用一个简单字段插值,例如 `{{ status }}` 或 `{{ outputValue }}`。不要使用 `toFixed()`、拼接表达式或多个插值作为绑定模型。
|
|
157
274
|
|
|
158
|
-
|
|
275
|
+
## 5. 语义开关按钮
|
|
159
276
|
|
|
160
|
-
|
|
277
|
+
普通文字 `<button>` 不会自动成为设备控件。需要布尔设备控制时使用标准语义,不添加事件:
|
|
161
278
|
|
|
162
|
-
|
|
279
|
+
```html
|
|
280
|
+
<button id="room-power" type="button" :aria-pressed="powered">电源</button>
|
|
281
|
+
<button id="room-mute" type="button" role="switch" :aria-checked="muted">静音</button>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
在 `data()` 中把 `powered` 和 `muted` 初始化为布尔值。也可以使用静态 `aria-pressed="false"` 或 `aria-checked="false"`,由节点更新状态。动态 ARIA 表达式只能是顶层字段,不能写取反、条件或函数。
|
|
285
|
+
|
|
286
|
+
## 6. 绑定与编辑流程
|
|
287
|
+
|
|
288
|
+
页面作者只需交付 Template。实际绑定由使用者在编辑页面完成:
|
|
163
289
|
|
|
164
|
-
|
|
290
|
+
1. 将 Template 保存到 `ui-control-panel`,配置 Dashboard 页面、Room ID、普通登录账号和独立编辑账号,然后保存并 Deploy。
|
|
291
|
+
2. 重新打开节点,点击 `Open binding editor`。编辑页登录使用编辑账号;进入后 Select 默认激活。`Open page` 打开普通控制页面,普通页面必须使用普通账号登录。
|
|
292
|
+
3. 在 Select 模式选择控件,选择设备、模式、Write point、Feedback point 和编号方式。已绑定控件在编辑模式显示亮紫色边框;这只是编辑提示。
|
|
293
|
+
4. 点 Apply。节点验证当前模板、设备目录和映射,备份当前 Flow,并通过运行时 Flow API 更新当前面板的映射和受管理桥接。它不会导入、清除或追加其他面板的 Flow。
|
|
294
|
+
5. Apply 成功后仍停留在编辑页;Bindings 列表可以 Edit 或 Remove,取消/保存后回到列表。Apply 后不应要求再次登录;点 Exit 才回到普通页面登录页。
|
|
165
295
|
|
|
166
|
-
|
|
296
|
+
Automator 的同步脚本只监听更新后的运行时 Flow 版本,并最多刷新一次 Flow 编辑器页面;它不读取、导入、合并、清除或再次提交 Flow。当前实现不判断画布是否有未 Deploy 改动,也不会合并画布草稿;在 Apply 前应先处理好 Automator 中未保存的画布改动。
|
|
297
|
+
|
|
298
|
+
Template 修改可能使已有映射失效。重新 Deploy 后,应重新打开绑定编辑器检查候选控件;列表增删、控件 ID/标签类型/模型写法改变后,也应重新检查并 Apply。普通页面登录账号不能进入编辑模式,未登录不能操作设备。
|
|
299
|
+
|
|
300
|
+
## 7. 点位匹配与量程
|
|
301
|
+
|
|
302
|
+
编辑器根据 AMX 参数描述推导可匹配类型,不根据页面文字,也不把驱动作者写入的旧式显式 `controlType` 当作页面契约。主要依据是 `.kind`、`.type`、`.access`、`.min`、`.max`、`.enums` 和 `.metadata`。Meter 的 `.metadata.meter` 是实时通道所需的特殊元数据,不能省略。
|
|
167
303
|
|
|
168
304
|
| 参数描述 | 可匹配控件 |
|
|
169
305
|
| --- | --- |
|
|
170
|
-
| boolean
|
|
171
|
-
| 可写 integer/float
|
|
172
|
-
| 只读 integer/float
|
|
173
|
-
| 只读 integer/float
|
|
174
|
-
|
|
|
175
|
-
| string
|
|
176
|
-
|
|
|
177
|
-
|
|
178
|
-
只有 `kind=param` 且具备相应 ro/wo/rw 权限的点位才参与绑定。范围未知不会通过“读取一个当前值”猜出量程;普通命令、数组或未知描述不保证适配。页面无需自己判断这些类型。
|
|
306
|
+
| boolean,或没有枚举且范围恰为 0 到 1 的 integer | 布尔开关;整数 0/1 会转换为前端布尔值。 |
|
|
307
|
+
| 可写 integer/float | range 或 `v-slider`;用于音量/增益等普通连续控制。 |
|
|
308
|
+
| 只读 integer/float,没有 Meter 元数据 | 文本展示等普通只读反馈,不能选择为 Meter。 |
|
|
309
|
+
| 只读 integer/float,带有效 `.metadata.meter` | Meter、原生 `meter`/`progress` 或 Vuetify 进度组件;只能读。 |
|
|
310
|
+
| 只读参数,带有效 `.metadata.xySeries` | XY-series;使用标准 Canvas 的 `:data-points`/`:data-series`,或地图容器的 `:data-coordinates` 数组字段;只能读。 |
|
|
311
|
+
| enum,或带枚举描述的 integer/string | 单选枚举。 |
|
|
312
|
+
| 没有枚举但路径以 `/source` 或 `/preset` 结尾的 integer | 选择类参数,仍需确认合法选项。 |
|
|
313
|
+
| string/dstring | 文本输入或文本展示。 |
|
|
179
314
|
|
|
180
|
-
|
|
315
|
+
只有 `kind=param` 且具备相应读/写权限的点位参与绑定。普通命令、数组和未知描述不保证可绑定。
|
|
181
316
|
|
|
182
|
-
|
|
317
|
+
Write point 改动时 Feedback point 会跟随;单独改 Feedback point 不会反向改 Write point。Meter 没有 Write point。编号方式默认 Zero-based;如果目录点位带有 `.onebased`,保存时以目录描述为准,否则使用编辑页选择。编号方式只影响驱动点位编号,不会给网页值加减 1。
|
|
183
318
|
|
|
184
|
-
##
|
|
319
|
+
## 8. 常见排查
|
|
185
320
|
|
|
186
321
|
| 现象 | 先检查 |
|
|
187
322
|
| --- | --- |
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
323
|
+
| 控件不能选择 | 是否在支持清单;是否有事件指令、复杂模型、动态量程、重复/不唯一 key、重复 ID 或自闭合 Vuetify 标签。 |
|
|
324
|
+
| `v-for` 实例没有出现 | 来源是否是顶层数组;key 是否严格符合对象/数组规则;列表项是否实际渲染且 key 唯一。 |
|
|
325
|
+
| 设备有了但点位为空 | 设备在线状态、AMX 参数目录、`kind/type/access` 和读写权限;不是所有命令都属于可绑定参数。 |
|
|
326
|
+
| Meter 可以选但无值 | 点位是否带 `.metadata.meter`;驱动是否实际发布 manifest 和 frame;页面字段是否与普通 Volume 分开。 |
|
|
327
|
+
| XY-series 可以选但无值 | 点位是否带 `.metadata.xySeries`;驱动是否发布对应 manifest/frame;数组字段是否为顶层数组;检查点数、帧大小和过期状态。 |
|
|
328
|
+
| 页面一直 Loading | 查看模板编译错误,重点检查原生 Meter/Progress 的 v-model 写法、自闭合 Vuetify、动态表达式和不支持的组件。设备离线不应被页面作者通过轮询“修复”。 |
|
|
329
|
+
| 滑杆动不了 | 是否仍处于 Select 编辑模式;模型是否为数字;是否给绑定控件添加了自己的事件或回写逻辑。 |
|
|
330
|
+
| 页面变化但设备不变 | 检查映射的 Write point、设备权限和运行日志。前端模型变化不等于设备已确认执行。 |
|
|
331
|
+
| 设备值变了但页面旧 | 检查 Feedback point、普通反馈事件或 Meter frame 是否到达;节点不会凭写入成功伪造真实反馈。 |
|
|
332
|
+
| Apply 失败并提示 Flow changed | 先处理 Automator 中的未 Deploy 改动,再刷新编辑器并重新打开绑定页;不要手工复制或追加生成桥接。 |
|
|
333
|
+
|
|
334
|
+
隔离测试只能证明模板识别、映射和软件通信逻辑,不能证明真实设备、MUSE 网络或驱动版本已经完成闭环测试。
|
|
335
|
+
|
|
336
|
+
## 9. 给 AI 的交付检查
|
|
337
|
+
|
|
338
|
+
- 输出可粘贴的 Template 片段,不输出 Node-RED Flow、设备路径、绑定 JSON、账号密码或 ControlPanel API 调用。
|
|
339
|
+
- 所有待绑定控件位于支持清单,模型字段简单、独立、可写且类型正确。
|
|
340
|
+
- 列表只使用本指南规定的 `v-for`、`:key` 和模型路径;对象 key 稳定唯一,数组 key 使用 index。
|
|
341
|
+
- 列表内的 Meter、Volume 和其他控件使用不同模型字段;重复实例依靠稳定 key 区分,不生成随机 ID。
|
|
342
|
+
- XY-series 使用独立的顶层数组字段,例如 `spectrum: []`,不要与 Meter、Volume 或普通反馈字段共用。
|
|
343
|
+
- 地图坐标使用独立的顶层数组字段,例如 `locations: []`,点顺序固定为 `[经度, 纬度]`;不要与 Meter、Volume 或普通反馈字段共用。
|
|
344
|
+
- 页面量程明确、数值常量有效;枚举选项与实际设备值对应。
|
|
345
|
+
- 没有手写 `data-cp-*`、设备通信、轮询、反馈回写、绑定控件事件或第二套发送逻辑。
|
|
346
|
+
- 实时表头仍是普通 HTML/Vue/Vuetify 控件;没有 stream、socket、订阅、帧协议或驱动代码。
|
|
347
|
+
- Vuetify 标签成对闭合;不使用本指南明确列出的不支持组件和属性。
|
|
348
|
+
- 保留正常的视觉和纯前端交互;不要为了绑定修改驱动或猜测设备量程。
|
|
349
|
+
- 不声称任意普通页面都能无条件自动绑定;遇到不支持的组件或表达式,应改写为支持的普通写法。
|
package/README.md
CHANGED
|
@@ -10,10 +10,12 @@ Both logins work over HTTP on a trusted local network. HTTPS is recommended for
|
|
|
10
10
|
|
|
11
11
|
Bindings are stored in `visualBindings` on the node. The runtime reads that mapping to authenticate commands, route feedback, and generate a native bridge. The page author does not put device paths, `data-cp-*` attributes, or device-send code in the Template. Author-provided `data-cp-*` attributes are rejected.
|
|
12
12
|
|
|
13
|
-
Supported controls include native range, checkbox, semantic pressed button, select, text input, meter, progress, and simple text display. Give repeated controls distinct standard IDs. Simple top-level Vue `v-model` works
|
|
13
|
+
Supported controls include native range, checkbox, semantic pressed button, select, text input, meter, progress, and simple text display. Give repeated controls distinct standard IDs. Simple top-level Vue `v-model` works, including object-list models such as `item.volume`, `item.audio.volume`, or `items[index].volume`, primitive array models such as `values[index]`, and an inner `<template v-for>` for multiple controls per row. During Vue re-rendering, a temporary duplicate list key is ignored until the key becomes unique again, so bindings do not attach twice. Controls with existing send handlers or ambiguous dynamic behavior are rejected until a suitable adapter exists. See `CONTROL_PANEL_AI_GUIDE.md` for examples and limits.
|
|
14
14
|
|
|
15
15
|
Realtime meter delivery does not change the page-authoring contract. Authors use ordinary `meter`, `progress`, `v-progress-linear`, or `v-progress-circular` controls with stable IDs and simple top-level fields. They do not declare devices, point paths, stream IDs, sockets, subscriptions, or frame formats. A `volume` binding is a writable gain setting with ordinary feedback; a `meter` binding is read-only realtime signal telemetry and requires a target that advertises Meter metadata. A normal numeric `level` target is not automatically a Meter. When the selected feedback point advertises realtime meter delivery, ControlPanel automatically shares the device stream, projects complete frames to the bound controls, applies webpage-side range conversion, and handles stale/reconnected data.
|
|
16
16
|
|
|
17
|
+
Readonly XY-series telemetry follows the same page boundary. Use a standard canvas with one simple Vue array binding, for example `<canvas id="spectrum" :data-points="spectrum"></canvas>`, or a map container with `<div id="room-map" :data-coordinates="locations"></div>`. Map coordinates use the standard `[longitude, latitude]` order. The page receives complete `[[x, y], ...]` snapshots; ControlPanel owns subscription, latest-frame replacement, stale handling, and reconnection. The page does not declare a device, stream, socket, or frame protocol. XY-series is independent from Meter and Volume: it never sends commands and does not represent a single scalar value.
|
|
18
|
+
|
|
17
19
|
Vuetify adapters cover `v-slider`, `v-switch`, `v-checkbox`, `v-select`, `v-radio-group`, `v-text-field`, `v-textarea`, and read-only `v-progress-linear` and `v-progress-circular`. Interactive components use a simple top-level `v-model`; progress may also use a simple `:model-value` field. Sliders accept fixed numeric `min`, `max`, and `step` props, including numeric Vue bindings such as `:max="100"`. Text fields commit on change, blur, or Enter (`v-text-field` only). Selection values must match the device's enum values; circular progress uses a fixed 0-100 range. `v-model.number` is accepted for numeric sliders and scalar selections, but not for boolean or text controls. Dynamic bounds, array-valued switches, multi-select, object selections, custom true/false values, indeterminate progress, and controls with their own event handlers need a separate adapter.
|
|
18
20
|
|
|
19
21
|
Use paired Vuetify tags in Dashboard templates, such as `<v-slider></v-slider>`; self-closing component tags can absorb the following controls during Dashboard rendering and are rejected before deployment.
|
package/control-catalog.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { schemaVersion } = require('./control-type-contract.json')
|
|
2
2
|
const { runtimeJsonRequest } = require('./panel-http')
|
|
3
3
|
const { meterRoute } = require('./meter-route')
|
|
4
|
+
const { xySeriesRoute } = require('./xy-series-route')
|
|
4
5
|
|
|
5
6
|
function catalogBase (settings) {
|
|
6
7
|
const port = Number(settings?.uiPort)
|
|
@@ -75,6 +76,7 @@ async function listCatalogTargets (base, device, fetcher = fetch) {
|
|
|
75
76
|
const max = catalogBound(properties['.max'])
|
|
76
77
|
const enums = properties['.enums']
|
|
77
78
|
const meter = properties['.metadata']?.meter ? meterRoute(properties['.metadata'].meter) : null
|
|
79
|
+
const xySeries = properties['.metadata']?.xySeries ? xySeriesRoute(properties['.metadata'].xySeries) : null
|
|
78
80
|
return {
|
|
79
81
|
path: item.path,
|
|
80
82
|
kind: properties['.kind'] || '',
|
|
@@ -83,10 +85,11 @@ async function listCatalogTargets (base, device, fetcher = fetch) {
|
|
|
83
85
|
onebased: typeof properties['.onebased'] === 'boolean' ? properties['.onebased']
|
|
84
86
|
: typeof item.onebased === 'boolean' ? item.onebased : undefined,
|
|
85
87
|
min, max, enums,
|
|
86
|
-
...(meter ? { controlType: 'meter' } : inferredControl(item.path, properties, type, min, max, enums)),
|
|
88
|
+
...(meter ? { controlType: 'meter' } : xySeries ? { controlType: 'xy-series' } : inferredControl(item.path, properties, type, min, max, enums)),
|
|
87
89
|
controlSchemaVersion: schemaVersion,
|
|
88
90
|
unit: properties['.metadata']?.unit || '',
|
|
89
|
-
...(meter ? { meter } : {})
|
|
91
|
+
...(meter ? { meter } : {}),
|
|
92
|
+
...(xySeries ? { xySeries } : {})
|
|
90
93
|
}
|
|
91
94
|
})
|
|
92
95
|
const unique = new Map()
|