node-red-contrib-power-saver 3.6.2 → 4.1.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.
Files changed (109) hide show
  1. package/.eslintrc.js +15 -0
  2. package/docs/.vuepress/components/AdsenseAdd.vue +40 -0
  3. package/docs/.vuepress/components/DonateButtons.vue +26 -3
  4. package/docs/.vuepress/components/VippsPlakat.vue +20 -0
  5. package/docs/.vuepress/config.js +17 -10
  6. package/docs/.vuepress/public/ads.txt +1 -0
  7. package/docs/README.md +6 -4
  8. package/docs/changelog/README.md +61 -1
  9. package/docs/contribute/README.md +12 -3
  10. package/docs/examples/README.md +6 -0
  11. package/docs/examples/example-grid-tariff-capacity-flow.json +23 -7
  12. package/docs/examples/example-grid-tariff-capacity-part.md +657 -22
  13. package/docs/faq/README.md +5 -1
  14. package/docs/faq/best-save-viewer.md +9 -1
  15. package/docs/guide/README.md +36 -5
  16. package/docs/images/best-save-config.png +0 -0
  17. package/docs/images/combine-two-lowest-price.png +0 -0
  18. package/docs/images/fixed-schedule-config.png +0 -0
  19. package/docs/images/global-context-window.png +0 -0
  20. package/docs/images/lowest-price-config.png +0 -0
  21. package/docs/images/node-ps-schedule-merger.png +0 -0
  22. package/docs/images/node-ps-strategy-fixed-schedule.png +0 -0
  23. package/docs/images/ps-strategy-fixed-schedule-example.png +0 -0
  24. package/docs/images/schedule-merger-config.png +0 -0
  25. package/docs/images/schedule-merger-example-1.png +0 -0
  26. package/docs/images/vipps-plakat.png +0 -0
  27. package/docs/images/vipps-qr.png +0 -0
  28. package/docs/images/vipps-smiling-rgb-orange-pos.png +0 -0
  29. package/docs/nodes/README.md +24 -4
  30. package/docs/nodes/dynamic-commands.md +91 -0
  31. package/docs/nodes/dynamic-config.md +84 -0
  32. package/docs/nodes/old-power-saver-doc.md +4 -0
  33. package/docs/nodes/ps-elvia-add-tariff.md +16 -0
  34. package/docs/nodes/ps-general-add-tariff.md +22 -0
  35. package/docs/nodes/ps-receive-price.md +14 -1
  36. package/docs/nodes/ps-schedule-merger.md +231 -0
  37. package/docs/nodes/ps-strategy-best-save.md +65 -109
  38. package/docs/nodes/ps-strategy-fixed-schedule.md +113 -0
  39. package/docs/nodes/ps-strategy-heat-capacitor.md +14 -1
  40. package/docs/nodes/ps-strategy-lowest-price.md +61 -110
  41. package/docs/nodes/strategy-input.md +4 -0
  42. package/package.json +5 -2
  43. package/src/elvia/elvia-add-tariff.html +1 -2
  44. package/src/elvia/elvia-add-tariff.js +0 -1
  45. package/src/elvia/elvia-api.js +6 -0
  46. package/src/elvia/elvia-tariff.html +1 -1
  47. package/src/general-add-tariff.html +14 -8
  48. package/src/general-add-tariff.js +0 -1
  49. package/src/handle-input.js +97 -106
  50. package/src/handle-output.js +117 -0
  51. package/src/receive-price-functions.js +3 -3
  52. package/src/schedule-merger-functions.js +98 -0
  53. package/src/schedule-merger.html +135 -0
  54. package/src/schedule-merger.js +112 -0
  55. package/src/strategy-best-save.html +38 -1
  56. package/src/strategy-best-save.js +17 -63
  57. package/src/strategy-fixed-schedule.html +339 -0
  58. package/src/strategy-fixed-schedule.js +84 -0
  59. package/src/strategy-functions.js +35 -0
  60. package/src/strategy-lowest-price.html +76 -38
  61. package/src/strategy-lowest-price.js +16 -35
  62. package/src/utils.js +75 -2
  63. package/test/commands-input-best-save.test.js +142 -0
  64. package/test/commands-input-lowest-price.test.js +149 -0
  65. package/test/commands-input-schedule-merger.test.js +128 -0
  66. package/test/data/best-save-overlap-result.json +5 -1
  67. package/test/data/best-save-result.json +4 -0
  68. package/test/data/commands-result-best-save.json +383 -0
  69. package/test/data/commands-result-lowest-price.json +340 -0
  70. package/test/data/fixed-schedule-result.json +353 -0
  71. package/test/data/lowest-price-result-cont-max-fail.json +5 -1
  72. package/test/data/lowest-price-result-cont-max.json +3 -1
  73. package/test/data/lowest-price-result-cont.json +8 -1
  74. package/test/data/lowest-price-result-missing-end.json +8 -3
  75. package/test/data/lowest-price-result-neg-cont.json +27 -0
  76. package/test/data/lowest-price-result-neg-split.json +23 -0
  77. package/test/data/lowest-price-result-split-allday.json +3 -1
  78. package/test/data/lowest-price-result-split-allday10.json +1 -0
  79. package/test/data/lowest-price-result-split-max.json +3 -1
  80. package/test/data/lowest-price-result-split.json +3 -1
  81. package/test/data/merge-schedule-data.js +238 -0
  82. package/test/data/negative-prices.json +197 -0
  83. package/test/data/nordpool-event-prices.json +96 -480
  84. package/test/data/nordpool-zero-prices.json +90 -0
  85. package/test/data/reconfigResult.js +1 -0
  86. package/test/data/result.js +1 -0
  87. package/test/data/tibber-result-end-0-24h.json +12 -2
  88. package/test/data/tibber-result-end-0.json +12 -2
  89. package/test/data/tibber-result.json +1 -0
  90. package/test/receive-price.test.js +22 -0
  91. package/test/schedule-merger-functions.test.js +101 -0
  92. package/test/schedule-merger-test-utils.js +27 -0
  93. package/test/schedule-merger.test.js +130 -0
  94. package/test/send-config-input.test.js +46 -2
  95. package/test/strategy-best-save-test-utils.js +1 -1
  96. package/test/strategy-best-save.test.js +91 -0
  97. package/test/strategy-fixed-schedule.test.js +117 -0
  98. package/test/strategy-heat-capacitor.test.js +1 -1
  99. package/test/strategy-lowest-price-functions.test.js +1 -1
  100. package/test/strategy-lowest-price-test-utils.js +31 -0
  101. package/test/strategy-lowest-price.test.js +55 -45
  102. package/test/test-utils.js +43 -36
  103. package/test/utils.test.js +13 -0
  104. package/docs/images/node-power-saver.png +0 -0
  105. package/docs/nodes/power-saver.md +0 -23
  106. package/src/power-saver.html +0 -116
  107. package/src/power-saver.js +0 -260
  108. package/test/commands-input.test.js +0 -47
  109. package/test/power-saver.test.js +0 -189
