node-red-contrib-room-tools 1.8.1 → 1.8.2
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 +6 -1
- package/README.md +5 -2
- package/control-panel.html +2 -2
- package/control-policy.js +18 -4
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ControlPanel AI 开发契约
|
|
2
2
|
|
|
3
|
-
适用节点:`ui-control-panel` 1.8.
|
|
3
|
+
适用节点:`ui-control-panel` 1.8.2。
|
|
4
4
|
|
|
5
5
|
把本文件作为 AI 开发或修改 ControlPanel Template 前的强制上下文。目标是让
|
|
6
6
|
不同页面使用同一套控制语义,避免滑杆积压、重复队列、反馈回环和跨房间误发。
|
|
@@ -151,6 +151,11 @@ Vuetify 滑杆可把相同逻辑分别放在 `@update:model-value` 和 `@end`。
|
|
|
151
151
|
连续控件直接发送目标值,不主动查询当前值。匹配目标的被动反馈会自动清除 pending;
|
|
152
152
|
未匹配的较旧反馈只更新 `deviceState`,`controlDisplayValue()` 仍返回最后目标。
|
|
153
153
|
|
|
154
|
+
离散控件的下一条同 topic 真实反馈会解除 pending,并直接以该反馈值显示实际状态,
|
|
155
|
+
即使 Automator 反馈携带的是 MUSE provider path、与 Template 的 UI `key` 不同。若同一房间内
|
|
156
|
+
有多个相同 topic 的控件,反馈必须提供与 Template 一致的 `controlKey`,避免误确认其他控件。
|
|
157
|
+
连续控件仍只有在反馈值匹配最后目标时才解除 pending。
|
|
158
|
+
|
|
154
159
|
离散按钮必须显示设备反馈值,不能先改本地状态。标准写法:
|
|
155
160
|
|
|
156
161
|
```html
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ regions follow the same URL structure.
|
|
|
15
15
|
|
|
16
16
|
## Control traffic policy
|
|
17
17
|
|
|
18
|
-
Version 1.8.
|
|
18
|
+
Version 1.8.2 uses the same control semantics at the panel, gateway and device
|
|
19
19
|
driver boundaries:
|
|
20
20
|
|
|
21
21
|
- `power`, `source`, `preset` and `mute` are discrete controls. The panel sends
|
|
@@ -58,7 +58,10 @@ the selected room. `deviceState` contains reported device state only. Accepted
|
|
|
58
58
|
commands update the gateway's private desired state but do not change
|
|
59
59
|
`deviceState`; matching Script-as-Device feedback is required before a Power,
|
|
60
60
|
Mute, Source or Preset control appears active. `isControlPending(key)` can be
|
|
61
|
-
used for a waiting indicator.
|
|
61
|
+
used for a waiting indicator. A discrete control is released by the next
|
|
62
|
+
authoritative feedback for its topic, even when Automator reports a provider
|
|
63
|
+
path instead of the Template's UI key. Continuous controls still wait for the
|
|
64
|
+
reported value to match their latest target.
|
|
62
65
|
|
|
63
66
|
Non-slider controls must use `:model-value`, `:value`, `:class` or
|
|
64
67
|
`:aria-pressed` from `deviceState`/`deviceValue()` and must not optimistically
|
package/control-panel.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const template = []
|
|
13
13
|
|
|
14
14
|
template.push('<!--')
|
|
15
|
-
template.push('AI / DEVELOPER CONTRACT - ui-control-panel 1.8.
|
|
15
|
+
template.push('AI / DEVELOPER CONTRACT - ui-control-panel 1.8.2')
|
|
16
16
|
template.push('AI START HERE: 先完整阅读 CONTROL_PANEL_AI_GUIDE.md、当前 ControlPanel Template 和 control-policy.js,再开发或修改页面。严格使用注入的 sendControl() 和设备反馈 API,禁止在 Template 中重写发送队列、限速、debounce、latest-wins、主动状态查询或反馈连接。所有非滑杆控件必须显示设备反馈确认的实际状态;滑杆必须直接发送并保持最后目标,不能被旧中间反馈拉回;设备反馈不能触发命令回发。完成后按照指南第 6 节逐项检查。')
|
|
17
17
|
template.push('1. Use the injected sendControl({ type, topic, key, value, final }) API for device commands.')
|
|
18
18
|
template.push('2. Never add debounce, throttle, latest-wins maps, Promise queues, or raw device TCP code here.')
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
template.push('5. key must be stable and unique per device control, for example rane/1/level/2.')
|
|
22
22
|
template.push('6. After room-gateway login call setControlSession(session); on logout call clearControlSession().')
|
|
23
23
|
template.push('7. Bind discrete control visuals to deviceState/deviceValue(), not to an optimistic local value.')
|
|
24
|
-
template.push('8. Use isControlPending(key) only for a waiting indicator;
|
|
24
|
+
template.push('8. Use isControlPending(key) only for a waiting indicator; discrete pending clears on authoritative topic feedback, while continuous pending waits for its target value.')
|
|
25
25
|
template.push('9. Sliders directly send and hold the latest target; use controlDisplayValue() so stale feedback cannot pull them back.')
|
|
26
26
|
template.push('10. Feedback may update UI state, but must never trigger a watcher that sends the value back.')
|
|
27
27
|
template.push('11. Do not redefine any injected ControlPanel API or its feedback state fields.')
|
package/control-policy.js
CHANGED
|
@@ -274,7 +274,6 @@ const __controlPanelPolicyMixin = {
|
|
|
274
274
|
},
|
|
275
275
|
__acknowledgeControlFeedback (feedback) {
|
|
276
276
|
for (const [key, request] of this.__controlPanelRequests) {
|
|
277
|
-
if (feedback.controlKey && feedback.controlKey !== key) continue
|
|
278
277
|
let matched = request.topic === 'room/reset'
|
|
279
278
|
let value
|
|
280
279
|
if (!matched && feedback.topic === request.topic) {
|
|
@@ -287,7 +286,15 @@ const __controlPanelPolicyMixin = {
|
|
|
287
286
|
value = feedback.state[field]
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
|
-
if (matched
|
|
289
|
+
if (!matched) continue
|
|
290
|
+
|
|
291
|
+
const sameTopicRequests = [...this.__controlPanelRequests.values()]
|
|
292
|
+
.filter(candidate => candidate.topic === request.topic).length
|
|
293
|
+
const correlated = !feedback.controlKey || feedback.controlKey === key || sameTopicRequests === 1
|
|
294
|
+
if (!correlated) continue
|
|
295
|
+
|
|
296
|
+
const continuous = ['level', 'dimmer', 'position'].includes(request.type)
|
|
297
|
+
if (!continuous || request.topic === 'room/reset' || Object.is(value, request.value)) {
|
|
291
298
|
this.__clearControlPending(key)
|
|
292
299
|
}
|
|
293
300
|
}
|
|
@@ -503,7 +510,6 @@ export default {
|
|
|
503
510
|
__acknowledgeControlFeedback (feedback) {
|
|
504
511
|
const feedbackFields = this.__controlPanelFeedbackFields()
|
|
505
512
|
for (const [key, request] of this.__controlPanelRequests) {
|
|
506
|
-
if (feedback.controlKey && feedback.controlKey !== key) continue
|
|
507
513
|
let matched = request.topic === 'room/reset'
|
|
508
514
|
let value
|
|
509
515
|
if (!matched && feedback.topic === request.topic) {
|
|
@@ -516,7 +522,15 @@ export default {
|
|
|
516
522
|
value = feedback.state[field]
|
|
517
523
|
}
|
|
518
524
|
}
|
|
519
|
-
if (matched
|
|
525
|
+
if (!matched) continue
|
|
526
|
+
|
|
527
|
+
const sameTopicRequests = [...this.__controlPanelRequests.values()]
|
|
528
|
+
.filter(candidate => candidate.topic === request.topic).length
|
|
529
|
+
const correlated = !feedback.controlKey || feedback.controlKey === key || sameTopicRequests === 1
|
|
530
|
+
if (!correlated) continue
|
|
531
|
+
|
|
532
|
+
const continuous = ['level', 'dimmer', 'position'].includes(request.type)
|
|
533
|
+
if (!continuous || request.topic === 'room/reset' || Object.is(value, request.value)) {
|
|
520
534
|
this.__clearControlPending(key)
|
|
521
535
|
}
|
|
522
536
|
}
|
package/package.json
CHANGED