node-red-contrib-ccu 3.4.2 → 4.0.0
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/.prettierignore +10 -0
- package/.prettierrc +14 -0
- package/AGENTS.md +96 -0
- package/CHANGELOG.md +66 -0
- package/README.md +17 -21
- package/ROADMAP.md +402 -0
- package/eslint.config.js +45 -0
- package/examples/rpc-events.json +65 -0
- package/examples/switch-datapoint.json +86 -0
- package/examples/system-variable.json +60 -0
- package/nodes/ccu-alexa.html +12 -7
- package/nodes/ccu-alexa.js +33 -14
- package/nodes/ccu-connection.html +9 -1
- package/nodes/ccu-connection.js +588 -335
- package/nodes/ccu-display.html +11 -4
- package/nodes/ccu-display.js +26 -9
- package/nodes/ccu-get-value.html +13 -4
- package/nodes/ccu-get-value.js +2 -2
- package/nodes/ccu-mqtt.html +9 -10
- package/nodes/ccu-mqtt.js +55 -29
- package/nodes/ccu-program.html +1 -1
- package/nodes/ccu-program.js +9 -7
- package/nodes/ccu-rpc-event.html +12 -7
- package/nodes/ccu-rpc-event.js +4 -5
- package/nodes/ccu-rpc.html +12 -5
- package/nodes/ccu-rpc.js +8 -6
- package/nodes/ccu-script.html +1 -1
- package/nodes/ccu-script.js +4 -3
- package/nodes/ccu-set-value.html +11 -7
- package/nodes/ccu-set-value.js +40 -16
- package/nodes/ccu-signal.html +11 -5
- package/nodes/ccu-signal.js +34 -23
- package/nodes/ccu-switch.html +13 -5
- package/nodes/ccu-sysvar.html +1 -1
- package/nodes/ccu-sysvar.js +11 -7
- package/nodes/ccu-value.html +12 -7
- package/nodes/ccu-value.js +36 -13
- package/nodes/lib/base62.js +22 -0
- package/nodes/lib/discover.js +127 -0
- package/nodes/lib/nextport.js +30 -0
- package/nodes/lib/similarity.js +51 -0
- package/nodes/lib/status.js +1 -1
- package/package.json +105 -155
- package/roadmap-archive/README.md +20 -0
- package/roadmap-archive/phase-1.md +48 -0
- package/roadmap-archive/todo-history.md +39 -0
- package/TODO.md +0 -41
- package/ccu_rega_localhost.json +0 -1
- package/ccu_values_localhost.json +0 -1
package/nodes/ccu-display.html
CHANGED
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
let ifacesLoaded = false;
|
|
103
103
|
let ifacesPending = false;
|
|
104
|
+
let ifaceLoadRetries = 5;
|
|
104
105
|
|
|
105
106
|
function loadIfaces(iface, cb) {
|
|
106
107
|
if (ifacesPending) {
|
|
@@ -108,7 +109,6 @@
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
ifacesPending = true;
|
|
111
|
-
console.log('loadIfaces()');
|
|
112
112
|
$nodeInputIface.html('');
|
|
113
113
|
const nodeId = $nodeInputCcuConfig.val();
|
|
114
114
|
if (nodeId === '_ADD_') {
|
|
@@ -128,6 +128,12 @@
|
|
|
128
128
|
cb();
|
|
129
129
|
ifacesPending = false;
|
|
130
130
|
}
|
|
131
|
+
}).fail(() => {
|
|
132
|
+
// config node not (yet) available in the runtime - allow retrying
|
|
133
|
+
ifacesPending = false;
|
|
134
|
+
if (ifaceLoadRetries-- > 0) {
|
|
135
|
+
setTimeout(() => loadIfaces(iface, cb), 1000);
|
|
136
|
+
}
|
|
131
137
|
});
|
|
132
138
|
}
|
|
133
139
|
}
|
|
@@ -136,7 +142,6 @@
|
|
|
136
142
|
if (ifacesLoaded) {
|
|
137
143
|
const nodeId = $nodeInputCcuConfig.val();
|
|
138
144
|
if (nodeId && nodeId !== '__ADD__') {
|
|
139
|
-
console.log('loadConfig()');
|
|
140
145
|
$.getJSON('ccu?type=display&config=' + nodeId + '&iface=' + $nodeInputIface.val(), d => {
|
|
141
146
|
data = d;
|
|
142
147
|
autoCompleteChannel();
|
|
@@ -148,7 +153,6 @@
|
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
$nodeInputCcuConfig.change(() => {
|
|
151
|
-
console.log('$nodeInputCcuConfig change');
|
|
152
156
|
loadIfaces(this.iface, () => {
|
|
153
157
|
ifacesLoaded = true;
|
|
154
158
|
$nodeInputIface.removeAttr('disabled');
|
|
@@ -156,6 +160,10 @@
|
|
|
156
160
|
});
|
|
157
161
|
});
|
|
158
162
|
|
|
163
|
+
// Node-RED 4.x does not reliably fire the change event on dialog
|
|
164
|
+
// open - trigger the initial load explicitly (see ROADMAP.md 8.1)
|
|
165
|
+
$nodeInputCcuConfig.trigger('change');
|
|
166
|
+
|
|
159
167
|
$nodeInputIface.change(() => {
|
|
160
168
|
if (ifacesLoaded) {
|
|
161
169
|
loadConfig();
|
|
@@ -198,7 +206,6 @@
|
|
|
198
206
|
});
|
|
199
207
|
}
|
|
200
208
|
|
|
201
|
-
console.log('autoCompleteChannel()', arr.length);
|
|
202
209
|
$nodeInputChannel.autocomplete('option', 'source', arr);
|
|
203
210
|
}
|
|
204
211
|
|
package/nodes/ccu-display.js
CHANGED
|
@@ -31,7 +31,7 @@ module.exports = function (RED) {
|
|
|
31
31
|
gruen: '0x84',
|
|
32
32
|
grün: '0x84',
|
|
33
33
|
green: '0x84',
|
|
34
|
-
blue: '0x85'
|
|
34
|
+
blue: '0x85',
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
if (map[col]) {
|
|
@@ -87,18 +87,33 @@ module.exports = function (RED) {
|
|
|
87
87
|
'signal rot': '0x8B',
|
|
88
88
|
red: '0x8B',
|
|
89
89
|
signalred: '0x8B',
|
|
90
|
-
'signal red': '0x8B'
|
|
90
|
+
'signal red': '0x8B',
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
if (map[ico]) {
|
|
94
94
|
ico = map[ico];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
if (
|
|
97
|
+
if (
|
|
98
|
+
![
|
|
99
|
+
'0x80',
|
|
100
|
+
'0x81',
|
|
101
|
+
'0x82',
|
|
102
|
+
'0x83',
|
|
103
|
+
'0x84',
|
|
104
|
+
'0x85',
|
|
105
|
+
'0x86',
|
|
106
|
+
'0x87',
|
|
107
|
+
'0x88',
|
|
108
|
+
'0x89',
|
|
109
|
+
'0x8a',
|
|
110
|
+
'0x8b',
|
|
111
|
+
].includes(ico)
|
|
112
|
+
) {
|
|
98
113
|
ico = '';
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
return ico ?
|
|
116
|
+
return ico ? ',0x13,' + ico : '';
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
function convertString(string) {
|
|
@@ -117,11 +132,11 @@ module.exports = function (RED) {
|
|
|
117
132
|
ä: '0x7B',
|
|
118
133
|
ö: '0x7C',
|
|
119
134
|
ü: '0x7D',
|
|
120
|
-
ß: '0x5F'
|
|
135
|
+
ß: '0x5F',
|
|
121
136
|
};
|
|
122
137
|
const res = [];
|
|
123
|
-
string.split('').forEach(c => {
|
|
124
|
-
res.push(charcodes[c] ||
|
|
138
|
+
string.split('').forEach((c) => {
|
|
139
|
+
res.push(charcodes[c] || '0x' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
125
140
|
});
|
|
126
141
|
|
|
127
142
|
return ',0x12,' + res.slice(0, 12).join(',');
|
|
@@ -163,10 +178,12 @@ module.exports = function (RED) {
|
|
|
163
178
|
|
|
164
179
|
payload += ',0x03';
|
|
165
180
|
|
|
166
|
-
this.ccu
|
|
181
|
+
this.ccu
|
|
182
|
+
.setValue(config.iface, config.channel, 'SUBMIT', payload)
|
|
167
183
|
.then(() => {
|
|
168
184
|
done();
|
|
169
|
-
})
|
|
185
|
+
})
|
|
186
|
+
.catch((error) => {
|
|
170
187
|
done(error);
|
|
171
188
|
});
|
|
172
189
|
});
|
package/nodes/ccu-get-value.html
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
let ifacesLoaded = false;
|
|
48
48
|
let ifacesPending = false;
|
|
49
|
+
let ifaceLoadRetries = 5;
|
|
49
50
|
|
|
50
51
|
if (!this.setProp) {
|
|
51
52
|
this.setProp = 'payload';
|
|
@@ -87,7 +88,6 @@
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
ifacesPending = true;
|
|
90
|
-
console.log('loadIfaces()');
|
|
91
91
|
|
|
92
92
|
$nodeInputIface.html('');
|
|
93
93
|
|
|
@@ -108,6 +108,12 @@
|
|
|
108
108
|
cb();
|
|
109
109
|
ifacesPending = false;
|
|
110
110
|
}
|
|
111
|
+
}).fail(() => {
|
|
112
|
+
// config node not (yet) available in the runtime - allow retrying
|
|
113
|
+
ifacesPending = false;
|
|
114
|
+
if (ifaceLoadRetries-- > 0) {
|
|
115
|
+
setTimeout(() => loadIfaces(iface, cb), 1000);
|
|
116
|
+
}
|
|
111
117
|
});
|
|
112
118
|
$.getJSON('ccu?type=sysvar&config=' + nodeId, data => {
|
|
113
119
|
$nodeInputSysvar.html('<option></option>');
|
|
@@ -117,6 +123,8 @@
|
|
|
117
123
|
});
|
|
118
124
|
}
|
|
119
125
|
});
|
|
126
|
+
} else {
|
|
127
|
+
ifacesPending = false;
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
@@ -125,7 +133,6 @@
|
|
|
125
133
|
return;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
console.log('loadConfig()');
|
|
129
136
|
const nodeId = $nodeInputCcuConfig.val();
|
|
130
137
|
const url = 'ccu?config=' + nodeId + '&type=channels&iface=' + $nodeInputIface.val();
|
|
131
138
|
$.getJSON(url, d => {
|
|
@@ -136,13 +143,16 @@
|
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
$nodeInputCcuConfig.change(() => {
|
|
139
|
-
console.log('$nodeInputCcuConfig change');
|
|
140
146
|
loadIfaces(this.iface, () => {
|
|
141
147
|
ifacesLoaded = true;
|
|
142
148
|
loadConfig();
|
|
143
149
|
});
|
|
144
150
|
});
|
|
145
151
|
|
|
152
|
+
// Node-RED 4.x does not reliably fire the change event on dialog
|
|
153
|
+
// open - trigger the initial load explicitly (see ROADMAP.md 8.1)
|
|
154
|
+
$nodeInputCcuConfig.trigger('change');
|
|
155
|
+
|
|
146
156
|
$nodeInputChannel.autocomplete({
|
|
147
157
|
source: [],
|
|
148
158
|
close: () => {
|
|
@@ -203,7 +213,6 @@
|
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
$nodeInputIface.change(() => {
|
|
206
|
-
console.log('$nodeInputIface change');
|
|
207
216
|
if ($nodeInputIface.val() === 'ReGaHSS') {
|
|
208
217
|
$('.form-row.datapoint').hide();
|
|
209
218
|
$('.form-row.sysvar').show();
|
package/nodes/ccu-get-value.js
CHANGED
|
@@ -66,10 +66,10 @@ module.exports = function (RED) {
|
|
|
66
66
|
if (done) {
|
|
67
67
|
done();
|
|
68
68
|
}
|
|
69
|
-
} else if (
|
|
69
|
+
} else if (this.setPropType === 'flow' || this.setPropType === 'global') {
|
|
70
70
|
const context = RED.util.parseContextStore(this.setProp);
|
|
71
71
|
const target = this.context()[this.setPropType];
|
|
72
|
-
target.set(context.key, value, context.store, err => {
|
|
72
|
+
target.set(context.key, value, context.store, (err) => {
|
|
73
73
|
if (err) {
|
|
74
74
|
done(err);
|
|
75
75
|
} else {
|
package/nodes/ccu-mqtt.html
CHANGED
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
cache: {value: false},
|
|
10
10
|
|
|
11
|
-
topicOutputEvent: {value: 'hm/status/${channelName}/${datapoint}'},
|
|
12
|
-
topicInputSetValue: {value: 'hm/set/${channelNameOrAddress}/${datapoint}'},
|
|
11
|
+
topicOutputEvent: {value: 'hm/status/${channelName}/${datapoint}'},
|
|
12
|
+
topicInputSetValue: {value: 'hm/set/${channelNameOrAddress}/${datapoint}'},
|
|
13
13
|
|
|
14
|
-
topicOutputSysvar: {value: 'hm/status/${name}'},
|
|
15
|
-
topicInputSysvar: {value: 'hm/set/${name}'},
|
|
14
|
+
topicOutputSysvar: {value: 'hm/status/${name}'},
|
|
15
|
+
topicInputSysvar: {value: 'hm/set/${name}'},
|
|
16
16
|
|
|
17
|
-
topicInputPutParam: {value: 'hm/paramset/${channelNameOrAddress}/${paramset}/${param}'},
|
|
18
|
-
topicInputPutParamset: {value: 'hm/paramset/${channelNameOrAddress}/${paramset}'},
|
|
17
|
+
topicInputPutParam: {value: 'hm/paramset/${channelNameOrAddress}/${paramset}/${param}'},
|
|
18
|
+
topicInputPutParamset: {value: 'hm/paramset/${channelNameOrAddress}/${paramset}'},
|
|
19
19
|
|
|
20
|
-
topicInputRpc: {value: 'hm/rpc/${iface}/${method}/${command}/${callid}'},
|
|
21
|
-
topicOutputRpc: {value: 'hm/response/${callid}'},
|
|
20
|
+
topicInputRpc: {value: 'hm/rpc/${iface}/${method}/${command}/${callid}'},
|
|
21
|
+
topicOutputRpc: {value: 'hm/response/${callid}'},
|
|
22
22
|
|
|
23
|
-
topicCounters: {value: 'hm/status/counter/${iface}/${rxtx}'},
|
|
23
|
+
topicCounters: {value: 'hm/status/counter/${iface}/${rxtx}'},
|
|
24
24
|
|
|
25
25
|
payloadOutput: {value: 'mqsh-extended'}
|
|
26
26
|
},
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
return this.name ? 'node_label_italic' : '';
|
|
38
38
|
},
|
|
39
39
|
oneditsave() {
|
|
40
|
-
console.log(this);
|
|
41
40
|
}
|
|
42
41
|
});
|
|
43
42
|
</script>
|
package/nodes/ccu-mqtt.js
CHANGED
|
@@ -33,19 +33,19 @@ module.exports = function (RED) {
|
|
|
33
33
|
|
|
34
34
|
this.ccu.register(this);
|
|
35
35
|
|
|
36
|
-
this.on('input', message => {
|
|
36
|
+
this.on('input', (message) => {
|
|
37
37
|
this.input(message);
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
this.idEventSubscription = this.ccu.subscribe({cache: config.cache}, message => {
|
|
40
|
+
this.idEventSubscription = this.ccu.subscribe({cache: config.cache}, (message) => {
|
|
41
41
|
this.event(message);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
this.idSysvarSubscription = this.ccu.subscribeSysvar({cache: config.cache, change: true}, message => {
|
|
44
|
+
this.idSysvarSubscription = this.ccu.subscribeSysvar({cache: config.cache, change: true}, (message) => {
|
|
45
45
|
this.sysvarOutput(message);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
this.idProgramSubscription = this.ccu.subscribeProgram({}, message => {
|
|
48
|
+
this.idProgramSubscription = this.ccu.subscribeProgram({}, (message) => {
|
|
49
49
|
this.programOutput(message);
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -53,9 +53,17 @@ module.exports = function (RED) {
|
|
|
53
53
|
|
|
54
54
|
if (this.topicCounters) {
|
|
55
55
|
setTimeout(() => {
|
|
56
|
-
this.ccu.enabledIfaces.forEach(iface => {
|
|
57
|
-
this.send({
|
|
58
|
-
|
|
56
|
+
this.ccu.enabledIfaces.forEach((iface) => {
|
|
57
|
+
this.send({
|
|
58
|
+
topic: this.ccu.topicReplace(this.topicCounters, {iface, rxtx: 'rx'}),
|
|
59
|
+
payload: '0',
|
|
60
|
+
retain: true,
|
|
61
|
+
});
|
|
62
|
+
this.send({
|
|
63
|
+
topic: this.ccu.topicReplace(this.topicCounters, {iface, rxtx: 'tx'}),
|
|
64
|
+
payload: '0',
|
|
65
|
+
retain: true,
|
|
66
|
+
});
|
|
59
67
|
});
|
|
60
68
|
}, 25000);
|
|
61
69
|
setInterval(() => {
|
|
@@ -75,7 +83,7 @@ module.exports = function (RED) {
|
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
checkCounters(c) {
|
|
78
|
-
Object.keys(this.ccu[c]).forEach(iface => {
|
|
86
|
+
Object.keys(this.ccu[c]).forEach((iface) => {
|
|
79
87
|
if (this.ccu[c][iface] !== this[c][iface]) {
|
|
80
88
|
this[c][iface] = this.ccu[c][iface];
|
|
81
89
|
const topic = this.ccu.topicReplace(this.topicCounters, {iface, rxtx: c.slice(0, 2)});
|
|
@@ -98,7 +106,11 @@ module.exports = function (RED) {
|
|
|
98
106
|
const messageNotWorking = RED.util.cloneMessage(message);
|
|
99
107
|
messageNotWorking.datapoint += '_NOTWORKING';
|
|
100
108
|
messageNotWorking.datapointName += '_NOTWORKING';
|
|
101
|
-
this.send({
|
|
109
|
+
this.send({
|
|
110
|
+
topic: this.ccu.topicReplace(this.topicOutputEvent, messageNotWorking),
|
|
111
|
+
payload: this.output(messageNotWorking),
|
|
112
|
+
retain: true,
|
|
113
|
+
});
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -119,7 +131,7 @@ module.exports = function (RED) {
|
|
|
119
131
|
return {
|
|
120
132
|
val: message.payload,
|
|
121
133
|
ts: message.ts,
|
|
122
|
-
lc: message.lc
|
|
134
|
+
lc: message.lc,
|
|
123
135
|
};
|
|
124
136
|
}
|
|
125
137
|
|
|
@@ -128,7 +140,7 @@ module.exports = function (RED) {
|
|
|
128
140
|
val: message.payload,
|
|
129
141
|
ts: message.ts,
|
|
130
142
|
lc: message.lc,
|
|
131
|
-
hm: message
|
|
143
|
+
hm: message,
|
|
132
144
|
};
|
|
133
145
|
delete payload.hm.topic;
|
|
134
146
|
delete payload.hm.payload;
|
|
@@ -156,21 +168,21 @@ module.exports = function (RED) {
|
|
|
156
168
|
sysvar: this.topicInputSysvar,
|
|
157
169
|
putParam: this.topicInputPutParam,
|
|
158
170
|
putParamset: this.topicInputPutParamset,
|
|
159
|
-
rpc: this.topicInputRpc
|
|
171
|
+
rpc: this.topicInputRpc,
|
|
160
172
|
};
|
|
161
173
|
|
|
162
174
|
let command;
|
|
163
175
|
let filter;
|
|
164
|
-
Object.keys(topicList).forEach(key => {
|
|
176
|
+
Object.keys(topicList).forEach((key) => {
|
|
165
177
|
if (!command) {
|
|
166
178
|
const parts = topicList[key].split('/');
|
|
167
179
|
const patternArray = [];
|
|
168
180
|
const placeholders = [];
|
|
169
181
|
for (let i = 0, {length} = parts; i < length; i++) {
|
|
170
182
|
let match;
|
|
171
|
-
if (match = parts[i].match(/^\${([\w-]+)}$/)) {
|
|
183
|
+
if ((match = parts[i].match(/^\${([\w-]+)}$/))) {
|
|
172
184
|
placeholders.push(match[1]);
|
|
173
|
-
patternArray[i] =
|
|
185
|
+
patternArray[i] = i + 1 < length ? '+' : '#';
|
|
174
186
|
} else {
|
|
175
187
|
patternArray[i] = parts[i];
|
|
176
188
|
}
|
|
@@ -180,7 +192,10 @@ module.exports = function (RED) {
|
|
|
180
192
|
const match = mw(topic, pattern);
|
|
181
193
|
if (match && match.length === placeholders.length) {
|
|
182
194
|
command = key;
|
|
183
|
-
filter = Object.assign.apply(
|
|
195
|
+
filter = Object.assign.apply(
|
|
196
|
+
{},
|
|
197
|
+
placeholders.map((v, i) => ({[v]: match[i]})),
|
|
198
|
+
);
|
|
184
199
|
}
|
|
185
200
|
}
|
|
186
201
|
});
|
|
@@ -264,10 +279,14 @@ module.exports = function (RED) {
|
|
|
264
279
|
return;
|
|
265
280
|
}
|
|
266
281
|
|
|
267
|
-
const psName = this.ccu.paramsetName(
|
|
282
|
+
const psName = this.ccu.paramsetName(
|
|
283
|
+
iface,
|
|
284
|
+
this.ccu.metadata.devices[iface][filter.channel],
|
|
285
|
+
filter.paramset,
|
|
286
|
+
);
|
|
268
287
|
const paramsetDescription = this.ccu.paramsetDescriptions[psName];
|
|
269
288
|
if (paramsetDescription && paramsetDescription[filter.param]) {
|
|
270
|
-
if (!
|
|
289
|
+
if (!paramsetDescription[filter.param].OPERATIONS && 2) {
|
|
271
290
|
this.error('param ' + filter.param + ' not writeable');
|
|
272
291
|
}
|
|
273
292
|
|
|
@@ -279,8 +298,9 @@ module.exports = function (RED) {
|
|
|
279
298
|
const paramset = {};
|
|
280
299
|
paramset[filter.param] = payload;
|
|
281
300
|
|
|
282
|
-
this.ccu
|
|
283
|
-
.
|
|
301
|
+
this.ccu
|
|
302
|
+
.methodCall(iface, 'putParamset', [filter.channel, filter.paramset, paramset])
|
|
303
|
+
.catch((error) => this.error(error.message));
|
|
284
304
|
}
|
|
285
305
|
|
|
286
306
|
putParamset(filter, payload) {
|
|
@@ -314,14 +334,18 @@ module.exports = function (RED) {
|
|
|
314
334
|
return;
|
|
315
335
|
}
|
|
316
336
|
|
|
317
|
-
const psName = this.ccu.paramsetName(
|
|
337
|
+
const psName = this.ccu.paramsetName(
|
|
338
|
+
iface,
|
|
339
|
+
this.ccu.metadata.devices[iface][filter.channel],
|
|
340
|
+
filter.paramset,
|
|
341
|
+
);
|
|
318
342
|
const paramsetDescription = this.ccu.paramsetDescriptions[psName];
|
|
319
343
|
|
|
320
344
|
const paramset = {};
|
|
321
345
|
|
|
322
|
-
Object.keys(payload).forEach(parameter => {
|
|
346
|
+
Object.keys(payload).forEach((parameter) => {
|
|
323
347
|
if (paramsetDescription && paramsetDescription[parameter]) {
|
|
324
|
-
if (!
|
|
348
|
+
if (!paramsetDescription[parameter].OPERATIONS && 2) {
|
|
325
349
|
this.error('param ' + parameter + ' not writeable');
|
|
326
350
|
}
|
|
327
351
|
|
|
@@ -332,19 +356,21 @@ module.exports = function (RED) {
|
|
|
332
356
|
}
|
|
333
357
|
});
|
|
334
358
|
|
|
335
|
-
this.ccu
|
|
336
|
-
.
|
|
359
|
+
this.ccu
|
|
360
|
+
.methodCall(iface, 'putParamset', [filter.channel, filter.paramset, paramset])
|
|
361
|
+
.catch((error) => this.error(error.message));
|
|
337
362
|
}
|
|
338
363
|
|
|
339
364
|
paramCast(value, paramset) {
|
|
340
365
|
switch (paramset && paramset.TYPE) {
|
|
341
366
|
case 'BOOL':
|
|
342
|
-
|
|
367
|
+
// Fallthrough by intention
|
|
343
368
|
case 'ACTION':
|
|
344
369
|
// OMG this is so ugly...
|
|
345
370
|
if (value === 'false') {
|
|
346
371
|
value = false;
|
|
347
|
-
} else if (!isNaN(value)) {
|
|
372
|
+
} else if (!isNaN(value)) {
|
|
373
|
+
// Make sure that the string "0" gets casted to boolean false
|
|
348
374
|
value = Number(value);
|
|
349
375
|
}
|
|
350
376
|
|
|
@@ -362,12 +388,12 @@ module.exports = function (RED) {
|
|
|
362
388
|
break;
|
|
363
389
|
case 'ENUM':
|
|
364
390
|
if (typeof value === 'string') {
|
|
365
|
-
if (paramset.ENUM &&
|
|
391
|
+
if (paramset.ENUM && paramset.ENUM.includes(value)) {
|
|
366
392
|
value = paramset.ENUM.indexOf(value);
|
|
367
393
|
}
|
|
368
394
|
}
|
|
369
395
|
|
|
370
|
-
|
|
396
|
+
// Fallthrough by intention
|
|
371
397
|
case 'INTEGER':
|
|
372
398
|
value = Number.parseInt(value, 10);
|
|
373
399
|
if (value < paramset.MIN) {
|
package/nodes/ccu-program.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
defaults: {
|
|
6
6
|
name: {value: ''},
|
|
7
7
|
ccuConfig: {value: 'localhost', type: 'ccu-connection', required: true},
|
|
8
|
-
topic: {value: 'ReGaHSS/${Name}'}
|
|
8
|
+
topic: {value: 'ReGaHSS/${Name}'}
|
|
9
9
|
},
|
|
10
10
|
inputs: 1,
|
|
11
11
|
outputs: 1,
|
package/nodes/ccu-program.js
CHANGED
|
@@ -19,7 +19,7 @@ module.exports = function (RED) {
|
|
|
19
19
|
this.name = config.name;
|
|
20
20
|
|
|
21
21
|
if (this.name) {
|
|
22
|
-
this.idSubscription = this.ccu.subscribeProgram(this.name, message => {
|
|
22
|
+
this.idSubscription = this.ccu.subscribeProgram(this.name, (message) => {
|
|
23
23
|
message.topic = this.ccu.topicReplace(config.topic, message);
|
|
24
24
|
this.send(message);
|
|
25
25
|
});
|
|
@@ -32,27 +32,29 @@ module.exports = function (RED) {
|
|
|
32
32
|
_input(message, send, done) {
|
|
33
33
|
switch (typeof message.payload) {
|
|
34
34
|
case 'boolean':
|
|
35
|
-
this.ccu
|
|
36
|
-
.
|
|
35
|
+
this.ccu
|
|
36
|
+
.programActive(this.name || message.topic, message.payload)
|
|
37
|
+
.then((message_) => {
|
|
37
38
|
send(message_);
|
|
38
39
|
|
|
39
40
|
this.status({fill: 'green', shape: 'dot', text: 'connected'});
|
|
40
41
|
done();
|
|
41
42
|
})
|
|
42
|
-
.catch(error => {
|
|
43
|
+
.catch((error) => {
|
|
43
44
|
this.status({fill: 'red', shape: 'dot', text: 'error'});
|
|
44
45
|
done(error);
|
|
45
46
|
});
|
|
46
47
|
break;
|
|
47
48
|
default:
|
|
48
|
-
this.ccu
|
|
49
|
-
.
|
|
49
|
+
this.ccu
|
|
50
|
+
.programExecute(this.name || message.topic)
|
|
51
|
+
.then((message) => {
|
|
50
52
|
send(message);
|
|
51
53
|
|
|
52
54
|
this.status({fill: 'green', shape: 'dot', text: 'connected'});
|
|
53
55
|
done();
|
|
54
56
|
})
|
|
55
|
-
.catch(error => {
|
|
57
|
+
.catch((error) => {
|
|
56
58
|
this.status({fill: 'red', shape: 'dot', text: 'error'});
|
|
57
59
|
done(error);
|
|
58
60
|
});
|
package/nodes/ccu-rpc-event.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
change: {value: false},
|
|
30
30
|
working: {value: false},
|
|
31
31
|
cache: {value: false},
|
|
32
|
-
topic: {value: '${CCU}/${Interface}/${channelName}/${datapoint}'}
|
|
32
|
+
topic: {value: '${CCU}/${Interface}/${channelName}/${datapoint}'}
|
|
33
33
|
},
|
|
34
34
|
inputs: 0,
|
|
35
35
|
outputs: 1,
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
let ifacesLoaded = false;
|
|
53
53
|
let ifacesPending = false;
|
|
54
|
+
let ifaceLoadRetries = 5;
|
|
54
55
|
|
|
55
56
|
$('.filter').each(function () {
|
|
56
57
|
const id = $(this).prop('id');
|
|
@@ -96,7 +97,6 @@
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
ifacesPending = true;
|
|
99
|
-
console.log('loadIfaces()');
|
|
100
100
|
$nodeInputIface.html('<option value="">*</option>');
|
|
101
101
|
const nodeId = $nodeInputCcuConfig.val();
|
|
102
102
|
if (nodeId === '_ADD_') {
|
|
@@ -116,6 +116,12 @@
|
|
|
116
116
|
cb();
|
|
117
117
|
ifacesPending = false;
|
|
118
118
|
}
|
|
119
|
+
}).fail(() => {
|
|
120
|
+
// config node not (yet) available in the runtime - allow retrying
|
|
121
|
+
ifacesPending = false;
|
|
122
|
+
if (ifaceLoadRetries-- > 0) {
|
|
123
|
+
setTimeout(() => loadIfaces(iface, cb), 1000);
|
|
124
|
+
}
|
|
119
125
|
});
|
|
120
126
|
}
|
|
121
127
|
}
|
|
@@ -128,15 +134,17 @@
|
|
|
128
134
|
});
|
|
129
135
|
});
|
|
130
136
|
|
|
137
|
+
// Node-RED 4.x does not reliably fire the change event on dialog
|
|
138
|
+
// open - trigger the initial load explicitly (see ROADMAP.md 8.1)
|
|
139
|
+
$nodeInputCcuConfig.trigger('change');
|
|
140
|
+
|
|
131
141
|
function loadConfig() {
|
|
132
142
|
if (ifacesLoaded) {
|
|
133
|
-
console.log('loadConfig');
|
|
134
143
|
const nodeId = $nodeInputCcuConfig.val();
|
|
135
144
|
const url = 'ccu?config=' + nodeId;
|
|
136
145
|
$.getJSON(url, d => {
|
|
137
146
|
data = d;
|
|
138
147
|
processChannelAssignments();
|
|
139
|
-
console.log('data', data);
|
|
140
148
|
autocomplete();
|
|
141
149
|
});
|
|
142
150
|
}
|
|
@@ -150,7 +158,6 @@
|
|
|
150
158
|
data.nameChannels = {};
|
|
151
159
|
data.nameDevices = {};
|
|
152
160
|
|
|
153
|
-
console.log(data);
|
|
154
161
|
|
|
155
162
|
Object.keys(data.channelRooms).forEach(channel => {
|
|
156
163
|
const room = data.channelRooms[channel];
|
|
@@ -235,7 +242,6 @@
|
|
|
235
242
|
const channelNames = $('#node-input-channelName').val() ? [$('#node-input-channelName').val()] : [];
|
|
236
243
|
const channelTypes = $('#node-input-channelType').val() ? [$('#node-input-channelType').val()] : [];
|
|
237
244
|
//const channelIndexes = $('#node-input-channelIndex').val() ? [$('#node-input-channelIndex').val()] : [];
|
|
238
|
-
const datapoints = $('#node-input-datapoint').val() ? [$('#node-input-datapoint').val()] : [];
|
|
239
245
|
|
|
240
246
|
const lists = {
|
|
241
247
|
device: [],
|
|
@@ -291,7 +297,6 @@
|
|
|
291
297
|
}
|
|
292
298
|
}
|
|
293
299
|
|
|
294
|
-
console.log('autocomplete', {iface, rooms, funcs, devices, deviceNames, deviceTypes, channels, channelNames, channelTypes, datapoints});
|
|
295
300
|
|
|
296
301
|
function checkDatapoint(datapoint) {
|
|
297
302
|
let deviceCheck = false;
|
package/nodes/ccu-rpc-event.js
CHANGED
|
@@ -18,7 +18,7 @@ module.exports = function (RED) {
|
|
|
18
18
|
cache: config.cache,
|
|
19
19
|
change: config.change,
|
|
20
20
|
stable: config.working,
|
|
21
|
-
iface: config.iface
|
|
21
|
+
iface: config.iface,
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
this.ccu.register(this);
|
|
@@ -33,9 +33,8 @@ module.exports = function (RED) {
|
|
|
33
33
|
'channelName',
|
|
34
34
|
'channelType',
|
|
35
35
|
'channelIndex',
|
|
36
|
-
'datapoint'
|
|
37
|
-
|
|
38
|
-
].forEach(attr => {
|
|
36
|
+
'datapoint',
|
|
37
|
+
].forEach((attr) => {
|
|
39
38
|
if (!config[attr]) {
|
|
40
39
|
return;
|
|
41
40
|
}
|
|
@@ -46,7 +45,7 @@ module.exports = function (RED) {
|
|
|
46
45
|
filter[attr] = config[attr];
|
|
47
46
|
}
|
|
48
47
|
});
|
|
49
|
-
this.idSubscription = this.ccu.subscribe(filter, message => {
|
|
48
|
+
this.idSubscription = this.ccu.subscribe(filter, (message) => {
|
|
50
49
|
message.topic = this.ccu.topicReplace(config.topic, message);
|
|
51
50
|
this.send(message);
|
|
52
51
|
});
|