@@ -0,0 +1,339 @@
1
+ <script type="text/javascript">
2
+ const defaultDaysSfs = { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true };
3
+ RED.nodes.registerType("ps-strategy-fixed-schedule", {
4
+ category: "Power Saver",
5
+ color: "#a6bbcf",
6
+ defaults: {
7
+ name: { value: "Fixed Schedule" },
8
+ periods: {
9
+ value: [{ start: "00", value: true }],
10
+ validate: function () {
11
+ return !this.periods.some((p) => !/^(true)|(false)$/.test("" + p.value));
12
+ },
13
+ },
14
+ validFrom: {
15
+ value: null,
16
+ required: false,
17
+ validate: RED.validators.regex(/^$|^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/),
18
+ },
19
+ validTo: {
20
+ value: null,
21
+ required: false,
22
+ validate: RED.validators.regex(/^$|^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$/),
23
+ },
24
+ days: { value: { ...defaultDaysSfs } },
25
+ sendCurrentValueWhenRescheduling: {
26
+ value: true,
27
+ required: true,
28
+ align: "left",
29
+ },
30
+ outputValueForOn: {
31
+ value: true,
32
+ required: true,
33
+ validate: RED.validators.typedInput("outputValueForOntype", false),
34
+ },
35
+ outputValueForOff: {
36
+ value: false,
37
+ required: true,
38
+ validate: RED.validators.typedInput("outputValueForOfftype", false),
39
+ },
40
+ outputValueForOntype: {
41
+ value: "bool",
42
+ required: true,
43
+ },
44
+ outputValueForOfftype: {
45
+ value: "bool",
46
+ required: true,
47
+ },
48
+ outputIfNoSchedule: { value: "false", required: true, align: "left" },
49
+ contextStorage: { value: "default", required: false, align: "left" },
50
+ },
51
+ hours: [
52
+ "00",
53
+ "01",
54
+ "02",
55
+ "03",
56
+ "04",
57
+ "05",
58
+ "06",
59
+ "07",
60
+ "08",
61
+ "09",
62
+ "10",
63
+ "11",
64
+ "12",
65
+ "13",
66
+ "14",
67
+ "15",
68
+ "16",
69
+ "17",
70
+ "18",
71
+ "19",
72
+ "20",
73
+ "21",
74
+ "22",
75
+ "23",
76
+ ],
77
+ inputs: 1,
78
+ outputs: 3,
79
+ icon: "font-awesome/fa-bar-chart",
80
+ color: "#FFCC66",
81
+ label: function () {
82
+ return this.name || "Fixed Schedule";
83
+ },
84
+ outputLabels: ["on", "off", "schedule"],
85
+ oneditprepare: function () {
86
+ $("#node-input-outputIfNoSchedule").typedInput({
87
+ types: [
88
+ {
89
+ value: "onoff",
90
+ options: [
91
+ { value: "true", label: "On" },
92
+ { value: "false", label: "Off" },
93
+ ],
94
+ },
95
+ ],
96
+ });
97
+ $("#node-input-contextStorage").typedInput({
98
+ types: [
99
+ {
100
+ value: "storages",
101
+ options: RED.settings.context.stores.map((s) => ({ value: s, label: s })),
102
+ },
103
+ ],
104
+ });
105
+ $("#node-input-outputValueForOn").typedInput({
106
+ default: "bool",
107
+ typeField: $("#node-input-outputValueForOntype"),
108
+ types: ["bool", "num", "str"],
109
+ });
110
+ $("#node-input-outputValueForOff").typedInput({
111
+ default: "bool",
112
+ typeField: $("#node-input-outputValueForOfftype"),
113
+ types: ["bool", "num", "str"],
114
+ });
115
+ const createElement = function (type, attrs = [], children = []) {
116
+ const el = document.createElement(type);
117
+ attrs.forEach((attr) => {
118
+ el.setAttribute(attr[0], attr[1]);
119
+ });
120
+ children.forEach((child) => {
121
+ el.append(child);
122
+ });
123
+ return el;
124
+ };
125
+
126
+ const createInputPart = function (name, i, text, inpStyle, value) {
127
+ const id = `node-input-${name}-${i}`;
128
+ const label = createElement(
129
+ "label",
130
+ [
131
+ ["for", id],
132
+ ["style", "margin-right: 10px;"],
133
+ ],
134
+ []
135
+ );
136
+ label.innerHTML = text;
137
+ const inp = createElement("input", [
138
+ ["type", "text"],
139
+ ["id", id],
140
+ ["style", `width: 80px; ${inpStyle};`],
141
+ ]);
142
+
143
+ inp.value = value;
144
+ return createElement("span", [["style", "text-align: right;"]], [label, inp]);
145
+ };
146
+
147
+ const addPeriod = function (periods) {
148
+ const prev = periods[periods.length - 1].start;
149
+ const next = prev === "23" ? "00" : "" + (parseInt(prev) + 1);
150
+ periods.push({ start: next, value: null });
151
+ drawPeriods(periods);
152
+ };
153
+
154
+ const removePeriod = function (periods, i) {
155
+ periods.splice(i, 1);
156
+ drawPeriods(periods);
157
+ RED.nodes.dirty(true);
158
+ };
159
+
160
+ const drawPeriods = function (periods) {
161
+ document.getElementById("node-input-period-container").replaceChildren();
162
+ for (let i = 0; i < periods.length; i++) {
163
+ let period = periods[i];
164
+
165
+ const timeEl = createInputPart("fromTime", i, "From time:", "margin-right: 20px;", period.start);
166
+ const valEl = createInputPart("value", i, "Value:", "margin-right: 20px;", period.value);
167
+
168
+ let li;
169
+ if (periods.length > 1) {
170
+ // Delete button
171
+ const delButton = document.createElement("button");
172
+ delButton.setAttribute("style", "width: 24px;");
173
+ delButton.innerText = "X";
174
+ delButton.addEventListener("click", () => {
175
+ removePeriod(periods, i);
176
+ setTypedInputOnValues(periods.length);
177
+ });
178
+ li = createElement("div", [["style", "text-align: left;"]], [timeEl, valEl, delButton]);
179
+ } else {
180
+ li = createElement("div", [["style", "text-align: left;"]], [timeEl, valEl]);
181
+ }
182
+ $("#node-input-period-container").append(li);
183
+
184
+ $("#node-input-fromTime-" + i).typedInput({
185
+ types: [
186
+ {
187
+ value: "fromTime",
188
+ options: hours.map((h) => ({ value: h, label: h + ":00" })),
189
+ },
190
+ ],
191
+ });
192
+ $("#node-input-fromTime-" + i).change(function () {
193
+ periods[i].start = this.value;
194
+ RED.nodes.dirty(true);
195
+ });
196
+ $("#node-input-value-" + i).change(function () {
197
+ periods[i].value = this.value;
198
+ RED.nodes.dirty(true);
199
+ });
200
+ }
201
+ };
202
+
203
+ const drawDays = function (days) {
204
+ const dayNames = Object.keys(days);
205
+ document.getElementById("node-input-days-container").replaceChildren();
206
+ for (let i = 0; i < dayNames.length; i++) {
207
+ let day = dayNames[i];
208
+
209
+ const id = `node-input-day-${i}`;
210
+ const label = createElement(
211
+ "label",
212
+ [
213
+ ["for", id],
214
+ ["style", " margin: 4px 10px 0px 2px;width: 30px; text-align: left;"],
215
+ ],
216
+ []
217
+ );
218
+ label.innerHTML = day;
219
+ const attrs = [
220
+ ["name", "node-input-day"],
221
+ ["type", "checkbox"],
222
+ ["id", id],
223
+ ["style", `width: 15px; margin: 2px 0px 5px 5px;`],
224
+ ];
225
+ if (days[day]) {
226
+ attrs.push(["checked", ""]);
227
+ }
228
+ const inp = createElement("input", attrs);
229
+ inp.value = dayNames[i];
230
+
231
+ const el = createElement("span", [["style", "text-align: right;"]], [inp, label]);
232
+
233
+ $("#node-input-days-container").append(el);
234
+
235
+ $("#node-input-day-" + i).change(function (e) {
236
+ days[day] = $(this).is(":checked");
237
+ RED.nodes.dirty(true);
238
+ });
239
+ }
240
+ };
241
+
242
+ const setTypedInputOnValues = function (length) {
243
+ for (let i = 0; i < length; i++) {
244
+ $("#node-input-value-" + i).typedInput({
245
+ types: [
246
+ {
247
+ value: "onoff",
248
+ options: [
249
+ { value: "true", label: "On" },
250
+ { value: "false", label: "Off" },
251
+ ],
252
+ },
253
+ ],
254
+ });
255
+ }
256
+ };
257
+
258
+ drawPeriods(this.periods);
259
+ $("#add-period-button").on("click", () => {
260
+ addPeriod(this.periods);
261
+ setTypedInputOnValues(this.periods.length);
262
+ });
263
+
264
+ // Set typed input for value on all periods
265
+ setTypedInputOnValues(this.periods.length);
266
+
267
+ if (!this.days) {
268
+ // To support nodes created before this was developed
269
+ this.days = { ...defaultDaysSfs };
270
+ }
271
+ drawDays(this.days);
272
+ },
273
+ });
274
+ </script>
275
+
276
+ <script type="text/html" data-template-name="ps-strategy-fixed-schedule">
277
+ <div class="form-row">
278
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
279
+ <input type="text" id="node-input-name" placeholder="Name" style="width: 240px">
280
+ </div>
281
+ <h3>Schedule</h3>
282
+ <div class="form-row node-input-period-container-row">
283
+ <div id="node-input-period-container"></div>
284
+ </div>
285
+
286
+ <div class="form-row">
287
+ <button type="button" id="add-period-button" class="red-ui-button">Add period</button>
288
+ </div>
289
+
290
+ <div class="form-row">
291
+ <label for="node-input-days-container"><i class="fa fa-calendar-o"></i> Days</label>
292
+ <span id="node-input-days-container"></span>
293
+ </div>
294
+
295
+ <div class="form-row">
296
+ <label for="node-input-validFrom"><i class="fa fa-calendar"></i> Valid from date</label>
297
+ <input type="text" id="node-input-validFrom" placeholder="YYYY-MM-DD" />
298
+ </div>
299
+
300
+ <div class="form-row">
301
+ <label for="node-input-validTo"><i class="fa fa-calendar"></i> Valid to date</label>
302
+ <input type="text" id="node-input-validTo" placeholder="YYYY-MM-DD" />
303
+ </div>
304
+ <h3>Output</h3>
305
+ <div class="form-row">
306
+ <div class="form-row">
307
+ <label for="node-input-outputValueForOn">Output value for on</label>
308
+ <input type="text" id="node-input-outputValueForOn" style="text-align: left; width: 120px">
309
+ <input type="hidden" id="node-input-outputValueForOntype">
310
+ </div>
311
+ <div class="form-row">
312
+ <label for="node-input-outputValueForOff">Output value for off</label>
313
+ <input type="text" id="node-input-outputValueForOff" style="text-align: left; width: 120px">
314
+ <input type="hidden" id="node-input-outputValueForOfftype">
315
+ </div>
316
+ <label for="node-input-sendCurrentValueWhenRescheduling" style="width:240px">
317
+ <input type="checkbox"
318
+ id="node-input-sendCurrentValueWhenRescheduling"
319
+ style="display:inline-block; width:22px; vertical-align:top;"
320
+ autocomplete="off"><span>Send when rescheduling</span>
321
+ </label>
322
+ </div>
323
+ <div class="form-row">
324
+ <label for="node-input-outputIfNoSchedule">If no schedule, send</label>
325
+ <input type="text" id="node-input-outputIfNoSchedule" style="width: 80px">
326
+ </label>
327
+ </div>
328
+ <h3>Context storage</h3>
329
+ <div class="form-row">
330
+ <label for="node-input-contextStorage"><i class="fa fa-archive"></i> Context storage</label>
331
+ <input type="text" id="node-input-contextStorage" style="width: 160px">
332
+ </div>
333
+ </script>
334
+
335
+ <script type="text/markdown" data-help-name="ps-strategy-fixed-schedule">
336
+ A node you can use to save money by turning off and on a switch based on power prices.
337
+
338
+ Please read more in the [node documentation](https://powersaver.no/nodes/ps-strategy-fixed-schedule)
339
+ </script>
@@ -0,0 +1,84 @@
1
+ const {
2
+ booleanConfig,
3
+ calcNullSavings,
4
+ fixOutputValues,
5
+ fixPeriods,
6
+ getSavings,
7
+ saveOriginalConfig,
8
+ } = require("./utils");
9
+ const { strategyOnInput } = require("./strategy-functions");
10
+ const { DateTime } = require("luxon");
11
+ const cloneDeep = require("lodash.clonedeep");
12
+
13
+ module.exports = function (RED) {
14
+ function StrategyFixedScheduleNode(config) {
15
+ RED.nodes.createNode(this, config);
16
+ const node = this;
17
+ node.status({});
18
+
19
+ const validConfig = {
20
+ periods: config.periods || [],
21
+ validFrom: config.validFrom,
22
+ validTo: config.validTo,
23
+ days: config.days || { Mon: true, Tue: true, Wed: true, Thu: true, Fri: true, Sat: true, Sun: true },
24
+ contextStorage: config.contextStorage || "default",
25
+ outputIfNoSchedule: booleanConfig(config.outputIfNoSchedule),
26
+ outputValueForOn: config.outputValueForOn || true,
27
+ outputValueForOff: config.outputValueForOff || false,
28
+ outputValueForOntype: config.outputValueForOntype || "bool",
29
+ outputValueForOfftype: config.outputValueForOfftype || "bool",
30
+ override: "auto",
31
+ sendCurrentValueWhenRescheduling: config.sendCurrentValueWhenRescheduling,
32
+ };
33
+
34
+ fixOutputValues(validConfig);
35
+ fixPeriods(validConfig);
36
+ saveOriginalConfig(node, validConfig);
37
+
38
+ node.on("close", function () {
39
+ clearTimeout(node.schedulingTimeout);
40
+ });
41
+
42
+ node.on("input", function (msg) {
43
+ strategyOnInput(node, msg, doPlanning, calcNullSavings);
44
+ });
45
+ }
46
+ RED.nodes.registerType("ps-strategy-fixed-schedule", StrategyFixedScheduleNode);
47
+ };
48
+
49
+ function doPlanning(node, priceData) {
50
+ const startTimes = priceData.map((pd) => pd.start);
51
+ const onOff = startTimes.map(() => node.outputIfNoSchedule);
52
+ const allHours = buildAllHours(node, node.periods);
53
+ const validFrom = DateTime.fromISO(node.validFrom || startTimes[0].substr(0, 10));
54
+ const validTo = DateTime.fromISO(node.validTo || startTimes[startTimes.length - 1].substr(0, 10));
55
+ startTimes.forEach((st, i) => {
56
+ const date = DateTime.fromISO(st.substr(0, 10));
57
+ const hour = DateTime.fromISO(st).hour;
58
+ const day = DateTime.fromISO(st).weekday;
59
+ const dayName = Object.keys(node.days)[day - 1];
60
+ if (date >= validFrom && date <= validTo && node.days[dayName]) {
61
+ onOff[i] = allHours[hour];
62
+ }
63
+ });
64
+
65
+ return onOff;
66
+ }
67
+
68
+ function buildAllHours(node, periods) {
69
+ const sortedPeriods = cloneDeep(periods);
70
+ sortedPeriods.sort((a, b) => a.start - b.start);
71
+ let res = [];
72
+ let hour = 0;
73
+ let current = sortedPeriods[sortedPeriods.length - 1];
74
+ sortedPeriods.push({ start: 24, value: null });
75
+ sortedPeriods.forEach((period) => {
76
+ const nextHour = parseInt(period.start);
77
+ while (hour < nextHour) {
78
+ res[hour] = current.value;
79
+ hour++;
80
+ }
81
+ current = period;
82
+ });
83
+ return res;
84
+ }
@@ -0,0 +1,35 @@
1
+ const { DateTime } = require("luxon");
2
+ const { getEffectiveConfig, getOutputForTime } = require("./utils");
3
+ const { handleStrategyInput } = require("./handle-input");
4
+ const { handleOutput, shallSendOutput, strategyShallSendSchedule } = require("./handle-output");
5
+
6
+ function strategyOnInput(node, msg, doPlanning, calcSavings) {
7
+ if (msg.payload?.name && msg.payload.name !== node.name) {
8
+ // If payload.name is set, and does not match this nodes name, discard message
9
+ return;
10
+ }
11
+ const config = getEffectiveConfig(node, msg);
12
+ const { plan, commands } = handleStrategyInput(node, msg, config, doPlanning, calcSavings);
13
+ if (plan) {
14
+ const planFromTime = msg.payload.time ? DateTime.fromISO(msg.payload.time) : DateTime.now();
15
+ const currentOutput = node.context().get("currentOutput");
16
+ const plannedOutputNow =
17
+ node.override === "auto"
18
+ ? getOutputForTime(plan.schedule, planFromTime, node.outputIfNoSchedule)
19
+ : node.override === "on";
20
+ const outputCommands = {
21
+ sendOutput: shallSendOutput(
22
+ msg,
23
+ commands,
24
+ currentOutput,
25
+ plannedOutputNow,
26
+ node.sendCurrentValueWhenRescheduling
27
+ ),
28
+ sendSchedule: strategyShallSendSchedule(msg, commands),
29
+ runSchedule: commands.replan !== false && config.override === "auto",
30
+ };
31
+ handleOutput(node, config, plan, outputCommands, planFromTime);
32
+ }
33
+ }
34
+
35
+ module.exports = { strategyOnInput };
@@ -61,6 +61,24 @@
61
61
  required: true,
62
62
  align: "left",
63
63
  },
