node-red-contrib-s2 0.1.2 → 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 +10 -4
- package/resources/s2-common.js +37 -0
- package/resources/s2-styles.css +263 -0
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
<script src="resources/node-red-contrib-s2/s2-common.js"></script>
|
|
2
2
|
<script type="text/javascript">
|
|
3
|
-
var GRID_CONNECTIONS = {
|
|
4
|
-
'1x16A': 3680,
|
|
5
|
-
'3x16A': 11040,
|
|
6
|
-
'3x25A': 17250,
|
|
7
|
-
'3x32A': 22080,
|
|
8
|
-
'3x40A': 27600,
|
|
9
|
-
'3x63A': 43470
|
|
10
|
-
}
|
|
11
|
-
|
|
12
3
|
RED.nodes.registerType('s2-rm-config', {
|
|
13
4
|
category: 'config',
|
|
14
5
|
defaults: {
|
|
@@ -21,10 +12,8 @@
|
|
|
21
12
|
manufacturer: { value: 'Victron Energy' },
|
|
22
13
|
model: { value: 'Virtual RM' },
|
|
23
14
|
firmwareVersion: { value: '1.0.0' },
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
gridConnection: { value: '' },
|
|
27
|
-
customMaxPowerW: { value: '' },
|
|
15
|
+
providesPowerMeasurement: { value: '3_PHASE_SYMMETRIC' },
|
|
16
|
+
providesForecast: { value: false },
|
|
28
17
|
instructionPollIntervalMs: { value: 2000, validate: RED.validators.number() },
|
|
29
18
|
skipInstructionStatus: { value: false }
|
|
30
19
|
},
|
|
@@ -56,26 +45,20 @@
|
|
|
56
45
|
$(this).prop('checked', savedRoles.indexOf($(this).val()) !== -1)
|
|
57
46
|
})
|
|
58
47
|
|
|
59
|
-
function updateBatteryLimitsVisibility () {
|
|
60
|
-
var hasStorage = $('.s2-rm-config-role-checkbox[value="ENERGY_STORAGE"]').is(':checked')
|
|
61
|
-
$('#s2-rm-config-battery-section').toggle(hasStorage)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
48
|
$('.s2-rm-config-role-checkbox').on('change', function () {
|
|
65
49
|
if ($('.s2-rm-config-role-checkbox:checked').length === 0) {
|
|
66
50
|
$(this).prop('checked', true)
|
|
67
51
|
}
|
|
68
|
-
updateBatteryLimitsVisibility()
|
|
69
52
|
})
|
|
70
53
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
54
|
+
// Migrate legacy boolean to string value
|
|
55
|
+
if (this.providesPowerMeasurement === true) {
|
|
56
|
+
this.providesPowerMeasurement = '3_PHASE_SYMMETRIC'
|
|
57
|
+
$('#node-config-input-providesPowerMeasurement').val('3_PHASE_SYMMETRIC')
|
|
58
|
+
} else if (this.providesPowerMeasurement === false) {
|
|
59
|
+
this.providesPowerMeasurement = ''
|
|
60
|
+
$('#node-config-input-providesPowerMeasurement').val('')
|
|
76
61
|
}
|
|
77
|
-
$('#node-config-input-gridConnection').on('change', updateCustomPowerVisibility)
|
|
78
|
-
updateCustomPowerVisibility()
|
|
79
62
|
|
|
80
63
|
$('#node-config-input-skipInstructionStatus').prop('checked', this.skipInstructionStatus === true)
|
|
81
64
|
|
|
@@ -158,22 +141,20 @@
|
|
|
158
141
|
</div>
|
|
159
142
|
|
|
160
143
|
<div class="form-row">
|
|
161
|
-
<label for="node-config-input-
|
|
162
|
-
<select id="node-config-input-
|
|
163
|
-
<option value=""
|
|
164
|
-
<option value="
|
|
165
|
-
<option value="
|
|
166
|
-
<option value="3x25A">3x25A (17.3 kW)</option>
|
|
167
|
-
<option value="3x32A">3x32A (22 kW)</option>
|
|
168
|
-
<option value="3x40A">3x40A (27.6 kW)</option>
|
|
169
|
-
<option value="3x63A">3x63A (43.5 kW)</option>
|
|
170
|
-
<option value="custom">Custom</option>
|
|
144
|
+
<label for="node-config-input-providesPowerMeasurement"><i class="fa fa-bolt"></i> Power Meas.<i class="fa fa-info-circle tooltip-icon" data-tooltip="Commodity quantities for power measurement. Advertised to the CEM in ResourceManagerDetails."></i></label>
|
|
145
|
+
<select id="node-config-input-providesPowerMeasurement" style="width: 70%;">
|
|
146
|
+
<option value="">None</option>
|
|
147
|
+
<option value="3_PHASE_SYMMETRIC">3-phase symmetric</option>
|
|
148
|
+
<option value="L1_L2_L3">Per phase (L1, L2, L3)</option>
|
|
171
149
|
</select>
|
|
172
150
|
</div>
|
|
173
151
|
|
|
174
|
-
<div class="form-row"
|
|
175
|
-
<label for="node-config-input-
|
|
176
|
-
|
|
152
|
+
<div class="form-row">
|
|
153
|
+
<label for="node-config-input-providesForecast">
|
|
154
|
+
<i class="fa fa-line-chart"></i> Forecast
|
|
155
|
+
</label>
|
|
156
|
+
<input type="checkbox" id="node-config-input-providesForecast" style="width: auto; margin-top: 6px;">
|
|
157
|
+
<span style="margin-left: 6px;">Provides power forecast</span>
|
|
177
158
|
</div>
|
|
178
159
|
|
|
179
160
|
<div class="form-row">
|
|
@@ -196,33 +177,6 @@
|
|
|
196
177
|
<input type="text" id="node-config-input-firmwareVersion" placeholder="e.g. 1.0.0">
|
|
197
178
|
</div>
|
|
198
179
|
|
|
199
|
-
<div id="s2-rm-config-battery-section">
|
|
200
|
-
<div class="form-row">
|
|
201
|
-
<label class="s2-section-label">
|
|
202
|
-
<i class="fa fa-battery-three-quarters"></i> Battery Limits
|
|
203
|
-
<i class="fa fa-info-circle tooltip-icon" data-tooltip="Hardware ceiling for battery power. CEM instructions are clamped to these values. Set to 0 for no limit."></i>
|
|
204
|
-
</label>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<div class="form-row s2-battery-row">
|
|
208
|
-
<label for="node-config-input-maxBatteryChargePower">
|
|
209
|
-
<i class="fa fa-arrow-circle-down"></i> Charge
|
|
210
|
-
<i class="fa fa-info-circle tooltip-icon" data-tooltip="Maximum battery charge power in Watts. Never exceeded when writing DESS limits."></i>
|
|
211
|
-
</label>
|
|
212
|
-
<input type="number" id="node-config-input-maxBatteryChargePower" placeholder="0 = no limit" min="0" step="1">
|
|
213
|
-
<span class="s2-unit-label">W</span>
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div class="form-row s2-battery-row">
|
|
217
|
-
<label for="node-config-input-maxBatteryDischargePower">
|
|
218
|
-
<i class="fa fa-arrow-circle-up"></i> Discharge
|
|
219
|
-
<i class="fa fa-info-circle tooltip-icon" data-tooltip="Maximum battery discharge power in Watts. Never exceeded when writing DESS limits."></i>
|
|
220
|
-
</label>
|
|
221
|
-
<input type="number" id="node-config-input-maxBatteryDischargePower" placeholder="0 = no limit" min="0" step="1">
|
|
222
|
-
<span class="s2-unit-label">W</span>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
180
|
<div class="form-row">
|
|
227
181
|
<a href="#" class="s2-advanced-toggle" id="s2-rm-config-advanced-toggle">
|
|
228
182
|
<i class="fa fa-cog"></i> Advanced
|
|
@@ -251,4 +205,18 @@
|
|
|
251
205
|
</div>
|
|
252
206
|
</script>
|
|
253
207
|
|
|
208
|
+
<script type="text/html" data-help-name="s2-rm-config">
|
|
209
|
+
<p>Configuration for <b>s2-rm</b>: the Resource Manager's identity, advertised capabilities, and protocol-level behavior. Sent to the CEM in <code>ResourceManagerDetails</code> during the S2 handshake.</p>
|
|
210
|
+
|
|
211
|
+
<h3>Identity</h3>
|
|
212
|
+
<p><b>Resource ID</b> is a stable UUID identifying this RM to the CEM - it's also used to build the CEM connection URL on <b>s2-cem-config</b>. Auto-generated the first time the node is added; avoid changing it after deployment, since the CEM associates state with this id.</p>
|
|
213
|
+
<p><b>Roles</b> and <b>Control Types</b> are advertised capabilities - at least one of each must be selected. Control types beyond OMBC and PEBC (FRBC, DDBC, PPBC) have no dedicated node yet; their instructions are still forwarded on <b>s2-rm</b>'s instructions output for you to handle yourself.</p>
|
|
214
|
+
|
|
215
|
+
<h3>Power Meas. / Forecast</h3>
|
|
216
|
+
<p>Advertise whether this RM reports <code>PowerMeasurement</code> (as a single 3-phase-symmetric value, or per-phase L1/L2/L3) and/or <code>PowerForecast</code>. These affect what's advertised to the CEM and whether <b>s2-rm</b> emits its <code>PowerMeasurementStart</code> signal on output 1 - they don't gate whether a <code>PowerMeasurement</code>/<code>Forecast</code> command you send is actually forwarded, which happens regardless.</p>
|
|
217
|
+
|
|
218
|
+
<h3>Advanced</h3>
|
|
219
|
+
<p><b>Poll interval</b> controls how often <b>s2-rm</b> checks for pending instructions whose <code>execution_time</code> has now arrived (PEBC instructions bypass this queue entirely and are delivered immediately). <b>Skip instruction status updates</b> suppresses automatic <code>InstructionStatusUpdate(ACCEPTED/STARTED)</code> messages - <code>ReceptionStatus</code> and control-type status messages are still sent - useful if the CEM generates high load from frequent instructions.</p>
|
|
220
|
+
</script>
|
|
221
|
+
|
|
254
222
|
<link rel="stylesheet" href="resources/node-red-contrib-s2/s2-styles.css">
|
|
@@ -10,8 +10,8 @@ module.exports = function (RED) {
|
|
|
10
10
|
this.model = config.model;
|
|
11
11
|
this.firmwareVersion = config.firmwareVersion;
|
|
12
12
|
this.controlTypes = config.controlTypes;
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
13
|
+
this.providesPowerMeasurement = config.providesPowerMeasurement;
|
|
14
|
+
this.providesForecast = config.providesForecast === true;
|
|
15
15
|
this.instructionPollIntervalMs = config.instructionPollIntervalMs != null ? Number(config.instructionPollIntervalMs) : 2000;
|
|
16
16
|
this.skipInstructionStatus = config.skipInstructionStatus === true;
|
|
17
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nodes/s2-rm-config/index.ts"],"names":[],"mappings":";AAGA,iBAAS,UAAU,GAAe;IAChC,SAAS,yBAAyB,CAAwB,MAAkB;QAC1E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAoB,CAAA;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAgB,CAAA;QACrC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAe,CAAA;QACnC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAe,CAAA;QACnC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAyB,CAAA;QACvD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/nodes/s2-rm-config/index.ts"],"names":[],"mappings":";AAGA,iBAAS,UAAU,GAAe;IAChC,SAAS,yBAAyB,CAAwB,MAAkB;QAC1E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAoB,CAAA;QAC7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAgB,CAAA;QACrC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAe,CAAA;QACnC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAe,CAAA;QACnC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAyB,CAAA;QACvD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAsB,CAAA;QACjD,IAAI,CAAC,wBAAwB,GAAG,MAAM,CAAC,wBAAkC,CAAA;QACzE,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,KAAK,IAAI,CAAA;QACxD,IAAI,CAAC,yBAAyB,GAAG,MAAM,CAAC,yBAAyB,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC3H,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC,qBAAqB,KAAK,IAAI,CAAA;IACpE,CAAC;IAED,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAA;AACnE,CAAC,CAAA"}
|
|
@@ -12,12 +12,10 @@ export interface S2RmConfigNode extends NodeRedNode {
|
|
|
12
12
|
model: string;
|
|
13
13
|
firmwareVersion: string;
|
|
14
14
|
controlTypes: string;
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
gridConnection: string;
|
|
20
|
-
customMaxPowerW: number | undefined;
|
|
15
|
+
/** Commodity quantity list for power measurement, or '' for none. */
|
|
16
|
+
providesPowerMeasurement: string;
|
|
17
|
+
/** Whether this RM provides power forecasts to the CEM. */
|
|
18
|
+
providesForecast: boolean;
|
|
21
19
|
/** How often (ms) to poll for due pending instructions. Defaults to 2000 if not set. */
|
|
22
20
|
instructionPollIntervalMs: number;
|
|
23
21
|
/** When true, skip sending InstructionStatusUpdate(ACCEPTED/STARTED) automatically.
|
|
@@ -25,6 +23,22 @@ export interface S2RmConfigNode extends NodeRedNode {
|
|
|
25
23
|
* acknowledgment messages and the extra D-Bus traffic causes CPU load. */
|
|
26
24
|
skipInstructionStatus: boolean;
|
|
27
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Server-side shape of the s2-ombc-config config node.
|
|
28
|
+
* Use RED.nodes.getNode(id) and cast to this type.
|
|
29
|
+
*/
|
|
30
|
+
export interface S2OmbcConfigNode extends NodeRedNode {
|
|
31
|
+
/** JSON-encoded OMBCSystemDescriptionConfig: { operationModes, transitions, timers } */
|
|
32
|
+
systemDescription: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Server-side shape of the s2-pebc-config config node.
|
|
36
|
+
* Use RED.nodes.getNode(id) and cast to this type.
|
|
37
|
+
*/
|
|
38
|
+
export interface S2PebcConfigNode extends NodeRedNode {
|
|
39
|
+
gridConnection: string;
|
|
40
|
+
customMaxPowerW: number | undefined;
|
|
41
|
+
}
|
|
28
42
|
/**
|
|
29
43
|
* Server-side shape of the s2-cem-config config node.
|
|
30
44
|
* Use RED.nodes.getNode(id) and cast to this type.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-s2",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Node-RED nodes for S2 energy management protocol (EN 50491-12-2)",
|
|
5
5
|
"author": "Victron Energy BV",
|
|
6
6
|
"repository": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"main": "dist/lib/index.js",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/",
|
|
13
|
+
"resources/",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE",
|
|
15
16
|
"CHANGELOG.md"
|
|
@@ -20,14 +21,19 @@
|
|
|
20
21
|
"s2-rm-config": "./dist/nodes/s2-rm-config/index.js",
|
|
21
22
|
"s2-cem-config": "./dist/nodes/s2-cem-config/index.js",
|
|
22
23
|
"s2-websocket": "./dist/nodes/s2-websocket/index.js",
|
|
23
|
-
"s2-rm": "./dist/nodes/s2-rm/index.js"
|
|
24
|
+
"s2-rm": "./dist/nodes/s2-rm/index.js",
|
|
25
|
+
"s2-ombc-config": "./dist/nodes/s2-ombc-config/index.js",
|
|
26
|
+
"s2-ombc": "./dist/nodes/s2-ombc/index.js",
|
|
27
|
+
"s2-pebc-config": "./dist/nodes/s2-pebc-config/index.js",
|
|
28
|
+
"s2-pebc": "./dist/nodes/s2-pebc/index.js"
|
|
24
29
|
}
|
|
25
30
|
},
|
|
26
31
|
"scripts": {
|
|
27
|
-
"prepublishOnly": "npm run build",
|
|
32
|
+
"prepublishOnly": "npm run build && npm run check-package-files",
|
|
28
33
|
"build": "tsc && npm run copy-html",
|
|
29
34
|
"clean": "rm -rf dist",
|
|
30
|
-
"copy-html": "
|
|
35
|
+
"copy-html": "node scripts/copy-html.js",
|
|
36
|
+
"check-package-files": "node scripts/check-package-files.js",
|
|
31
37
|
"test": "npm run lint && npm run test:unit",
|
|
32
38
|
"test:unit": "jest",
|
|
33
39
|
"test:watch": "jest --watch",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* global $ */
|
|
2
|
+
|
|
3
|
+
window.__s2Common = window.__s2Common || {}
|
|
4
|
+
|
|
5
|
+
window.__s2Common.initializeTooltips = function () {
|
|
6
|
+
$('.s2-tooltip-container').remove()
|
|
7
|
+
$('.s2-form .tooltip-icon').off('mouseenter.s2 mouseleave.s2')
|
|
8
|
+
|
|
9
|
+
$('.s2-form .tooltip-icon').on('mouseenter.s2', function () {
|
|
10
|
+
const $icon = $(this)
|
|
11
|
+
const tooltipText = $icon.attr('data-tooltip')
|
|
12
|
+
if (!tooltipText) return
|
|
13
|
+
|
|
14
|
+
const $tooltip = $('<div class="s2-tooltip-container"></div>').text(tooltipText)
|
|
15
|
+
$('body').append($tooltip)
|
|
16
|
+
|
|
17
|
+
const iconOffset = $icon.offset()
|
|
18
|
+
const tooltipHeight = $tooltip.outerHeight()
|
|
19
|
+
const tooltipWidth = $tooltip.outerWidth()
|
|
20
|
+
|
|
21
|
+
$tooltip.css({
|
|
22
|
+
top: iconOffset.top - tooltipHeight - 8,
|
|
23
|
+
left: iconOffset.left - (tooltipWidth / 2) + ($icon.outerWidth() / 2)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
$icon.data('s2-tooltip', $tooltip)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
$('.s2-form .tooltip-icon').on('mouseleave.s2', function () {
|
|
30
|
+
const $icon = $(this)
|
|
31
|
+
const $tooltip = $icon.data('s2-tooltip')
|
|
32
|
+
if ($tooltip) {
|
|
33
|
+
$tooltip.remove()
|
|
34
|
+
$icon.removeData('s2-tooltip')
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S2 Node-RED Nodes - Stylesheet
|
|
3
|
+
*
|
|
4
|
+
* All selectors MUST be scoped under .s2-form to prevent
|
|
5
|
+
* affecting other Node-RED nodes.
|
|
6
|
+
*
|
|
7
|
+
* Label and input sizing intentionally defers to Node-RED defaults so that
|
|
8
|
+
* config node selectors (red-ui-search-container) lay out correctly inline.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ============================================================================
|
|
12
|
+
FORM LAYOUT
|
|
13
|
+
============================================================================ */
|
|
14
|
+
|
|
15
|
+
.s2-form {
|
|
16
|
+
overflow-x: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Keep icon + text vertically centred inside labels without overriding the
|
|
20
|
+
width - Node-RED's default ~110px label width is used instead. */
|
|
21
|
+
/* Label width slightly wider than NR default (110px) to accommodate the
|
|
22
|
+
field icon + text. The tooltip icon is absolutely positioned inside the
|
|
23
|
+
label so it never pushes the input - all inputs align consistently. */
|
|
24
|
+
.s2-form .form-row label {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 4px;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
width: 120px !important;
|
|
30
|
+
position: relative;
|
|
31
|
+
padding-right: 20px;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.s2-form .form-row input[type="checkbox"] {
|
|
36
|
+
width: auto !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Tooltip icon - absolutely positioned at the right edge of the label. */
|
|
40
|
+
.s2-form .tooltip-icon {
|
|
41
|
+
position: absolute;
|
|
42
|
+
right: 2px;
|
|
43
|
+
top: 50%;
|
|
44
|
+
transform: translateY(-50%);
|
|
45
|
+
font-size: 0.8em;
|
|
46
|
+
color: #00BCFF;
|
|
47
|
+
cursor: help;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Tooltip container - appended to <body> by s2-common.js so it is never
|
|
51
|
+
clipped by a parent overflow or panel boundary. */
|
|
52
|
+
.s2-tooltip-container {
|
|
53
|
+
position: absolute;
|
|
54
|
+
background: #fff;
|
|
55
|
+
color: #444;
|
|
56
|
+
padding: 8px 12px;
|
|
57
|
+
border: 1px solid #ccc;
|
|
58
|
+
border-radius: 3px;
|
|
59
|
+
font-size: 13px;
|
|
60
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
61
|
+
line-height: 1.4;
|
|
62
|
+
max-width: 280px;
|
|
63
|
+
z-index: 10000;
|
|
64
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ============================================================================
|
|
69
|
+
CONTROL TYPES / ROLES - Checkbox Grid
|
|
70
|
+
============================================================================ */
|
|
71
|
+
|
|
72
|
+
/* Section labels (Roles, Control Types) span the full row.
|
|
73
|
+
Selector includes the `label` element type to match (and, combined with
|
|
74
|
+
source order, beat) .s2-form .form-row label's specificity above - a
|
|
75
|
+
class-only selector loses that tie-break despite both using !important,
|
|
76
|
+
leaving the label clipped to 120px with its text and tooltip icon
|
|
77
|
+
overlapping instead of spanning the row. */
|
|
78
|
+
.s2-form label.s2-section-label {
|
|
79
|
+
width: 100% !important;
|
|
80
|
+
display: inline-block;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Indent checkbox grid to align with inputs (matches Node-RED default label width). */
|
|
84
|
+
.s2-form .s2-full-row {
|
|
85
|
+
display: block;
|
|
86
|
+
margin-left: 110px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.s2-form .s2-control-types-container {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: 116px 116px;
|
|
92
|
+
gap: 6px 8px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.s2-form .s2-checkbox-label {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 6px;
|
|
99
|
+
width: auto !important;
|
|
100
|
+
font-weight: normal;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.s2-form .s2-checkbox-label input[type="checkbox"] {
|
|
105
|
+
width: auto;
|
|
106
|
+
margin: 0;
|
|
107
|
+
flex-shrink: 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ============================================================================
|
|
111
|
+
UNIT LABELS AND BATTERY LIMIT ROWS
|
|
112
|
+
============================================================================ */
|
|
113
|
+
|
|
114
|
+
/* Unit suffix (W, ms) displayed after a number input */
|
|
115
|
+
.s2-form .s2-unit-label {
|
|
116
|
+
margin-left: 6px;
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
color: #555;
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Battery limit rows use flex so the unit label stays on the same line as
|
|
123
|
+
the input, and are indented to visually nest them under the section header. */
|
|
124
|
+
.s2-form .s2-battery-row {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
padding-left: 20px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.s2-form .s2-battery-row label {
|
|
131
|
+
flex-shrink: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.s2-form .s2-battery-row input[type="number"] {
|
|
135
|
+
flex: 1;
|
|
136
|
+
min-width: 0;
|
|
137
|
+
width: auto !important;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Advanced section rows use flex to keep unit labels inline. */
|
|
141
|
+
.s2-form .s2-advanced-row {
|
|
142
|
+
display: flex;
|
|
143
|
+
align-items: center;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.s2-form .s2-advanced-row label {
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.s2-form .s2-advanced-row input[type="number"] {
|
|
151
|
+
flex: 1;
|
|
152
|
+
min-width: 0;
|
|
153
|
+
width: auto !important;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Advanced toggle link */
|
|
157
|
+
.s2-form .s2-advanced-toggle {
|
|
158
|
+
color: #666;
|
|
159
|
+
font-size: 0.9em;
|
|
160
|
+
text-decoration: none;
|
|
161
|
+
display: inline-flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 5px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.s2-form .s2-advanced-toggle:hover {
|
|
167
|
+
color: #333;
|
|
168
|
+
text-decoration: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* ============================================================================
|
|
172
|
+
OMBC SYSTEM DESCRIPTION - Friendly/Advanced tabs and operation-mode rows
|
|
173
|
+
============================================================================ */
|
|
174
|
+
|
|
175
|
+
.s2-form .s2-ombc-tabs {
|
|
176
|
+
display: flex;
|
|
177
|
+
gap: 4px;
|
|
178
|
+
border-bottom: 1px solid #ccc;
|
|
179
|
+
margin-bottom: 8px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.s2-form .s2-ombc-tab {
|
|
183
|
+
padding: 5px 12px;
|
|
184
|
+
color: #666;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
border: 1px solid transparent;
|
|
187
|
+
border-bottom: none;
|
|
188
|
+
border-radius: 3px 3px 0 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.s2-form .s2-ombc-tab:hover {
|
|
192
|
+
color: #333;
|
|
193
|
+
text-decoration: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.s2-form .s2-ombc-tab.s2-ombc-tab-active {
|
|
197
|
+
color: #333;
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
border-color: #ccc;
|
|
200
|
+
background: #fff;
|
|
201
|
+
margin-bottom: -1px;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.s2-form .s2-ombc-mode-row {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 10px;
|
|
208
|
+
flex-wrap: wrap;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.s2-form .s2-ombc-mode-row + .s2-ombc-mode-row {
|
|
212
|
+
margin-top: 4px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.s2-form .s2-ombc-mode-row input[type="text"] {
|
|
216
|
+
flex: 1;
|
|
217
|
+
min-width: 100px;
|
|
218
|
+
width: auto !important;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.s2-form .s2-ombc-mode-row input[type="number"] {
|
|
222
|
+
width: 70px !important;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* `label` element type included for the same specificity reason as
|
|
226
|
+
label.s2-section-label above. */
|
|
227
|
+
.s2-form label.s2-ombc-mode-checkbox-label {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 4px;
|
|
231
|
+
width: auto !important;
|
|
232
|
+
white-space: nowrap;
|
|
233
|
+
font-weight: normal;
|
|
234
|
+
cursor: pointer;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.s2-form .s2-ombc-mode-checkbox-label input[type="checkbox"] {
|
|
238
|
+
width: auto !important;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.s2-form .s2-ombc-mode-value-wrap {
|
|
242
|
+
display: inline-flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
gap: 4px;
|
|
245
|
+
white-space: nowrap;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.s2-form .s2-ombc-mode-protected-note {
|
|
249
|
+
color: #888;
|
|
250
|
+
font-size: 0.85em;
|
|
251
|
+
font-style: italic;
|
|
252
|
+
margin-top: 2px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.s2-form .s2-ombc-advanced-warning {
|
|
256
|
+
color: #a06400;
|
|
257
|
+
background: #fff8e6;
|
|
258
|
+
border: 1px solid #f0d78c;
|
|
259
|
+
border-radius: 3px;
|
|
260
|
+
padding: 6px 10px;
|
|
261
|
+
margin-bottom: 8px;
|
|
262
|
+
font-size: 0.9em;
|
|
263
|
+
}
|