@energiok/node-red-contrib-pricecontrol-thermal 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,15 @@
1
+ module.exports = {
2
+ "env": {
3
+ "browser": true,
4
+ "commonjs": true,
5
+ "es2021": true
6
+ },
7
+ "extends": "eslint:recommended",
8
+ "overrides": [
9
+ ],
10
+ "parserOptions": {
11
+ "ecmaVersion": "latest"
12
+ },
13
+ "rules": {
14
+ }
15
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ [The changelog has moved here](https://powersaver.no/changelog/#change-log)
@@ -0,0 +1,264 @@
1
+ # Comparison: Best Save vs Smart Thermal Strategy
2
+
3
+ ## Overview
4
+
5
+ Both nodes solve the same fundamental problem: **minimize electricity cost while maintaining system requirements**. However, they use different approaches.
6
+
7
+ ---
8
+
9
+ ## Best Save Strategy (Deleted/Old)
10
+
11
+ ### Core Algorithm
12
+ Uses a **greedy optimization** approach that:
13
+ 1. Calculates "saving per minute" for every possible off-sequence
14
+ 2. Sorts sequences by total savings (highest first)
15
+ 3. Iteratively picks the best sequences that satisfy constraints
16
+
17
+ ### Key Features
18
+
19
+ #### 1. Recovery Mechanism
20
+ ```javascript
21
+ // After being off, system must recover
22
+ recoveryPercentage: 100 // % of off-time that must be on afterward
23
+ recoveryMaxMinutes: 60 // Cap on recovery time
24
+ ```
25
+ **Example**: If system is off for 4 hours (240 min), it must be on for 2-4 hours after (depending on recoveryPercentage).
26
+
27
+ #### 2. Constraint Validation
28
+ ```javascript
29
+ function isOnOffSequencesOk(onOff, maxMinutesOff, minMinutesOff, recoveryPercentage, recoveryMaxMinutes)
30
+ ```
31
+ - **maxMinutesOff**: Maximum continuous off time (prevents damage)
32
+ - **minMinutesOff**: Minimum off duration (prevents short-cycling)
33
+ - **recoveryPercentage**: Ensures sufficient recovery after long off periods
34
+ - **recoveryMaxMinutes**: Caps recovery time (prevents excessive on-time)
35
+
36
+ #### 3. Minimum Savings Threshold
37
+ ```javascript
38
+ minSaving: 0.05 // Only turn off if saving > 5 øre/kWh
39
+ ```
40
+ Prevents turning off for trivial savings.
41
+
42
+ #### 4. Day-Before Context
43
+ ```javascript
44
+ lastValueDayBefore = undefined
45
+ lastCountDayBefore = 0
46
+ ```
47
+ Considers previous day's state to ensure constraints span across days.
48
+
49
+ ### Pros
50
+ - **Optimal** for the given constraints (maximizes savings)
51
+ - **Flexible** - works for any device (water heater, heat pump, etc.)
52
+ - **Recovery logic** ensures system doesn't stay off too long
53
+ - **Cross-day awareness** prevents constraint violations at midnight
54
+
55
+ ### Cons
56
+ - **Complex algorithm** (nested loops, sorting, filtering)
57
+ - **No temperature awareness** (doesn't consider thermal dynamics)
58
+ - **Binary on/off** (no intermediate states)
59
+ - **Assumes constant load** when on
60
+
61
+ ---
62
+
63
+ ## Smart Thermal Strategy (Current)
64
+
65
+ ### Core Algorithm
66
+ Uses a **physics-based** approach that:
67
+ 1. Calculates how long building can coast based on outdoor temperature
68
+ 2. Identifies price peaks (top 20%)
69
+ 3. Schedules coast during peaks, boost before peaks
70
+
71
+ ### Key Features
72
+
73
+ #### 1. Thermal Modeling
74
+ ```javascript
75
+ function calculateCoastTime(outdoorTemp, heatLossCoefficient = 0.05) {
76
+ const indoorTemp = 22;
77
+ const comfortMin = 20;
78
+ const tempDelta = indoorTemp - outdoorTemp;
79
+ const tempBuffer = indoorTemp - comfortMin;
80
+ const heatLossRate = tempDelta * heatLossCoefficient;
81
+ return (tempBuffer / heatLossRate) * 60; // minutes
82
+ }
83
+ ```
84
+ **Adapts to weather**: Colder outdoor = shorter coast time allowed.
85
+
86
+ #### 2. Peak Detection
87
+ ```javascript
88
+ function findPeaks(prices, topPercentage = 20)
89
+ ```
90
+ - Identifies top 20% of prices as "expensive"
91
+ - Coasts during these periods only
92
+
93
+ #### 3. Anti-Short-Cycling
94
+ ```javascript
95
+ minCoastMinutes: 60
96
+ minBoostMinutes: 60
97
+ function enforceMinimumDurations(modes, intervalMinutes, minCoastMinutes, minBoostMinutes)
98
+ ```
99
+ Prevents rapid mode switching (similar to minMinutesOff in best-save).
100
+
101
+ #### 4. Simple Output
102
+ ```javascript
103
+ output: "boost" or "coast" // Just mode strings
104
+ ```
105
+ User decides actual temperatures/settings.
106
+
107
+ ### Pros
108
+ - **Temperature-aware** - considers outdoor temp and thermal mass
109
+ - **Simple to understand** - coast during expensive periods
110
+ - **User-friendly config** - only 5 parameters
111
+ - **Weather adaptive** - automatically adjusts to conditions
112
+
113
+ ### Cons
114
+ - **Not optimal** - doesn't maximize savings, just avoids peaks
115
+ - **Assumes thermal model** - may not fit all buildings
116
+ - **No recovery logic** - doesn't ensure minimum on-time after long coast
117
+ - **No minimum savings** - will coast even for small price differences
118
+
119
+ ---
120
+
121
+ ## What Smart Thermal Could Learn from Best Save
122
+
123
+ ### 1. **Recovery Logic** ⭐ MOST VALUABLE
124
+ The best-save recovery mechanism ensures the system doesn't stay off too long and has adequate recovery time.
125
+
126
+ **How to add to Smart Thermal**:
127
+ ```javascript
128
+ // After coasting, ensure minimum boost time for recovery
129
+ const coastDuration = calculateCoastDuration(schedule);
130
+ const minBoostAfterCoast = Math.max(
131
+ coastDuration * (recoveryPercentage / 100),
132
+ minBoostMinutes
133
+ );
134
+ ```
135
+
136
+ **Example**: If system coasts for 4 hours during a price peak, require at least 2-4 hours of boost afterward to ensure building is properly heated.
137
+
138
+ ### 2. **Minimum Savings Threshold** ⭐ VALUABLE
139
+ Prevents turning off for trivial price differences.
140
+
141
+ **How to add to Smart Thermal**:
142
+ ```javascript
143
+ // Only coast if price difference justifies it
144
+ const avgPrice = calculateAverage(prices);
145
+ const peakPrice = item.price;
146
+ if (peakPrice - avgPrice < minSaving) {
147
+ mode = 'boost'; // Not worth coasting
148
+ }
149
+ ```
150
+
151
+ ### 3. **Cross-Day State Tracking**
152
+ Best-save considers yesterday's final state to ensure constraints don't break at midnight.
153
+
154
+ **How to add to Smart Thermal**:
155
+ ```javascript
156
+ // Store last mode and duration in context
157
+ const lastMode = node.context().get('lastMode') || 'boost';
158
+ const lastModeDuration = node.context().get('lastModeDuration') || 0;
159
+
160
+ // Use when enforcing minimum durations
161
+ const effectiveModes = [
162
+ ...fillArray(lastMode, lastModeDuration),
163
+ ...todaysModes
164
+ ];
165
+ ```
166
+
167
+ ### 4. **Greedy Optimization** (Optional)
168
+ Best-save's algorithm finds the truly optimal solution, not just "avoid peaks".
169
+
170
+ **Trade-off**: Much more complex, probably not worth it for the thermal use case since weather/temperature dynamics dominate over pure price optimization.
171
+
172
+ ---
173
+
174
+ ## Recommended Improvements to Smart Thermal
175
+
176
+ ### Priority 1: Add Recovery Logic ⭐⭐⭐
177
+ ```javascript
178
+ // Add to config
179
+ recoveryPercentage: 100 // Default: equal recovery time
180
+ recoveryMaxMinutes: 120 // Default: cap at 2 hours
181
+
182
+ // In createSchedule function
183
+ function enforceRecovery(schedule) {
184
+ let coastMinutes = 0;
185
+ for (let i = 0; i < schedule.length; i++) {
186
+ if (schedule[i].mode === 'coast') {
187
+ coastMinutes++;
188
+ } else if (coastMinutes > 0) {
189
+ // Just finished a coast period, ensure recovery
190
+ const minRecovery = Math.min(
191
+ coastMinutes * (recoveryPercentage / 100),
192
+ recoveryMaxMinutes / intervalMinutes
193
+ );
194
+
195
+ // Force next minRecovery intervals to be boost
196
+ for (let j = 0; j < minRecovery && i + j < schedule.length; j++) {
197
+ schedule[i + j].mode = 'boost';
198
+ }
199
+ coastMinutes = 0;
200
+ }
201
+ }
202
+ return schedule;
203
+ }
204
+ ```
205
+
206
+ ### Priority 2: Add Minimum Savings Threshold ⭐⭐
207
+ ```javascript
208
+ // Add to config
209
+ minSaving: 5 // Only coast if saving > 5 øre/kWh
210
+
211
+ // In createSchedule function
212
+ const avgPrice = prices.reduce((sum, p) => sum + p.value, 0) / prices.length;
213
+
214
+ schedule.forEach(item => {
215
+ if (item.mode === 'coast' && item.price - avgPrice < minSaving) {
216
+ item.mode = 'boost'; // Not worth the savings
217
+ }
218
+ });
219
+ ```
220
+
221
+ ### Priority 3: Cross-Day Context ⭐
222
+ ```javascript
223
+ // Store state at end of each schedule creation
224
+ node.context().set('lastScheduleEnd', {
225
+ mode: schedule[schedule.length - 1].mode,
226
+ duration: calculateDurationInSameMode(schedule)
227
+ }, contextStore);
228
+
229
+ // Use when enforcing minimum durations
230
+ const lastState = node.context().get('lastScheduleEnd', contextStore);
231
+ if (lastState) {
232
+ const combinedModes = [
233
+ ...fillArray(lastState.mode, lastState.duration),
234
+ ...currentModes
235
+ ];
236
+ enforceMinimumDurations(combinedModes, ...);
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Conclusion
243
+
244
+ **Best Save** is an optimal general-purpose scheduler that maximizes savings for any binary on/off device.
245
+
246
+ **Smart Thermal** is a simpler, temperature-aware scheduler specifically designed for heating systems.
247
+
248
+ **Best additions from Best Save to Smart Thermal**:
249
+ 1. ✅ **Recovery logic** - Ensure adequate heating after coast periods
250
+ 2. ✅ **Minimum savings threshold** - Don't coast for trivial savings
251
+ 3. ✅ **Cross-day context** - Better constraint enforcement at day boundaries
252
+
253
+ The greedy optimization algorithm from Best Save is **not recommended** because thermal dynamics (heat loss, outdoor temp) are more important than pure price optimization for heating systems.
254
+
255
+ ---
256
+
257
+ ## Implementation Status
258
+
259
+ - [ ] Recovery logic
260
+ - [ ] Minimum savings threshold
261
+ - [ ] Cross-day context
262
+ - [x] Basic thermal modeling
263
+ - [x] Anti-short-cycling
264
+ - [x] Simple user interface
@@ -0,0 +1,288 @@
1
+ # Dashboard Template for 3-Mode Smart Thermal
2
+
3
+ This template visualizes the three-state thermal optimizer: **normal**, **boost**, and **coast**.
4
+
5
+ ## Colors
6
+ - **Blue** - Normal operation (default state)
7
+ - **Green** - Boost (pre-heat before peaks)
8
+ - **Red** - Coast (turn off during expensive periods)
9
+
10
+ ## Template Code for ui-template node
11
+
12
+ ```html
13
+ <template>
14
+ <div class="thermal-chart">
15
+ <div class="chart-header">
16
+ <h3>Price & Mode Schedule</h3>
17
+ <div class="legend">
18
+ <span class="legend-item normal"><span class="legend-color"></span> Normal</span>
19
+ <span class="legend-item boost"><span class="legend-color"></span> Boost (Pre-heat)</span>
20
+ <span class="legend-item coast"><span class="legend-color"></span> Coast (Turn Off)</span>
21
+ </div>
22
+ </div>
23
+
24
+ <div v-if="msg.payload && msg.payload.schedule" class="schedule-container">
25
+ <div class="schedule-bar">
26
+ <div
27
+ v-for="(item, index) in msg.payload.schedule"
28
+ :key="index"
29
+ class="schedule-item"
30
+ :class="item.mode"
31
+ :style="{width: (100 / msg.payload.schedule.length) + '%'}"
32
+ :title="formatTime(item.time) + ': ' + item.price + ' øre/kWh (' + item.mode + ')'"
33
+ >
34
+ <div class="time-label" v-if="index % 4 === 0">{{ formatTime(item.time) }}</div>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="price-chart">
39
+ <svg viewBox="0 0 1000 200" preserveAspectRatio="none">
40
+ <!-- Price line -->
41
+ <polyline
42
+ :points="getPriceLinePoints()"
43
+ fill="none"
44
+ stroke="#2196F3"
45
+ stroke-width="2"
46
+ />
47
+ <!-- Price points with mode colors -->
48
+ <circle
49
+ v-for="(item, index) in msg.payload.schedule"
50
+ :key="'point-' + index"
51
+ :cx="(index / (msg.payload.schedule.length - 1)) * 1000"
52
+ :cy="200 - ((item.price - minPrice) / priceRange) * 180"
53
+ r="3"
54
+ :fill="getModeColor(item.mode)"
55
+ />
56
+ </svg>
57
+
58
+ <div class="y-axis">
59
+ <span class="y-label">{{ Math.round(maxPrice) }} øre</span>
60
+ <span class="y-label">{{ Math.round((maxPrice + minPrice) / 2) }} øre</span>
61
+ <span class="y-label">{{ Math.round(minPrice) }} øre</span>
62
+ </div>
63
+ </div>
64
+
65
+ <div class="info-bar">
66
+ <span>Outdoor: {{ msg.payload.outdoorTemp }}°C</span>
67
+ <span>Current: {{ msg.payload.currentMode }}</span>
68
+ </div>
69
+ </div>
70
+
71
+ <div v-else class="no-data">
72
+ Waiting for data...
73
+ </div>
74
+ </div>
75
+ </template>
76
+
77
+ <script>
78
+ export default {
79
+ data() {
80
+ return {
81
+ msg: { payload: null }
82
+ }
83
+ },
84
+ computed: {
85
+ minPrice() {
86
+ if (!this.msg.payload?.schedule) return 0;
87
+ return Math.min(...this.msg.payload.schedule.map(s => s.price || 0));
88
+ },
89
+ maxPrice() {
90
+ if (!this.msg.payload?.schedule) return 100;
91
+ return Math.max(...this.msg.payload.schedule.map(s => s.price || 0));
92
+ },
93
+ priceRange() {
94
+ return this.maxPrice - this.minPrice || 1;
95
+ }
96
+ },
97
+ methods: {
98
+ formatTime(timestamp) {
99
+ const date = new Date(timestamp);
100
+ return date.toLocaleTimeString('no-NO', { hour: '2-digit', minute: '2-digit' });
101
+ },
102
+ getPriceLinePoints() {
103
+ if (!this.msg.payload?.schedule) return '';
104
+
105
+ return this.msg.payload.schedule.map((item, index) => {
106
+ const x = (index / (this.msg.payload.schedule.length - 1)) * 1000;
107
+ const y = 200 - ((item.price - this.minPrice) / this.priceRange) * 180;
108
+ return `${x},${y}`;
109
+ }).join(' ');
110
+ },
111
+ getModeColor(mode) {
112
+ switch(mode) {
113
+ case 'boost': return '#4CAF50'; // Green
114
+ case 'coast': return '#FF5722'; // Red
115
+ case 'normal':
116
+ default: return '#2196F3'; // Blue
117
+ }
118
+ }
119
+ },
120
+ watch: {
121
+ msg: {
122
+ handler(newMsg) {
123
+ console.log('Received data:', newMsg);
124
+ },
125
+ deep: true
126
+ }
127
+ }
128
+ }
129
+ </script>
130
+
131
+ <style scoped>
132
+ .thermal-chart {
133
+ padding: 16px;
134
+ background: #fafafa;
135
+ border-radius: 8px;
136
+ font-family: Arial, sans-serif;
137
+ }
138
+
139
+ .chart-header {
140
+ display: flex;
141
+ justify-content: space-between;
142
+ align-items: center;
143
+ margin-bottom: 20px;
144
+ flex-wrap: wrap;
145
+ }
146
+
147
+ .chart-header h3 {
148
+ margin: 0;
149
+ color: #333;
150
+ }
151
+
152
+ .legend {
153
+ display: flex;
154
+ gap: 15px;
155
+ flex-wrap: wrap;
156
+ }
157
+
158
+ .legend-item {
159
+ display: flex;
160
+ align-items: center;
161
+ gap: 6px;
162
+ font-size: 13px;
163
+ }
164
+
165
+ .legend-color {
166
+ width: 18px;
167
+ height: 18px;
168
+ border-radius: 3px;
169
+ }
170
+
171
+ .legend-item.normal .legend-color {
172
+ background: #2196F3;
173
+ }
174
+
175
+ .legend-item.boost .legend-color {
176
+ background: #4CAF50;
177
+ }
178
+
179
+ .legend-item.coast .legend-color {
180
+ background: #FF5722;
181
+ }
182
+
183
+ .schedule-container {
184
+ background: white;
185
+ border-radius: 8px;
186
+ padding: 16px;
187
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
188
+ }
189
+
190
+ .schedule-bar {
191
+ display: flex;
192
+ height: 60px;
193
+ border-radius: 4px;
194
+ overflow: hidden;
195
+ margin-bottom: 20px;
196
+ border: 1px solid #ddd;
197
+ }
198
+
199
+ .schedule-item {
200
+ position: relative;
201
+ cursor: pointer;
202
+ transition: opacity 0.2s;
203
+ }
204
+
205
+ .schedule-item:hover {
206
+ opacity: 0.8;
207
+ }
208
+
209
+ .schedule-item.normal {
210
+ background: linear-gradient(180deg, #42A5F5 0%, #2196F3 100%);
211
+ }
212
+
213
+ .schedule-item.boost {
214
+ background: linear-gradient(180deg, #66BB6A 0%, #4CAF50 100%);
215
+ }
216
+
217
+ .schedule-item.coast {
218
+ background: linear-gradient(180deg, #FF7043 0%, #FF5722 100%);
219
+ }
220
+
221
+ .time-label {
222
+ position: absolute;
223
+ bottom: -20px;
224
+ left: 0;
225
+ font-size: 10px;
226
+ color: #666;
227
+ white-space: nowrap;
228
+ }
229
+
230
+ .price-chart {
231
+ position: relative;
232
+ height: 200px;
233
+ margin: 20px 0;
234
+ padding-left: 60px;
235
+ }
236
+
237
+ .price-chart svg {
238
+ width: 100%;
239
+ height: 100%;
240
+ background: #f5f5f5;
241
+ border-radius: 4px;
242
+ }
243
+
244
+ .y-axis {
245
+ position: absolute;
246
+ left: 0;
247
+ top: 0;
248
+ height: 100%;
249
+ display: flex;
250
+ flex-direction: column;
251
+ justify-content: space-between;
252
+ padding: 10px 0;
253
+ }
254
+
255
+ .y-label {
256
+ font-size: 12px;
257
+ color: #666;
258
+ }
259
+
260
+ .info-bar {
261
+ display: flex;
262
+ justify-content: space-between;
263
+ padding-top: 16px;
264
+ border-top: 1px solid #e0e0e0;
265
+ font-size: 14px;
266
+ color: #666;
267
+ }
268
+
269
+ .no-data {
270
+ text-align: center;
271
+ padding: 40px;
272
+ color: #999;
273
+ font-style: italic;
274
+ }
275
+ </style>
276
+ ```
277
+
278
+ ## Summary
279
+
280
+ **Three modes now supported:**
281
+ 1. **Normal** (Blue) - Default operation, most of the time
282
+ 2. **Boost** (Green) - Pre-heat when prices are significantly cheap AND there's an upcoming peak
283
+ 3. **Coast** (Red) - Turn off when prices are significantly expensive
284
+
285
+ **Thresholds that control behavior:**
286
+ - `minCoastSaving`: Only coast if price is 10+ øre/kWh above average
287
+ - `minBoostSaving`: Only boost if price is 10+ øre/kWh below average
288
+ - This prevents mode switching for trivial price differences!
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ MIT
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # node-red-contrib-power-saver
2
+
3
+ A Node-RED node to save money when power prices are changing by the hour.
4
+
5
+ ![Logo](docs/.vuepress/public/logo.png)
6
+
7
+ ## Please read more in the [documentation](https://powersaver.no/).
8
+
9
+
10
+ [![Donation](./donation.png)](https://powersaver.no/contribute/#donate)
@@ -0,0 +1,92 @@
1
+ # Simplified Configuration
2
+
3
+ The smart thermal node can run with three primary parameters. Everything else has sensible defaults.
4
+
5
+ ## Configuration Parameters
6
+
7
+ ### 1. Heat Loss Coefficient (0.03 - 0.08)
8
+ **Default:** 0.05
9
+
10
+ Controls how quickly your building loses heat. This is the only parameter you need to tune based on your building.
11
+
12
+ - **0.03** = Very well insulated modern house (slow heat loss)
13
+ - **0.05** = Average insulation (default)
14
+ - **0.08** = Older house, poor insulation (fast heat loss)
15
+
16
+ **How to tune:** Run it for a few days. If temperature drops too much during coast periods, increase the coefficient. If it does not coast enough, decrease it.
17
+
18
+ ### 2. Minimum Mode Duration (minutes)
19
+ **Default:** 60 minutes
20
+
21
+ Minimum time the system stays in any mode (coast, boost, or normal) before switching. Prevents short-cycling that damages equipment.
22
+
23
+ - **30-45 min** = More responsive (switches modes more often)
24
+ - **60 min** = Balanced (default)
25
+ - **90+ min** = More stable (prioritizes comfort over optimization)
26
+
27
+ ### 3. Minimum Savings Percent
28
+ **Default:** 5
29
+
30
+ Minimum price variation required before coast/boost can activate.
31
+
32
+ - **2-3%** = Aggressive (more mode changes)
33
+ - **5%** = Balanced (default)
34
+ - **8-12%** = Conservative (fewer mode changes)
35
+
36
+ ## What Was Removed
37
+
38
+ The following legacy parameters are no longer supported:
39
+
40
+ - `peakThreshold`
41
+ - `troughThreshold`
42
+ - `minCoastSavingPercent`
43
+ - `minBoostSavingPercent`
44
+ - `minCoastSaving`
45
+ - `minBoostSaving`
46
+ - `minCoastMinutes`
47
+ - `minBoostMinutes`
48
+ - `minNormalMinutes`
49
+
50
+ ## Example Configuration
51
+
52
+ ```javascript
53
+ {
54
+ heatLossCoefficient: 0.05, // Average insulation
55
+ minModeDuration: 60, // 1 hour minimum per mode
56
+ minSavingsPercent: 5 // Balanced price sensitivity
57
+ }
58
+ ```
59
+
60
+ ## How It Works
61
+
62
+ 1. Prices are clustered into cheap, normal, and expensive groups.
63
+ 2. `minSavingsPercent` gates clustering when variation is too small.
64
+ 3. Heat loss coefficient limits coast duration based on outdoor temperature.
65
+ 4. Minimum mode duration prevents excessive mode switching.
66
+
67
+ ## Migration from Old Config
68
+
69
+ **Old (many parameters):**
70
+ ```javascript
71
+ {
72
+ heatLossCoefficient: 0.05,
73
+ peakThreshold: 20,
74
+ troughThreshold: 20,
75
+ minCoastSavingPercent: 5,
76
+ minBoostSavingPercent: 5,
77
+ minCoastSaving: undefined,
78
+ minBoostSaving: undefined,
79
+ minCoastMinutes: 60,
80
+ minBoostMinutes: 60,
81
+ minNormalMinutes: 60
82
+ }
83
+ ```
84
+
85
+ **New (3 parameters):**
86
+ ```javascript
87
+ {
88
+ heatLossCoefficient: 0.05,
89
+ minModeDuration: 60,
90
+ minSavingsPercent: 5
91
+ }
92
+ ```