64
+ outputValueForOn: {
65
+ value: true,
66
+ required: true,
67
+ validate: RED.validators.typedInput("outputValueForOntype", false),
68
+ },
69
+ outputValueForOff: {
70
+ value: false,
71
+ required: true,
72
+ validate: RED.validators.typedInput("outputValueForOfftype", false),
73
+ },
74
+ outputValueForOntype: {
75
+ value: "bool",
76
+ required: true,
77
+ },
78
+ outputValueForOfftype: {
79
+ value: "bool",
80
+ required: true,
81
+ },
64
82
  outputIfNoSchedule: { value: "true", required: true, align: "left" },
65
83
  outputOutsidePeriod: { value: "false", required: true, align: "left" },
66
84
  contextStorage: { value: "default", required: false, align: "left" },
@@ -132,58 +150,78 @@
132
150
  },
133
151
  ],
134
152
  });
153
+ $("#node-input-outputValueForOn").typedInput({
154
+ default: "bool",
155
+ typeField: $("#node-input-outputValueForOntype"),
156
+ types: ["bool", "num", "str"],
157
+ });
158
+ $("#node-input-outputValueForOff").typedInput({
159
+ default: "bool",
160
+ typeField: $("#node-input-outputValueForOfftype"),
161
+ types: ["bool", "num", "str"],
162
+ });
135
163
  },
