@bldgblocks/node-red-contrib-control 0.1.32 → 0.1.33
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/nodes/accumulate-block.html +1 -1
- package/nodes/add-block.html +1 -1
- package/nodes/analog-switch-block.html +1 -1
- package/nodes/and-block.html +1 -1
- package/nodes/average-block.html +1 -1
- package/nodes/boolean-switch-block.html +1 -1
- package/nodes/boolean-to-number-block.html +1 -1
- package/nodes/cache-block.html +1 -1
- package/nodes/call-status-block.html +1 -1
- package/nodes/changeover-block.html +1 -1
- package/nodes/comment-block.html +1 -1
- package/nodes/compare-block.html +1 -1
- package/nodes/contextual-label-block.html +1 -1
- package/nodes/convert-block.html +1 -1
- package/nodes/count-block.html +2 -2
- package/nodes/delay-block.html +1 -1
- package/nodes/divide-block.html +1 -1
- package/nodes/edge-block.html +1 -1
- package/nodes/enum-switch-block.html +1 -1
- package/nodes/frequency-block.html +1 -1
- package/nodes/global-getter.html +34 -3
- package/nodes/global-getter.js +71 -45
- package/nodes/global-setter.html +21 -10
- package/nodes/global-setter.js +154 -79
- package/nodes/history-collector.html +283 -0
- package/nodes/history-collector.js +150 -0
- package/nodes/history-config.html +236 -0
- package/nodes/history-config.js +8 -0
- package/nodes/hysteresis-block.html +1 -1
- package/nodes/interpolate-block.html +1 -1
- package/nodes/latch-block.html +1 -1
- package/nodes/load-sequence-block.html +1 -1
- package/nodes/max-block.html +1 -1
- package/nodes/memory-block.html +1 -1
- package/nodes/min-block.html +1 -1
- package/nodes/minmax-block.html +1 -1
- package/nodes/modulo-block.html +1 -1
- package/nodes/multiply-block.html +1 -1
- package/nodes/negate-block.html +1 -1
- package/nodes/network-point-registry.html +86 -0
- package/nodes/network-point-registry.js +90 -0
- package/nodes/network-read.html +56 -0
- package/nodes/network-read.js +59 -0
- package/nodes/network-register.html +110 -0
- package/nodes/network-register.js +161 -0
- package/nodes/network-write.html +64 -0
- package/nodes/network-write.js +126 -0
- package/nodes/nullify-block.html +1 -1
- package/nodes/on-change-block.html +1 -1
- package/nodes/oneshot-block.html +1 -1
- package/nodes/or-block.html +1 -1
- package/nodes/pid-block.html +1 -1
- package/nodes/priority-block.html +1 -1
- package/nodes/rate-limit-block.html +2 -2
- package/nodes/rate-of-change-block.html +1 -1
- package/nodes/rate-of-change-block.js +5 -2
- package/nodes/round-block.html +6 -5
- package/nodes/round-block.js +5 -3
- package/nodes/saw-tooth-wave-block.html +2 -2
- package/nodes/scale-range-block.html +1 -1
- package/nodes/sine-wave-block.html +2 -2
- package/nodes/string-builder-block.html +1 -1
- package/nodes/subtract-block.html +1 -1
- package/nodes/thermistor-block.html +1 -1
- package/nodes/tick-tock-block.html +2 -2
- package/nodes/time-sequence-block.html +1 -1
- package/nodes/triangle-wave-block.html +2 -2
- package/nodes/tstat-block.html +1 -1
- package/nodes/units-block.html +8 -38
- package/nodes/units-block.js +3 -42
- package/package.json +11 -4
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function HistoryCollectorNode(config) {
|
|
3
|
+
RED.nodes.createNode(this, config);
|
|
4
|
+
this.historyConfig = RED.nodes.getNode(config.historyConfig);
|
|
5
|
+
this.seriesName = config.seriesName;
|
|
6
|
+
this.storageType = config.storageType || 'memory';
|
|
7
|
+
this.tags = config.tags || '';
|
|
8
|
+
const node = this;
|
|
9
|
+
|
|
10
|
+
// Parse tags into key-value object
|
|
11
|
+
function parseTags(tagsString) {
|
|
12
|
+
if (!tagsString) return {};
|
|
13
|
+
const tags = {};
|
|
14
|
+
const pairs = tagsString.split(',').map(t => t.trim());
|
|
15
|
+
tags["historyGroup"] = node.historyConfig.name;
|
|
16
|
+
pairs.forEach((pair, index) => {
|
|
17
|
+
if (pair.includes('=') || pair.includes(':')) {
|
|
18
|
+
const [key, value] = pair.includes('=') ? pair.split('=') : pair.split(':');
|
|
19
|
+
if (key && value) tags[key.trim()] = value.trim();
|
|
20
|
+
} else {
|
|
21
|
+
tags[`tag${index}`] = pair;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return tags;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
node.on('input', function(msg) {
|
|
28
|
+
// Guard against invalid message
|
|
29
|
+
if (!msg) {
|
|
30
|
+
node.status({ fill: "red", shape: "ring", text: "invalid message" });
|
|
31
|
+
node.error('Invalid message received');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validate configuration
|
|
36
|
+
if (!node.historyConfig) {
|
|
37
|
+
node.status({ fill: "red", shape: "ring", text: "missing history config" });
|
|
38
|
+
node.error('Missing history configuration', msg);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!node.seriesName) {
|
|
42
|
+
node.status({ fill: "red", shape: "ring", text: "missing series name" });
|
|
43
|
+
node.error('Missing series name', msg);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!node.historyConfig.name) {
|
|
47
|
+
node.status({ fill: "red", shape: "ring", text: "missing bucket name" });
|
|
48
|
+
node.error('Missing bucket name in history configuration', msg);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate payload
|
|
53
|
+
let payloadValue = msg.payload;
|
|
54
|
+
let formattedValue;
|
|
55
|
+
if (typeof payloadValue === 'number') {
|
|
56
|
+
formattedValue = isNaN(payloadValue) ? null : payloadValue;
|
|
57
|
+
} else if (typeof payloadValue === 'boolean') {
|
|
58
|
+
formattedValue = payloadValue;
|
|
59
|
+
} else if (typeof payloadValue === 'string') {
|
|
60
|
+
formattedValue = payloadValue;
|
|
61
|
+
if (payloadValue.endsWith('i') && !isNaN(parseInt(payloadValue))) {
|
|
62
|
+
formattedValue = parseInt(payloadValue); // Handle InfluxDB integer format
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
node.status({ fill: "red", shape: "ring", text: "invalid payload" });
|
|
66
|
+
node.warn(`Invalid payload type: ${typeof payloadValue}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (formattedValue === null) {
|
|
71
|
+
node.status({ fill: "red", shape: "ring", text: "invalid payload" });
|
|
72
|
+
node.warn(`Invalid payload value: ${msg.payload}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Construct line protocol
|
|
77
|
+
const escapedMeasurementName = node.seriesName.replace(/[, =]/g, '\\$&');
|
|
78
|
+
const msNow = Date.now();
|
|
79
|
+
const timestamp = msNow * 1e6;
|
|
80
|
+
const tagsObj = parseTags(node.tags);
|
|
81
|
+
const tagsString = Object.entries(tagsObj)
|
|
82
|
+
.map(([k, v]) => `${k.replace(/[, =]/g, '\\$&')}=${v.replace(/[, =]/g, '\\$&')}`)
|
|
83
|
+
.join(',');
|
|
84
|
+
const valueString = typeof formattedValue === 'string' ? `"${formattedValue}"` : formattedValue;
|
|
85
|
+
const line = `${escapedMeasurementName}${tagsString ? ',' + tagsString : ''} value=${valueString} ${timestamp}`;
|
|
86
|
+
|
|
87
|
+
// Set initial status
|
|
88
|
+
node.status({ fill: "green", shape: "dot", text: "configuration received" });
|
|
89
|
+
|
|
90
|
+
// Handle storage type
|
|
91
|
+
if (node.storageType === 'memory') {
|
|
92
|
+
const contextKey = `history_data_${node.historyConfig.name}`;
|
|
93
|
+
let bucketData = node.context().global.get(contextKey) || [];
|
|
94
|
+
bucketData.push(line);
|
|
95
|
+
|
|
96
|
+
const maxMemoryBytes = (node.historyConfig.maxMemoryMb || 10) * 1024 * 1024;
|
|
97
|
+
let totalSize = Buffer.byteLength(JSON.stringify(bucketData), 'utf8');
|
|
98
|
+
while (totalSize > maxMemoryBytes && bucketData.length > 0) {
|
|
99
|
+
bucketData.shift();
|
|
100
|
+
totalSize = Buffer.byteLength(JSON.stringify(bucketData), 'utf8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
node.context().global.set(contextKey, bucketData);
|
|
104
|
+
node.status({ fill: "blue", shape: "dot", text: `stored: ${valueString}` });
|
|
105
|
+
} else if (node.storageType === 'lineProtocol') {
|
|
106
|
+
msg.measurement = escapedMeasurementName;
|
|
107
|
+
msg.payload = line;
|
|
108
|
+
node.send(msg);
|
|
109
|
+
node.status({ fill: "blue", shape: "dot", text: `sent: ${valueString}` });
|
|
110
|
+
} else if (node.storageType === 'object') {
|
|
111
|
+
msg.measurement = escapedMeasurementName;
|
|
112
|
+
msg.payload = {
|
|
113
|
+
measurement: escapedMeasurementName,
|
|
114
|
+
tags: Object.entries(tagsObj).map(([k, v]) => `${k}=${v}`),
|
|
115
|
+
value: formattedValue,
|
|
116
|
+
timestamp: timestamp
|
|
117
|
+
};
|
|
118
|
+
node.send(msg);
|
|
119
|
+
node.status({ fill: "blue", shape: "dot", text: `sent: ${valueString}` });
|
|
120
|
+
} else if (node.storageType === 'objectArray') {
|
|
121
|
+
msg.measurement = escapedMeasurementName;
|
|
122
|
+
msg.timestamp = timestamp;
|
|
123
|
+
msg.payload = [
|
|
124
|
+
{
|
|
125
|
+
value: formattedValue
|
|
126
|
+
},
|
|
127
|
+
tagsObj
|
|
128
|
+
]
|
|
129
|
+
node.send(msg);
|
|
130
|
+
node.status({ fill: "blue", shape: "dot", text: `sent: ${valueString}` });
|
|
131
|
+
} else if (node.storageType === 'batchObject') {
|
|
132
|
+
msg.payload = {
|
|
133
|
+
measurement: escapedMeasurementName,
|
|
134
|
+
timestamp: timestamp,
|
|
135
|
+
fields: {
|
|
136
|
+
value: formattedValue
|
|
137
|
+
},
|
|
138
|
+
tags: tagsObj
|
|
139
|
+
}
|
|
140
|
+
node.send(msg);
|
|
141
|
+
node.status({ fill: "blue", shape: "dot", text: `sent: ${valueString}` });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
node.on("close", function(done) {
|
|
146
|
+
done();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
RED.nodes.registerType("history-collector", HistoryCollectorNode);
|
|
150
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="history-config">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-config-input-name" placeholder="History Config">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label><i class="fa fa-tags"></i> Tags</label>
|
|
9
|
+
<div style="margin-top: 10px;">
|
|
10
|
+
<table id="node-config-tags-table" style="width:100%;">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Key</th>
|
|
14
|
+
<th>Value</th>
|
|
15
|
+
<th></th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody></tbody>
|
|
19
|
+
</table>
|
|
20
|
+
<button id="node-config-add-tag" type="button" style="margin-top: 10px;">Add Tag</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="form-row">
|
|
25
|
+
<label><i class="fa fa-list"></i> Series Configurations</label>
|
|
26
|
+
<div style="margin-top: 10px;">
|
|
27
|
+
<table id="node-config-series-table" style="width:100%;">
|
|
28
|
+
<thead>
|
|
29
|
+
<tr>
|
|
30
|
+
<th>Series Name</th>
|
|
31
|
+
<th>Units</th>
|
|
32
|
+
<th></th>
|
|
33
|
+
</tr>
|
|
34
|
+
</thead>
|
|
35
|
+
<tbody></tbody>
|
|
36
|
+
</table>
|
|
37
|
+
<button id="node-config-add-series" type="button" style="margin-top: 10px;">Add Series</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<script type="text/javascript">
|
|
43
|
+
RED.nodes.registerType('history-config', {
|
|
44
|
+
category: 'config',
|
|
45
|
+
defaults: {
|
|
46
|
+
series: { value: [], required: true },
|
|
47
|
+
name: { value: "default", required: true },
|
|
48
|
+
tags: { value: [], required: true }
|
|
49
|
+
},
|
|
50
|
+
label: function() {
|
|
51
|
+
return this.name || "History Config";
|
|
52
|
+
},
|
|
53
|
+
paletteLabel: "History Config",
|
|
54
|
+
oneditprepare: function() {
|
|
55
|
+
const node = this;
|
|
56
|
+
const tableBody = $("#node-config-series-table tbody");
|
|
57
|
+
const tagsTableBody = $("#node-config-tags-table tbody");
|
|
58
|
+
const addTagButton = $("#node-config-add-tag");
|
|
59
|
+
const nameInput = $("#node-config-input-name");
|
|
60
|
+
node.tags = node.tags || [];
|
|
61
|
+
node.series = node.series || [];
|
|
62
|
+
|
|
63
|
+
// --- Naming Logic ---
|
|
64
|
+
function sanitizeName(input, fallback) {
|
|
65
|
+
const safeInput = (input || "").toString();
|
|
66
|
+
const cleaned = safeInput.trim().replace(/[^a-zA-Z0-9_]/g, '_');
|
|
67
|
+
return cleaned || fallback;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleNameInput() {
|
|
71
|
+
const cleanName = sanitizeName(nameInput.val());
|
|
72
|
+
nameInput.val(cleanName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
nameInput.val(sanitizeName(node.name));
|
|
76
|
+
nameInput.on("change", function() {
|
|
77
|
+
const cleanName = sanitizeName(nameInput.val());
|
|
78
|
+
nameInput.val(cleanName);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// --- Tags Table Logic ---
|
|
82
|
+
|
|
83
|
+
// Helper to ensure tags are always tag0, tag1, tag2...
|
|
84
|
+
function renumberTags() {
|
|
85
|
+
tagsTableBody.find("tr").each(function(index) {
|
|
86
|
+
$(this).find(".node-config-tag-label").text("tag" + index);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function addTagRow(data = {}) {
|
|
91
|
+
// Determine label based on current row count (though renumberTags fixes it anyway)
|
|
92
|
+
const currentIndex = tagsTableBody.find("tr").length;
|
|
93
|
+
|
|
94
|
+
const row = $(`
|
|
95
|
+
<tr>
|
|
96
|
+
<td style="vertical-align: middle; text-align: center;">
|
|
97
|
+
<span class="node-config-tag-label" style="font-weight: bold; color: #666;">tag${currentIndex}</span>
|
|
98
|
+
</td>
|
|
99
|
+
<td>
|
|
100
|
+
<input type="text" class="node-config-tag-value" value="${data.tagValue || ''}" style="width: 100%;" placeholder="value" required>
|
|
101
|
+
</td>
|
|
102
|
+
<td>
|
|
103
|
+
<button type="button" class="node-config-remove-tag" style="color: white; background-color: red; border-radius: 2px; border: none; padding: 2px 8px; cursor: pointer;">Remove</button>
|
|
104
|
+
</td>
|
|
105
|
+
</tr>
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
tagsTableBody.append(row);
|
|
109
|
+
|
|
110
|
+
row.find(".node-config-remove-tag").on("click", () => {
|
|
111
|
+
row.remove();
|
|
112
|
+
renumberTags();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
renumberTags(); // Ensure specific ordering
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Initialize tags table
|
|
119
|
+
(node.tags || []).forEach(addTagRow);
|
|
120
|
+
if (node.tags.length === 0) {
|
|
121
|
+
addTagRow({tagValue: "virtual"});
|
|
122
|
+
addTagRow({tagValue: "physical"});
|
|
123
|
+
addTagRow({tagValue: "input"});
|
|
124
|
+
addTagRow({tagValue: "output"});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
addTagButton.on("click", () => addTagRow());
|
|
128
|
+
|
|
129
|
+
// --- Series Table Logic ---
|
|
130
|
+
|
|
131
|
+
function addRow(data = {}) {
|
|
132
|
+
const row = $(`
|
|
133
|
+
<tr>
|
|
134
|
+
<td><input type="text" class="node-config-series-name" value="${data.seriesName || ''}" style="width: 100%;" required></td>
|
|
135
|
+
<td><input type="text" class="node-config-series-units" value="${data.seriesUnits || ''}" style="width: 100%;"></td>
|
|
136
|
+
<td><button type="button" class="node-config-remove-row" style="color: white; background-color: red; border-radius: 2px; border: none; padding: 2px 8px; cursor: pointer;">Remove</button></td>
|
|
137
|
+
</tr>
|
|
138
|
+
`);
|
|
139
|
+
|
|
140
|
+
row.find(".node-config-series-name").on("change", function() {
|
|
141
|
+
$(this).val(sanitizeName($(this).val(), ""));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
tableBody.append(row);
|
|
145
|
+
row.find(".node-config-remove-row").on("click", () => row.remove());
|
|
146
|
+
row.find(".node-config-remove-row").hover(
|
|
147
|
+
function() { $(this).css("background-color", "#cc0000"); },
|
|
148
|
+
function() { $(this).css("background-color", "red"); }
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
(node.series || []).forEach(addRow);
|
|
153
|
+
if (node.series.length === 0) {
|
|
154
|
+
addRow({seriesName: "OutsideTemp", seriesUnits: "°F"});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
$("#node-config-add-series").on("click", () => addRow());
|
|
158
|
+
},
|
|
159
|
+
oneditsave: function() {
|
|
160
|
+
function cleanSaveStr(str) {
|
|
161
|
+
return (str || "").toString().trim().replace(/[^a-zA-Z0-9_]/g, '_');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let hasTagError = false;
|
|
165
|
+
// Save Tags
|
|
166
|
+
const tagsTableBody = $("#node-config-tags-table tbody");
|
|
167
|
+
const tags = [];
|
|
168
|
+
|
|
169
|
+
tagsTableBody.find("tr").each(function(index) {
|
|
170
|
+
const tagName = "tag" + index;
|
|
171
|
+
const tagValue = $(this).find(".node-config-tag-value").val().trim();
|
|
172
|
+
|
|
173
|
+
if (!tagValue) {
|
|
174
|
+
RED.notify(`Tag Value is required for ${tagName}`, "error");
|
|
175
|
+
hasTagError = true;
|
|
176
|
+
return false; // Break the loop
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
tags.push({
|
|
180
|
+
tagName: tagName,
|
|
181
|
+
tagValue: tagValue
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Stop save if tags are invalid
|
|
186
|
+
if (hasTagError) {
|
|
187
|
+
throw new Error("Tag validation failed");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
this.tags = tags;
|
|
191
|
+
|
|
192
|
+
// Save Series
|
|
193
|
+
const tableBody = $("#node-config-series-table tbody");
|
|
194
|
+
const series = [];
|
|
195
|
+
const seriesNames = new Set();
|
|
196
|
+
|
|
197
|
+
let hasError = false;
|
|
198
|
+
tableBody.find("tr").each(function() {
|
|
199
|
+
const rawName = $(this).find(".node-config-series-name").val();
|
|
200
|
+
const seriesName = cleanSaveStr(rawName);
|
|
201
|
+
|
|
202
|
+
if (!seriesName) {
|
|
203
|
+
RED.notify("Series Name is required for all series", "error");
|
|
204
|
+
hasError = true;
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
if (seriesNames.has(seriesName)) {
|
|
208
|
+
RED.notify(`Duplicate Series Name: ${seriesName}`, "error");
|
|
209
|
+
hasError = true;
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
seriesNames.add(seriesName);
|
|
213
|
+
series.push({
|
|
214
|
+
seriesName: seriesName,
|
|
215
|
+
seriesUnits: $(this).find(".node-config-series-units").val(),
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (hasError) {
|
|
220
|
+
throw new Error("Validation failed");
|
|
221
|
+
}
|
|
222
|
+
if (series.length === 0) {
|
|
223
|
+
RED.notify("At least one valid series is required", "error");
|
|
224
|
+
throw new Error("No valid series");
|
|
225
|
+
}
|
|
226
|
+
this.series = series;
|
|
227
|
+
this.name = cleanSaveStr($("#node-config-input-name").val()) || 'default';
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<script type="text/markdown" data-help-name="history-config">
|
|
233
|
+
Store configuration for history series selections.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function HistoryConfigNode(n) {
|
|
3
|
+
RED.nodes.createNode(this, n);
|
|
4
|
+
this.series = n.series || [];
|
|
5
|
+
this.name = n.name ? n.name.replace(/[^a-zA-Z0-9_]/g, '_') : 'default';
|
|
6
|
+
}
|
|
7
|
+
RED.nodes.registerType("history-config", HistoryConfigNode);
|
|
8
|
+
};
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
31
31
|
<script type="text/javascript">
|
|
32
32
|
RED.nodes.registerType("hysteresis-block", {
|
|
33
|
-
category: "control",
|
|
33
|
+
category: "bldgblocks control",
|
|
34
34
|
color: "#301934",
|
|
35
35
|
defaults: {
|
|
36
36
|
name: { value: "" },
|
package/nodes/latch-block.html
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
50
50
|
<script type="text/javascript">
|
|
51
51
|
RED.nodes.registerType("load-sequence-block", {
|
|
52
|
-
category: "control",
|
|
52
|
+
category: "bldgblocks control",
|
|
53
53
|
color: "#301934",
|
|
54
54
|
defaults: {
|
|
55
55
|
name: { value: "" },
|
package/nodes/max-block.html
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
15
15
|
<script type="text/javascript">
|
|
16
16
|
RED.nodes.registerType("max-block", {
|
|
17
|
-
category: "control",
|
|
17
|
+
category: "bldgblocks control",
|
|
18
18
|
color: "#301934",
|
|
19
19
|
defaults: {
|
|
20
20
|
name: { value: "" },
|
package/nodes/memory-block.html
CHANGED
package/nodes/min-block.html
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
15
15
|
<script type="text/javascript">
|
|
16
16
|
RED.nodes.registerType("min-block", {
|
|
17
|
-
category: "control",
|
|
17
|
+
category: "bldgblocks control",
|
|
18
18
|
color: "#301934",
|
|
19
19
|
defaults: {
|
|
20
20
|
name: { value: "" },
|
package/nodes/minmax-block.html
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<!-- JavaScript Section: Registers the node and handles editor logic -->
|
|
20
20
|
<script type="text/javascript">
|
|
21
21
|
RED.nodes.registerType("minmax-block", {
|
|
22
|
-
category: "control",
|
|
22
|
+
category: "bldgblocks control",
|
|
23
23
|
color: "#301934",
|
|
24
24
|
defaults: {
|
|
25
25
|
name: { value: "" },
|
package/nodes/modulo-block.html
CHANGED
package/nodes/negate-block.html
CHANGED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="network-point-registry">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<input type="text" id="node-config-input-name" placeholder="Main Registry">
|
|
5
|
+
</div>
|
|
6
|
+
<div class="form-tips">
|
|
7
|
+
<p><b>Point Registry</b></p>
|
|
8
|
+
<p>This node maintains the mapping between Network Point IDs (integers) and Global Variables.</p>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="form-row">
|
|
12
|
+
<label><i class="fa fa-list-ul"></i> Points</label>
|
|
13
|
+
<div id="node-input-point-list-div"
|
|
14
|
+
style="
|
|
15
|
+
border:1px solid #ccc;
|
|
16
|
+
height:200px; /* set a fixed height */
|
|
17
|
+
overflow-y:auto; /* enable vertical scrolling */
|
|
18
|
+
padding:5px;
|
|
19
|
+
box-sizing:border-box;">
|
|
20
|
+
<!-- The list will be injected by the JavaScript below -->
|
|
21
|
+
<ul id="node-input-point-list" style="margin:0;padding-left:1.2em;"></ul>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script type="text/javascript">
|
|
27
|
+
RED.nodes.registerType('network-point-registry', {
|
|
28
|
+
category: 'config',
|
|
29
|
+
defaults: {
|
|
30
|
+
name: { value: "" }
|
|
31
|
+
},
|
|
32
|
+
label: function() {
|
|
33
|
+
return this.name || "Point Registry";
|
|
34
|
+
},
|
|
35
|
+
oneditprepare: function() {
|
|
36
|
+
const node = this;
|
|
37
|
+
const $list = $("#node-input-point-list");
|
|
38
|
+
|
|
39
|
+
function loadPoints() {
|
|
40
|
+
$.getJSON(`/network-point-registry/list/${node.id}`, function(data) {
|
|
41
|
+
$list.empty();
|
|
42
|
+
if (!data.length) return $list.append('<li>No points defined</li>');
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
data.sort((a, b) => parseInt(a.id, 10) - parseInt(b.id, 10));
|
|
46
|
+
|
|
47
|
+
data.forEach(pt => {
|
|
48
|
+
const li = $('<li>').css({ display:'flex', alignItems:'center' });
|
|
49
|
+
|
|
50
|
+
// Text part: ID + path
|
|
51
|
+
const txt = $('<span>')
|
|
52
|
+
.text(`ID ${pt.id}: ${pt.path ?? 'not set'}`)
|
|
53
|
+
.css({ flexGrow:1 }); // push button to the right
|
|
54
|
+
|
|
55
|
+
// Button part
|
|
56
|
+
const btn = $('<button type="button" class="editor-button">')
|
|
57
|
+
.attr('title','Find node')
|
|
58
|
+
.html('<i class="fa fa-search"></i>')
|
|
59
|
+
.on('click', function (e) {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
|
|
62
|
+
RED.view.reveal(pt.nodeId);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
li.append(txt, btn);
|
|
66
|
+
$list.append(li);
|
|
67
|
+
});
|
|
68
|
+
}).fail(function() {
|
|
69
|
+
$list.html('<li style="color:red;">Could not load points</li>');
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
loadPoints();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<script type="text/markdown" data-help-name="network-point-registry">
|
|
79
|
+
Central database mapping Point IDs to Global Variables.
|
|
80
|
+
|
|
81
|
+
### Details
|
|
82
|
+
|
|
83
|
+
**API for Developers:**
|
|
84
|
+
* `register(id, meta)`: Claim an ID.
|
|
85
|
+
* `lookup(id)`: Find the path/store for an ID.
|
|
86
|
+
</script>
|