node-red-contrib-s2 0.1.3 → 0.2.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/README.md +36 -8
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/s2/schedule.d.ts +3 -2
- package/dist/lib/s2/schedule.js +8 -7
- package/dist/lib/s2/schedule.js.map +1 -1
- package/dist/lib/s2/session.d.ts +17 -22
- package/dist/lib/s2/session.js +29 -59
- package/dist/lib/s2/session.js.map +1 -1
- package/dist/nodes/s2-cem-config/index.html +10 -0
- package/dist/nodes/s2-ombc/icons/s2-logo.svg +14 -0
- package/dist/nodes/s2-ombc/index.d.ts +30 -0
- package/dist/nodes/s2-ombc/index.html +82 -0
- package/dist/nodes/s2-ombc/index.js +153 -0
- package/dist/nodes/s2-ombc/index.js.map +1 -0
- package/dist/nodes/s2-ombc-config/index.d.ts +3 -0
- package/dist/nodes/s2-ombc-config/index.html +428 -0
- package/dist/nodes/s2-ombc-config/index.js +9 -0
- package/dist/nodes/s2-ombc-config/index.js.map +1 -0
- package/dist/nodes/s2-pebc/icons/s2-logo.svg +14 -0
- package/dist/nodes/s2-pebc/index.d.ts +44 -0
- package/dist/nodes/s2-pebc/index.html +85 -0
- package/dist/nodes/s2-pebc/index.js +417 -0
- package/dist/nodes/s2-pebc/index.js.map +1 -0
- package/dist/nodes/s2-pebc-config/index.d.ts +3 -0
- package/dist/nodes/s2-pebc-config/index.html +64 -0
- package/dist/nodes/s2-pebc-config/index.js +10 -0
- package/dist/nodes/s2-pebc-config/index.js.map +1 -0
- package/dist/nodes/s2-rm/index.d.ts +21 -11
- package/dist/nodes/s2-rm/index.html +12 -88
- package/dist/nodes/s2-rm/index.js +85 -309
- package/dist/nodes/s2-rm/index.js.map +1 -1
- package/dist/nodes/s2-rm-config/index.html +34 -66
- package/dist/nodes/s2-rm-config/index.js +2 -2
- package/dist/nodes/s2-rm-config/index.js.map +1 -1
- package/dist/types/config-nodes.d.ts +20 -6
- package/package.json +6 -2
- package/resources/s2-styles.css +101 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const messages_1 = require("../../lib/s2/messages");
|
|
3
|
+
const PERSISTED_STATUS_KEY_PREFIX = 's2OmbcStatus:';
|
|
4
|
+
module.exports = function (RED) {
|
|
5
|
+
function S2OmbcNode(config) {
|
|
6
|
+
RED.nodes.createNode(this, config);
|
|
7
|
+
const node = this;
|
|
8
|
+
const ombcConfigNode = RED.nodes.getNode(config.ombcConfig);
|
|
9
|
+
if (!ombcConfigNode) {
|
|
10
|
+
node.error('s2-ombc-config node is missing - please configure an OMBC System Description');
|
|
11
|
+
node.status({ fill: 'red', shape: 'dot', text: 'config missing' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let systemDescription = { operationModes: [], transitions: [], timers: [] };
|
|
15
|
+
if (ombcConfigNode.systemDescription) {
|
|
16
|
+
try {
|
|
17
|
+
systemDescription = JSON.parse(ombcConfigNode.systemDescription);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
node.error('Invalid OMBC System Description JSON: ' + e.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const cemStates = new Map();
|
|
24
|
+
function getOrCreateState(cemId) {
|
|
25
|
+
let state = cemStates.get(cemId);
|
|
26
|
+
if (!state) {
|
|
27
|
+
state = { selectedControlType: null };
|
|
28
|
+
cemStates.set(cemId, state);
|
|
29
|
+
}
|
|
30
|
+
return state;
|
|
31
|
+
}
|
|
32
|
+
function getPersistedStatus(cemId) {
|
|
33
|
+
return node.context().get(PERSISTED_STATUS_KEY_PREFIX + cemId) || null;
|
|
34
|
+
}
|
|
35
|
+
function setPersistedStatus(cemId, status) {
|
|
36
|
+
node.context().set(PERSISTED_STATUS_KEY_PREFIX + cemId, status);
|
|
37
|
+
}
|
|
38
|
+
function resolveMode(rawInstr) {
|
|
39
|
+
var _a;
|
|
40
|
+
const modeId = (rawInstr.operation_mode_id || rawInstr.operation_mode);
|
|
41
|
+
if (!modeId)
|
|
42
|
+
return null;
|
|
43
|
+
const factor = typeof rawInstr.operation_mode_factor === 'number' ? rawInstr.operation_mode_factor : 1;
|
|
44
|
+
const modes = systemDescription.operationModes;
|
|
45
|
+
const modeIndex = (_a = modes === null || modes === void 0 ? void 0 : modes.findIndex(m => m.id === modeId)) !== null && _a !== void 0 ? _a : -1;
|
|
46
|
+
const mode = modeIndex >= 0 ? modes[modeIndex] : undefined;
|
|
47
|
+
return {
|
|
48
|
+
id: modeId,
|
|
49
|
+
index: modeIndex,
|
|
50
|
+
label: (mode === null || mode === void 0 ? void 0 : mode.diagnostic_label) || modeId,
|
|
51
|
+
factor,
|
|
52
|
+
powerRanges: (mode === null || mode === void 0 ? void 0 : mode.power_ranges) || []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function handleInstruction(msg) {
|
|
56
|
+
if (msg.controlType !== messages_1.ControlType.OMBC) {
|
|
57
|
+
node.status({ fill: 'yellow', shape: 'ring', text: `ignoring ${String(msg.controlType)} instruction` });
|
|
58
|
+
node.send([msg, null]);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const rawInstr = (msg.ombc || msg.payload);
|
|
62
|
+
const resolved = resolveMode(rawInstr);
|
|
63
|
+
const outMsg = { ...msg };
|
|
64
|
+
if (resolved) {
|
|
65
|
+
outMsg.topic = resolved.label;
|
|
66
|
+
outMsg.ombc = { ...(msg.ombc || {}), operationMode: resolved };
|
|
67
|
+
node.status({ fill: 'green', shape: 'dot', text: resolved.label });
|
|
68
|
+
}
|
|
69
|
+
node.send([outMsg, null]);
|
|
70
|
+
}
|
|
71
|
+
function handleSelectControlType(msg) {
|
|
72
|
+
const cemId = msg.cemId;
|
|
73
|
+
if (!cemId)
|
|
74
|
+
return;
|
|
75
|
+
const payload = msg.payload;
|
|
76
|
+
const controlType = payload.control_type;
|
|
77
|
+
getOrCreateState(cemId).selectedControlType = controlType;
|
|
78
|
+
if (controlType !== messages_1.ControlType.OMBC)
|
|
79
|
+
return;
|
|
80
|
+
node.send([null, { payload: { command: 'SystemDescription', cemId, controlType: messages_1.ControlType.OMBC, ombc: systemDescription } }]);
|
|
81
|
+
const persisted = getPersistedStatus(cemId);
|
|
82
|
+
if (persisted) {
|
|
83
|
+
node.send([null, { payload: { command: 'UpdateStatus', cemId, controlType: messages_1.ControlType.OMBC, ombc: persisted } }]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function handleConfirm(msg, done) {
|
|
87
|
+
const cemId = msg.cemId;
|
|
88
|
+
const payload = msg.payload;
|
|
89
|
+
const modeId = payload.confirmedOperationModeId;
|
|
90
|
+
if (!cemId || !modeId) {
|
|
91
|
+
done(new Error('Confirm message requires cemId and payload.confirmedOperationModeId'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const state = getOrCreateState(cemId);
|
|
95
|
+
if (state.selectedControlType !== messages_1.ControlType.OMBC) {
|
|
96
|
+
node.status({ fill: 'yellow', shape: 'ring', text: 'OMBC not selected - confirm ignored' });
|
|
97
|
+
done();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const factor = payload.operationModeFactor;
|
|
101
|
+
const previous = getPersistedStatus(cemId);
|
|
102
|
+
let status = {
|
|
103
|
+
activeOperationModeId: modeId,
|
|
104
|
+
operationModeFactor: typeof factor === 'number' ? factor : 1
|
|
105
|
+
};
|
|
106
|
+
if (previous && previous.activeOperationModeId !== modeId) {
|
|
107
|
+
status = {
|
|
108
|
+
...status,
|
|
109
|
+
previousOperationModeId: previous.activeOperationModeId,
|
|
110
|
+
transitionTimestamp: new Date().toISOString()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
setPersistedStatus(cemId, status);
|
|
114
|
+
node.status({ fill: 'green', shape: 'dot', text: `confirmed: ${modeId}` });
|
|
115
|
+
node.send([null, { payload: { command: 'UpdateStatus', cemId, controlType: messages_1.ControlType.OMBC, ombc: status } }]);
|
|
116
|
+
done();
|
|
117
|
+
}
|
|
118
|
+
node.status({ fill: 'grey', shape: 'ring', text: 'idle' });
|
|
119
|
+
node.on('input', (msg, _send, done) => {
|
|
120
|
+
const cemId = msg.cemId;
|
|
121
|
+
const topic = msg.topic;
|
|
122
|
+
if (topic === 'Disconnected' && cemId) {
|
|
123
|
+
const state = cemStates.get(cemId);
|
|
124
|
+
if (state)
|
|
125
|
+
state.selectedControlType = null;
|
|
126
|
+
done();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const payload = msg.payload;
|
|
130
|
+
if (!payload || typeof payload !== 'object') {
|
|
131
|
+
done();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if ('confirmedOperationModeId' in payload) {
|
|
135
|
+
handleConfirm(msg, done);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if ('controlType' in msg) {
|
|
139
|
+
handleInstruction(msg);
|
|
140
|
+
done();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (payload.message_type === messages_1.MessageType.SELECT_CONTROL_TYPE) {
|
|
144
|
+
handleSelectControlType(msg);
|
|
145
|
+
done();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
done();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
RED.nodes.registerType('s2-ombc', S2OmbcNode);
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nodes/s2-ombc/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
<script src="resources/node-red-contrib-s2/s2-common.js"></script>
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
(function () {
|
|
4
|
+
function generateUuid () {
|
|
5
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
6
|
+
var r = Math.random() * 16 | 0
|
|
7
|
+
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
|
8
|
+
})
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Every configured mode able to transition to every other configured mode,
|
|
12
|
+
// with no timers - the fully-connected graph friendly mode always assumes.
|
|
13
|
+
function buildTransitions (modeIds) {
|
|
14
|
+
var transitions = []
|
|
15
|
+
for (var i = 0; i < modeIds.length; i++) {
|
|
16
|
+
for (var j = 0; j < modeIds.length; j++) {
|
|
17
|
+
if (i === j) continue
|
|
18
|
+
transitions.push({
|
|
19
|
+
id: generateUuid(),
|
|
20
|
+
from: modeIds[i],
|
|
21
|
+
to: modeIds[j],
|
|
22
|
+
start_timers: [],
|
|
23
|
+
blocking_timers: [],
|
|
24
|
+
abnormal_condition_only: false
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return transitions
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function friendlyStateToSystemDescription (modes) {
|
|
32
|
+
var operationModes = modes.map(function (m) {
|
|
33
|
+
var powerRanges
|
|
34
|
+
if (m.symmetric) {
|
|
35
|
+
powerRanges = [{
|
|
36
|
+
commodity_quantity: 'ELECTRIC.POWER.3_PHASE_SYMMETRIC',
|
|
37
|
+
start_of_range: m.valueSymmetric,
|
|
38
|
+
end_of_range: m.valueSymmetric
|
|
39
|
+
}]
|
|
40
|
+
} else {
|
|
41
|
+
powerRanges = [
|
|
42
|
+
{ commodity_quantity: 'ELECTRIC.POWER.L1', start_of_range: m.valueL1, end_of_range: m.valueL1 },
|
|
43
|
+
{ commodity_quantity: 'ELECTRIC.POWER.L2', start_of_range: m.valueL2, end_of_range: m.valueL2 },
|
|
44
|
+
{ commodity_quantity: 'ELECTRIC.POWER.L3', start_of_range: m.valueL3, end_of_range: m.valueL3 }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
id: m.id,
|
|
49
|
+
diagnostic_label: m.label,
|
|
50
|
+
power_ranges: powerRanges,
|
|
51
|
+
abnormal_condition_only: !!m.abnormalConditionOnly
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
return {
|
|
55
|
+
operationModes: operationModes,
|
|
56
|
+
transitions: buildTransitions(operationModes.map(function (m) { return m.id })),
|
|
57
|
+
timers: []
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isFriendlyPowerRanges (ranges) {
|
|
62
|
+
if (!Array.isArray(ranges)) return false
|
|
63
|
+
if (ranges.length === 1) {
|
|
64
|
+
var r = ranges[0]
|
|
65
|
+
return !!r && r.commodity_quantity === 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' &&
|
|
66
|
+
typeof r.start_of_range === 'number' && r.start_of_range === r.end_of_range
|
|
67
|
+
}
|
|
68
|
+
if (ranges.length === 3) {
|
|
69
|
+
var byCq = {}
|
|
70
|
+
for (var i = 0; i < ranges.length; i++) {
|
|
71
|
+
var rr = ranges[i]
|
|
72
|
+
if (!rr || typeof rr.start_of_range !== 'number' || rr.start_of_range !== rr.end_of_range) return false
|
|
73
|
+
byCq[rr.commodity_quantity] = rr.start_of_range
|
|
74
|
+
}
|
|
75
|
+
return ('ELECTRIC.POWER.L1' in byCq) && ('ELECTRIC.POWER.L2' in byCq) && ('ELECTRIC.POWER.L3' in byCq)
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Strict structural check: true only when systemDescription is exactly what
|
|
81
|
+
// friendly mode itself would produce, so switching modes never silently
|
|
82
|
+
// narrows something Advanced mode expresses that friendly mode can't.
|
|
83
|
+
function isFriendlyRepresentable (sysDesc) {
|
|
84
|
+
if (!sysDesc || typeof sysDesc !== 'object') return false
|
|
85
|
+
var modes = sysDesc.operationModes
|
|
86
|
+
if (!Array.isArray(modes) || modes.length === 0) return false
|
|
87
|
+
|
|
88
|
+
var modeIds = []
|
|
89
|
+
for (var i = 0; i < modes.length; i++) {
|
|
90
|
+
var m = modes[i]
|
|
91
|
+
if (!m || typeof m.id !== 'string' || !m.id || modeIds.indexOf(m.id) !== -1) return false
|
|
92
|
+
if (!isFriendlyPowerRanges(m.power_ranges)) return false
|
|
93
|
+
modeIds.push(m.id)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
var transitions = sysDesc.transitions
|
|
97
|
+
if (!Array.isArray(transitions) || transitions.length !== modeIds.length * (modeIds.length - 1)) return false
|
|
98
|
+
|
|
99
|
+
var seenPairs = {}
|
|
100
|
+
for (var j = 0; j < transitions.length; j++) {
|
|
101
|
+
var t = transitions[j]
|
|
102
|
+
if (!t || modeIds.indexOf(t.from) === -1 || modeIds.indexOf(t.to) === -1 || t.from === t.to) return false
|
|
103
|
+
if (!Array.isArray(t.start_timers) || t.start_timers.length !== 0) return false
|
|
104
|
+
if (!Array.isArray(t.blocking_timers) || t.blocking_timers.length !== 0) return false
|
|
105
|
+
if (t.abnormal_condition_only) return false
|
|
106
|
+
var key = t.from + '>' + t.to
|
|
107
|
+
if (seenPairs[key]) return false
|
|
108
|
+
seenPairs[key] = true
|
|
109
|
+
}
|
|
110
|
+
for (var a = 0; a < modeIds.length; a++) {
|
|
111
|
+
for (var b = 0; b < modeIds.length; b++) {
|
|
112
|
+
if (a === b) continue
|
|
113
|
+
if (!seenPairs[modeIds[a] + '>' + modeIds[b]]) return false
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var timers = sysDesc.timers
|
|
118
|
+
return Array.isArray(timers) && timers.length === 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Only called after isFriendlyRepresentable has passed.
|
|
122
|
+
function systemDescriptionToFriendlyState (sysDesc) {
|
|
123
|
+
return sysDesc.operationModes.map(function (m) {
|
|
124
|
+
var ranges = m.power_ranges
|
|
125
|
+
if (ranges.length === 1) {
|
|
126
|
+
return {
|
|
127
|
+
id: m.id,
|
|
128
|
+
protected: false,
|
|
129
|
+
label: m.diagnostic_label || '',
|
|
130
|
+
abnormalConditionOnly: !!m.abnormal_condition_only,
|
|
131
|
+
symmetric: true,
|
|
132
|
+
valueSymmetric: ranges[0].start_of_range,
|
|
133
|
+
valueL1: 0,
|
|
134
|
+
valueL2: 0,
|
|
135
|
+
valueL3: 0
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
var byCq = {}
|
|
139
|
+
ranges.forEach(function (r) { byCq[r.commodity_quantity] = r.start_of_range })
|
|
140
|
+
return {
|
|
141
|
+
id: m.id,
|
|
142
|
+
protected: false,
|
|
143
|
+
label: m.diagnostic_label || '',
|
|
144
|
+
abnormalConditionOnly: !!m.abnormal_condition_only,
|
|
145
|
+
symmetric: false,
|
|
146
|
+
valueSymmetric: 0,
|
|
147
|
+
valueL1: byCq['ELECTRIC.POWER.L1'],
|
|
148
|
+
valueL2: byCq['ELECTRIC.POWER.L2'],
|
|
149
|
+
valueL3: byCq['ELECTRIC.POWER.L3']
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function defaultStandbyMode () {
|
|
155
|
+
return {
|
|
156
|
+
id: generateUuid(),
|
|
157
|
+
protected: true,
|
|
158
|
+
label: 'Standby/off',
|
|
159
|
+
abnormalConditionOnly: false,
|
|
160
|
+
symmetric: true,
|
|
161
|
+
valueSymmetric: 0,
|
|
162
|
+
valueL1: 0,
|
|
163
|
+
valueL2: 0,
|
|
164
|
+
valueL3: 0
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
RED.nodes.registerType('s2-ombc-config', {
|
|
169
|
+
category: 'config',
|
|
170
|
+
defaults: {
|
|
171
|
+
name: { value: '' },
|
|
172
|
+
systemDescription: { value: '' }
|
|
173
|
+
},
|
|
174
|
+
label: function () {
|
|
175
|
+
return this.name || 's2-ombc-config'
|
|
176
|
+
},
|
|
177
|
+
oneditprepare: function () {
|
|
178
|
+
var listSel = '#s2-ombc-mode-list'
|
|
179
|
+
var currentMode = 'friendly'
|
|
180
|
+
|
|
181
|
+
$('#node-config-input-systemDescription').typedInput({ types: ['json'] })
|
|
182
|
+
|
|
183
|
+
function addModeItem (container, index, opt) {
|
|
184
|
+
opt = opt || {}
|
|
185
|
+
if (!opt.id) opt.id = generateUuid()
|
|
186
|
+
// Node-RED's default editableList item content is `display:grid;
|
|
187
|
+
// grid-template-columns:50% 50%`, which grid-places our two rows into
|
|
188
|
+
// separate 50%-wide columns instead of stacking them - override back
|
|
189
|
+
// to a normal block flow (core nodes like `switch` do the same thing
|
|
190
|
+
// to get their own single-row flex layout instead of the grid default).
|
|
191
|
+
container.css({ overflow: 'visible', display: 'block' })
|
|
192
|
+
|
|
193
|
+
var row1 = $('<div/>', { class: 's2-ombc-mode-row' }).appendTo(container)
|
|
194
|
+
$('<input/>', { type: 'text', class: 's2-ombc-mode-label', placeholder: 'Mode label' })
|
|
195
|
+
.val(opt.label || '')
|
|
196
|
+
.appendTo(row1)
|
|
197
|
+
|
|
198
|
+
var row1b = $('<div/>', { class: 's2-ombc-mode-row' }).appendTo(container)
|
|
199
|
+
var abnormalLabel = $('<label/>', { class: 's2-ombc-mode-checkbox-label' }).appendTo(row1b)
|
|
200
|
+
$('<input/>', { type: 'checkbox', class: 's2-ombc-mode-abnormal' })
|
|
201
|
+
.prop('checked', !!opt.abnormalConditionOnly)
|
|
202
|
+
.appendTo(abnormalLabel)
|
|
203
|
+
abnormalLabel.append(' Abnormal only')
|
|
204
|
+
|
|
205
|
+
var row2 = $('<div/>', { class: 's2-ombc-mode-row' }).appendTo(container)
|
|
206
|
+
var symLabel = $('<label/>', { class: 's2-ombc-mode-checkbox-label' }).appendTo(row2)
|
|
207
|
+
var symCheckbox = $('<input/>', { type: 'checkbox', class: 's2-ombc-mode-symmetric' })
|
|
208
|
+
.prop('checked', opt.symmetric !== false)
|
|
209
|
+
.appendTo(symLabel)
|
|
210
|
+
symLabel.append(' Same value on all phases')
|
|
211
|
+
|
|
212
|
+
var row3 = $('<div/>', { class: 's2-ombc-mode-row' }).appendTo(container)
|
|
213
|
+
var symWrap = $('<span/>', { class: 's2-ombc-mode-value-wrap' }).appendTo(row3)
|
|
214
|
+
var symValue = $('<input/>', { type: 'number', class: 's2-ombc-mode-value-sym', step: '1' })
|
|
215
|
+
.val(opt.valueSymmetric != null ? opt.valueSymmetric : 0)
|
|
216
|
+
.appendTo(symWrap)
|
|
217
|
+
symWrap.append($('<span/>', { class: 's2-unit-label' }).text('W'))
|
|
218
|
+
|
|
219
|
+
var phaseWrap = $('<span/>', { class: 's2-ombc-mode-value-wrap' }).appendTo(row3)
|
|
220
|
+
phaseWrap.append('L1 ')
|
|
221
|
+
var l1 = $('<input/>', { type: 'number', class: 's2-ombc-mode-value-l1', step: '1' }).val(opt.valueL1 || 0).appendTo(phaseWrap)
|
|
222
|
+
phaseWrap.append(' L2 ')
|
|
223
|
+
var l2 = $('<input/>', { type: 'number', class: 's2-ombc-mode-value-l2', step: '1' }).val(opt.valueL2 || 0).appendTo(phaseWrap)
|
|
224
|
+
phaseWrap.append(' L3 ')
|
|
225
|
+
var l3 = $('<input/>', { type: 'number', class: 's2-ombc-mode-value-l3', step: '1' }).val(opt.valueL3 || 0).appendTo(phaseWrap)
|
|
226
|
+
phaseWrap.append($('<span/>', { class: 's2-unit-label' }).text('W'))
|
|
227
|
+
|
|
228
|
+
function updateSymVisibility () {
|
|
229
|
+
if (symCheckbox.prop('checked')) {
|
|
230
|
+
symWrap.show()
|
|
231
|
+
phaseWrap.hide()
|
|
232
|
+
} else {
|
|
233
|
+
symWrap.hide()
|
|
234
|
+
phaseWrap.show()
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
symCheckbox.on('change', updateSymVisibility)
|
|
238
|
+
updateSymVisibility()
|
|
239
|
+
|
|
240
|
+
if (opt.protected) {
|
|
241
|
+
$('<div/>', { class: 's2-ombc-mode-protected-note' }).text('Always present - remove via Advanced mode if not needed.').appendTo(container)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$(listSel).css('min-height', '320px').css('min-width', '480px').editableList({
|
|
246
|
+
addItem: addModeItem,
|
|
247
|
+
removeItem: function (opt) {
|
|
248
|
+
// Protected rows can't be removed through the list's own control -
|
|
249
|
+
// restore it. (Still removable by hand-editing Advanced/JSON mode.)
|
|
250
|
+
if (opt && opt.protected) {
|
|
251
|
+
$(listSel).editableList('addItem', opt)
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
removable: true,
|
|
255
|
+
sortable: true
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
function getFriendlyModes () {
|
|
259
|
+
var modes = []
|
|
260
|
+
$(listSel).editableList('items').each(function () {
|
|
261
|
+
var $item = $(this)
|
|
262
|
+
var data = $item.data('data') || {}
|
|
263
|
+
modes.push({
|
|
264
|
+
id: data.id,
|
|
265
|
+
protected: !!data.protected,
|
|
266
|
+
label: $item.find('.s2-ombc-mode-label').val(),
|
|
267
|
+
abnormalConditionOnly: $item.find('.s2-ombc-mode-abnormal').prop('checked'),
|
|
268
|
+
symmetric: $item.find('.s2-ombc-mode-symmetric').prop('checked'),
|
|
269
|
+
valueSymmetric: Number($item.find('.s2-ombc-mode-value-sym').val()) || 0,
|
|
270
|
+
valueL1: Number($item.find('.s2-ombc-mode-value-l1').val()) || 0,
|
|
271
|
+
valueL2: Number($item.find('.s2-ombc-mode-value-l2').val()) || 0,
|
|
272
|
+
valueL3: Number($item.find('.s2-ombc-mode-value-l3').val()) || 0
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
return modes
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function showFriendlySection () {
|
|
279
|
+
currentMode = 'friendly'
|
|
280
|
+
$('#s2-ombc-friendly-section').show()
|
|
281
|
+
$('#s2-ombc-advanced-section').hide()
|
|
282
|
+
$('#s2-ombc-advanced-warning').hide()
|
|
283
|
+
$('#s2-ombc-tab-friendly').addClass('s2-ombc-tab-active')
|
|
284
|
+
$('#s2-ombc-tab-advanced').removeClass('s2-ombc-tab-active')
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function showAdvancedSection (warningText) {
|
|
288
|
+
currentMode = 'advanced'
|
|
289
|
+
$('#s2-ombc-friendly-section').hide()
|
|
290
|
+
$('#s2-ombc-advanced-section').show()
|
|
291
|
+
if (warningText) {
|
|
292
|
+
$('#s2-ombc-advanced-warning').text(warningText).show()
|
|
293
|
+
} else {
|
|
294
|
+
$('#s2-ombc-advanced-warning').hide()
|
|
295
|
+
}
|
|
296
|
+
$('#s2-ombc-tab-advanced').addClass('s2-ombc-tab-active')
|
|
297
|
+
$('#s2-ombc-tab-friendly').removeClass('s2-ombc-tab-active')
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
$('#s2-ombc-tab-advanced').on('click', function (e) {
|
|
301
|
+
e.preventDefault()
|
|
302
|
+
if (currentMode === 'advanced') return
|
|
303
|
+
var sysDesc = friendlyStateToSystemDescription(getFriendlyModes())
|
|
304
|
+
$('#node-config-input-systemDescription').typedInput('value', JSON.stringify(sysDesc, null, 2))
|
|
305
|
+
showAdvancedSection()
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
$('#s2-ombc-tab-friendly').on('click', function (e) {
|
|
309
|
+
e.preventDefault()
|
|
310
|
+
if (currentMode === 'friendly') return
|
|
311
|
+
var raw = $('#node-config-input-systemDescription').typedInput('value')
|
|
312
|
+
var parsed
|
|
313
|
+
try {
|
|
314
|
+
parsed = JSON.parse(raw)
|
|
315
|
+
} catch (err) {
|
|
316
|
+
showAdvancedSection('Invalid JSON - fix it before switching to the friendly editor.')
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
if (!isFriendlyRepresentable(parsed)) {
|
|
320
|
+
showAdvancedSection('This system description uses something only Advanced mode supports (a genuine power range, a non-standard commodity, a blocked transition, or a timer) - continue editing it here.')
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
$(listSel).editableList('empty')
|
|
324
|
+
systemDescriptionToFriendlyState(parsed).forEach(function (m) {
|
|
325
|
+
$(listSel).editableList('addItem', m)
|
|
326
|
+
})
|
|
327
|
+
showFriendlySection()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
var raw = this.systemDescription
|
|
331
|
+
var parsed = null
|
|
332
|
+
if (raw) {
|
|
333
|
+
try { parsed = JSON.parse(raw) } catch (e) { parsed = null }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!raw) {
|
|
337
|
+
$(listSel).editableList('addItem', defaultStandbyMode())
|
|
338
|
+
showFriendlySection()
|
|
339
|
+
} else if (parsed && isFriendlyRepresentable(parsed)) {
|
|
340
|
+
systemDescriptionToFriendlyState(parsed).forEach(function (m) {
|
|
341
|
+
$(listSel).editableList('addItem', m)
|
|
342
|
+
})
|
|
343
|
+
showFriendlySection()
|
|
344
|
+
} else {
|
|
345
|
+
$('#node-config-input-systemDescription').typedInput('value', raw)
|
|
346
|
+
showAdvancedSection()
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
window.__s2Common.initializeTooltips()
|
|
350
|
+
},
|
|
351
|
+
oneditsave: function () {
|
|
352
|
+
// Only friendly mode needs an explicit save step - Advanced mode's JSON
|
|
353
|
+
// typedInput already holds the value that gets read automatically.
|
|
354
|
+
var $list = $('#s2-ombc-mode-list')
|
|
355
|
+
if ($('#s2-ombc-tab-friendly').hasClass('s2-ombc-tab-active') && $list.length) {
|
|
356
|
+
var modes = []
|
|
357
|
+
$list.editableList('items').each(function () {
|
|
358
|
+
var $item = $(this)
|
|
359
|
+
var data = $item.data('data') || {}
|
|
360
|
+
modes.push({
|
|
361
|
+
id: data.id,
|
|
362
|
+
label: $item.find('.s2-ombc-mode-label').val(),
|
|
363
|
+
abnormalConditionOnly: $item.find('.s2-ombc-mode-abnormal').prop('checked'),
|
|
364
|
+
symmetric: $item.find('.s2-ombc-mode-symmetric').prop('checked'),
|
|
365
|
+
valueSymmetric: Number($item.find('.s2-ombc-mode-value-sym').val()) || 0,
|
|
366
|
+
valueL1: Number($item.find('.s2-ombc-mode-value-l1').val()) || 0,
|
|
367
|
+
valueL2: Number($item.find('.s2-ombc-mode-value-l2').val()) || 0,
|
|
368
|
+
valueL3: Number($item.find('.s2-ombc-mode-value-l3').val()) || 0
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
var sysDesc = friendlyStateToSystemDescription(modes)
|
|
372
|
+
$('#node-config-input-systemDescription').typedInput('value', JSON.stringify(sysDesc, null, 2))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
})()
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
<script type="text/html" data-template-name="s2-ombc-config">
|
|
380
|
+
<div class="s2-form">
|
|
381
|
+
|
|
382
|
+
<div class="form-row">
|
|
383
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
384
|
+
<input type="text" id="node-config-input-name" placeholder="Name (optional)">
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<div class="form-row">
|
|
388
|
+
<label class="s2-section-label"><i class="fa fa-list"></i> System Description<i class="fa fa-info-circle tooltip-icon" data-tooltip="OMBC.SystemDescription: the operation modes this device can be in, and how it can transition between them. Sent to the CEM by s2-ombc once OMBC is the selected control type. Friendly mode covers the common case (fixed-power modes, every mode able to transition to every other); switch to Advanced for anything else - a genuine power range, a non-standard commodity, a blocked transition, or a timer."></i></label>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div class="form-row s2-full-row">
|
|
392
|
+
<div class="s2-ombc-tabs">
|
|
393
|
+
<a href="#" id="s2-ombc-tab-friendly" class="s2-ombc-tab s2-ombc-tab-active">Friendly</a>
|
|
394
|
+
<a href="#" id="s2-ombc-tab-advanced" class="s2-ombc-tab">Advanced (JSON)</a>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<div id="s2-ombc-friendly-section" class="form-row s2-full-row">
|
|
399
|
+
<ol id="s2-ombc-mode-list"></ol>
|
|
400
|
+
</div>
|
|
401
|
+
|
|
402
|
+
<div id="s2-ombc-advanced-section" class="form-row s2-full-row" style="display:none;">
|
|
403
|
+
<div id="s2-ombc-advanced-warning" class="s2-ombc-advanced-warning" style="display:none;"></div>
|
|
404
|
+
<input type="text" id="node-config-input-systemDescription">
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
</div>
|
|
408
|
+
</script>
|
|
409
|
+
|
|
410
|
+
<script type="text/html" data-help-name="s2-ombc-config">
|
|
411
|
+
<p>Configuration for <b>s2-ombc</b>: the OMBC system description (operation modes and how the device can transition between them). Sent to the CEM by <b>s2-ombc</b> once the CEM has selected Operation Mode Based Control.</p>
|
|
412
|
+
|
|
413
|
+
<h3>Friendly mode</h3>
|
|
414
|
+
<p>Covers the common case: a list of operation modes, each with a diagnostic label, an optional "abnormal condition only" flag, and a fixed power level (the same value on all three phases, or a separate value per phase). Every mode is assumed able to transition to every other mode - there is no separate step to define transitions.</p>
|
|
415
|
+
<p>A "Standby/off" mode is always present and can't be removed from the list itself (use Advanced mode if you genuinely don't want one). Add further modes with the "add" button below the list.</p>
|
|
416
|
+
|
|
417
|
+
<h3>Advanced (JSON) mode</h3>
|
|
418
|
+
<p>The raw <code>OMBC.SystemDescription</code> JSON (<code>operationModes</code>, <code>transitions</code>, <code>timers</code>), for anything friendly mode doesn't cover:</p>
|
|
419
|
+
<ul>
|
|
420
|
+
<li>a genuine power range (a mode whose lower and upper bound differ, rather than one fixed value)</li>
|
|
421
|
+
<li>a commodity quantity other than 3-phase-symmetric or per-phase L1/L2/L3</li>
|
|
422
|
+
<li>blocking a specific transition, rather than every mode being able to reach every other mode</li>
|
|
423
|
+
<li>a timer (<code>start_timers</code>/<code>blocking_timers</code> on a transition)</li>
|
|
424
|
+
</ul>
|
|
425
|
+
<p>Switching from Friendly to Advanced converts the current modes to JSON. Switching back only works if the JSON still matches what friendly mode can represent (fixed-power modes, every mode transitioning to every other, no timers) - otherwise the editor stays in Advanced mode and explains why, rather than discarding anything.</p>
|
|
426
|
+
</script>
|
|
427
|
+
|
|
428
|
+
<link rel="stylesheet" href="resources/node-red-contrib-s2/s2-styles.css">
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
module.exports = function (RED) {
|
|
3
|
+
function S2OmbcConfigNodeConstructor(config) {
|
|
4
|
+
RED.nodes.createNode(this, config);
|
|
5
|
+
this.systemDescription = config.systemDescription;
|
|
6
|
+
}
|
|
7
|
+
RED.nodes.registerType('s2-ombc-config', S2OmbcConfigNodeConstructor);
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nodes/s2-ombc-config/index.ts"],"names":[],"mappings":";AAGA,iBAAS,UAAU,GAAe;IAChC,SAAS,2BAA2B,CAA0B,MAAkB;QAC9E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,iBAAiB,GAAG,MAAM,CAAC,iBAA2B,CAAA;IAC7D,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,gBAAgB,EAAE,2BAA2B,CAAC,CAAA;AACvE,CAAC,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1042 1042" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
|
2
|
+
<g transform="matrix(1,0,0,1,-312,-142)">
|
|
3
|
+
<g transform="matrix(1038.75,-1038.75,-1038.75,-1038.75,314.531,1181.19)">
|
|
4
|
+
<path d="M0.75,0.251C0.739,0.262 0.727,0.272 0.715,0.281C0.708,0.286 0.701,0.291 0.694,0.296C0.65,0.325 0.6,0.344 0.549,0.351C0.516,0.356 0.482,0.356 0.449,0.351C0.398,0.344 0.348,0.325 0.303,0.296C0.284,0.283 0.265,0.268 0.248,0.251C0.22,0.223 0.22,0.178 0.248,0.15C0.276,0.123 0.321,0.123 0.348,0.15C0.366,0.168 0.386,0.182 0.407,0.192C0.465,0.22 0.533,0.22 0.591,0.192C0.612,0.182 0.632,0.168 0.649,0.15L0.758,0.042C0.773,0.027 0.789,0.015 0.806,0.006C0.821,-0.003 0.837,-0.009 0.853,-0.013C0.902,-0.026 0.954,-0.022 1,-0L0.75,0.251ZM0.525,0.115C0.499,0.112 0.482,0.097 0.474,0.088C0.465,0.078 0.466,0.062 0.477,0.052C0.487,0.043 0.503,0.044 0.513,0.054C0.515,0.057 0.521,0.063 0.531,0.064C0.541,0.065 0.551,0.061 0.557,0.052C0.564,0.042 0.564,0.026 0.556,0.016C0.549,0.008 0.538,0.006 0.535,0.005L0.33,-0.014L0.447,-0.132C0.457,-0.142 0.474,-0.142 0.483,-0.132C0.493,-0.122 0.493,-0.106 0.483,-0.096L0.443,-0.055L0.541,-0.045C0.551,-0.044 0.577,-0.039 0.596,-0.015C0.618,0.013 0.619,0.052 0.599,0.081C0.596,0.085 0.593,0.089 0.59,0.092C0.573,0.109 0.549,0.118 0.525,0.115ZM0.649,-0.15C0.632,-0.168 0.612,-0.182 0.591,-0.192C0.533,-0.22 0.465,-0.22 0.407,-0.192C0.386,-0.182 0.366,-0.168 0.348,-0.15L0.24,-0.042C0.225,-0.027 0.209,-0.015 0.192,-0.006C0.177,0.003 0.161,0.009 0.145,0.013C0.096,0.026 0.044,0.022 -0.003,0L0.248,-0.251C0.259,-0.262 0.271,-0.272 0.282,-0.281C0.289,-0.286 0.296,-0.291 0.303,-0.296C0.348,-0.325 0.398,-0.344 0.449,-0.351C0.482,-0.356 0.516,-0.356 0.549,-0.351C0.6,-0.344 0.65,-0.325 0.694,-0.296C0.714,-0.283 0.732,-0.268 0.75,-0.251C0.777,-0.223 0.777,-0.178 0.75,-0.15C0.722,-0.123 0.677,-0.123 0.649,-0.15Z" style="fill:url(#s2logo-gradient);fill-rule:nonzero;"/>
|
|
5
|
+
</g>
|
|
6
|
+
</g>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="s2logo-gradient" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,-1,0,3.73045e-07)">
|
|
9
|
+
<stop offset="0" style="stop-color:rgb(45,19,75);stop-opacity:1"/>
|
|
10
|
+
<stop offset="0.37" style="stop-color:rgb(25,38,101);stop-opacity:1"/>
|
|
11
|
+
<stop offset="1" style="stop-color:rgb(17,88,163);stop-opacity:1"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
</svg>
|