136
164
  });
137
165
  </script>
138
166
 
139
167
  <script type="text/html" data-template-name="ps-strategy-lowest-price">
140
- <div class="form-row">
141
- <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
142
- <input type="text" id="node-input-name" placeholder="Name" style="width: 240px">
143
- </div>
144
- <div class="form-row">
145
- <label for="node-input-fromTime"><i class="fa fa-clock-o"></i> From time</label>
146
- <input type="text" id="node-input-fromTime" style="width: 80px">
168
+ <div class="form-row">
169
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
170
+ <input type="text" id="node-input-name" placeholder="Name" style="width: 240px">
147
171
  </div>
148
- <div class="form-row">
149
- <label for="node-input-toTime"><i class="fa fa-clock-o"></i> To time</label>
150
- <input type="text" id="node-input-toTime" style="width: 80px">
151
- </div>
152
- <div class="form-row">
153
- <label for="node-input-hoursOn"><i class="fa fa-arrows-h"></i> Hours on</label>
154
- <input type="text" id="node-input-hoursOn" style="width: 80px">
155
- </div>
156
- <div class="form-row">
157
- <label for="node-input-maxPrice"><i class="fa fa-minus"></i> Max price</label>
158
- <input type="text" id="node-input-maxPrice" placeholder="Max price" style="width: 80px">
159
- </div>
160
- <div class="form-row">
161
- <label for="node-input-doNotSplit">Consecutive on-period</label>
162
- <input type="checkbox" id="node-input-doNotSplit" style="display:inline-block; width:22px; vertical-align:top;">
163
- </label>
164
- </div>
165
172
  <div class="form-row">
166
- <label for="node-input-sendCurrentValueWhenRescheduling" style="width:240px">
167
- <input type="checkbox"
168
- id="node-input-sendCurrentValueWhenRescheduling"
169
- style="display:inline-block; width:22px; vertical-align:top;"
170
- autocomplete="off"><span>Send when rescheduling</span>
171
- </label>
173
+ <label for="node-input-fromTime"><i class="fa fa-clock-o"></i> From time</label>
174
+ <input type="text" id="node-input-fromTime" style="width: 80px">
175
+ </div>
176
+ <div class="form-row">
177
+ <label for="node-input-toTime"><i class="fa fa-clock-o"></i> To time</label>
178
+ <input type="text" id="node-input-toTime" style="width: 80px">
172
179
  </div>
173
180
  <div class="form-row">
174
- <label for="node-input-outputIfNoSchedule">If no schedule, send</label>
175
- <input type="text" id="node-input-outputIfNoSchedule" style="width: 80px">
176
- </label>
181
+ <label for="node-input-hoursOn"><i class="fa fa-arrows-h"></i> Hours on</label>
182
+ <input type="text" id="node-input-hoursOn" style="width: 80px">
177
183
  </div>
178
184
  <div class="form-row">
179
- <label for="node-input-outputIfNoSchedule">Outside period, send</label>
180
- <input type="text" id="node-input-outputOutsidePeriod" style="width: 80px">
181
- </label>
185
+ <label for="node-input-maxPrice"><i class="fa fa-minus"></i> Max price</label>
186
+ <input type="text" id="node-input-maxPrice" placeholder="Max price" style="width: 80px">
182
187
  </div>
183
188
  <div class="form-row">
184
- <label for="node-input-contextStorage"><i class="fa fa-archive"></i> Context storage</label>
185
- <input type="text" id="node-input-contextStorage" style="width: 160px">
189
+ <label for="node-input-doNotSplit">Consecutive on-period</label>
190
+ <input type="checkbox" id="node-input-doNotSplit" style="display:inline-block; width:22px; vertical-align:top;">
191
+ </label>
186
192
  </div>
193
+ <div class="form-row">
194
+ <label for="node-input-outputValueForOn">Output value for on</label>
195
+ <input type="text" id="node-input-outputValueForOn" style="text-align: left; width: 120px">
196
+ <input type="hidden" id="node-input-outputValueForOntype">
197
+ </div>
198
+ <div class="form-row">
199
+ <label for="node-input-outputValueForOff">Output value for off</label>
200
+ <input type="text" id="node-input-outputValueForOff" style="text-align: left; width: 120px">
201
+ <input type="hidden" id="node-input-outputValueForOfftype">
202
+ </div>
203
+ <div class="form-row">
204
+ <label for="node-input-sendCurrentValueWhenRescheduling" style="width:240px">
205
+ <input type="checkbox"
206
+ id="node-input-sendCurrentValueWhenRescheduling"
207
+ style="display:inline-block; width:22px; vertical-align:top;"
208
+ autocomplete="off"><span>Send when rescheduling</span>
209
+ </label>
210
+ </div>
211
+ <div class="form-row">
212
+ <label for="node-input-outputIfNoSchedule">If no schedule, send</label>
213
+ <input type="text" id="node-input-outputIfNoSchedule" style="width: 80px">
214
+ </label>
215
+ </div>
216
+ <div class="form-row">
217
+ <label for="node-input-outputIfNoSchedule">Outside period, send</label>
218
+ <input type="text" id="node-input-outputOutsidePeriod" style="width: 80px">
219
+ </label>
220
+ </div>
221
+ <div class="form-row">
222
+ <label for="node-input-contextStorage"><i class="fa fa-archive"></i> Context storage</label>
223
+ <input type="text" id="node-input-contextStorage" style="width: 160px">
224
+ </div>
187
225
  </script>
188
226
 
189
227
  <script type="text/markdown" data-help-name="ps-strategy-lowest-price">