Skip to content

Commit 1520442

Browse files
authored
Add setting for minimum on time (#20)
* Add setting for minimum on time
1 parent 4a81504 commit 1520442

File tree

9 files changed

+109
-26
lines changed

9 files changed

+109
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ The debug output will contain the following data
132132
nextStart: number,
133133
nextEnd: number,
134134
onState: boolean,
135-
noOnStateToday: boolean,
135+
operationToday: 'normal' | 'noMidnightWrap' | 'minimumOnTimeNotMet',
136136
}
137137
```
138138
All the above numbers will be number of minutes since midnight on the day the event happens, except nextStart and nextEnd, which will be the number of minutes until the next event.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"author": {
44
"name": "Thomas Bowman Mørch"
55
},
6-
"version": "0.6.0",
6+
"version": "0.7.0",
77
"engines": {
88
"node": ">=14.0.0"
99
},

sonar-project.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ sonar.organization=tbowmogithub
1111
# Encoding of the source code. Default is default system encoding
1212
#sonar.sourceEncoding=UTF-8
1313
sonar.javascript.lcov.reportPaths=./.test_output/coverage/lcov.info
14+
sonar.sources=src/
15+
sonar.tests=src/
16+
sonar.exclusions=**/*.spec.ts
17+
sonar.test.inclusions=**/*.spec.ts

src/lib/small-timer-runner.spec.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ describe('lib/small-timer-runner', () => {
3535
onMsgType: 'str',
3636
wrapMidnight: false,
3737
debugEnable: false,
38+
minimumOnTime: 0,
3839
id: '',
3940
type: '',
4041
name: '',
@@ -46,7 +47,7 @@ describe('lib/small-timer-runner', () => {
4647
getTimeToNextStartEvent: sinon.stub(),
4748
getTimeToNextEndEvent: sinon.stub(),
4849
getOnState: sinon.stub().returns(false),
49-
noOnStateToday: sinon.stub().returns(false),
50+
operationToday: sinon.stub().returns('normal'),
5051
debug: sinon.stub().returns({debug: 'this is debug'}),
5152
}
5253

@@ -74,15 +75,33 @@ describe('lib/small-timer-runner', () => {
7475
const stubs = setupTest()
7576
stubs.stubbedTimeCalc.getTimeToNextStartEvent.returns(10)
7677
stubs.stubbedTimeCalc.getTimeToNextEndEvent.returns(20)
77-
stubs.stubbedTimeCalc.noOnStateToday.returns(true)
78+
stubs.stubbedTimeCalc.operationToday.returns('noMidnightWrap')
7879

79-
new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
80+
const runner = new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
8081

82+
runner.onMessage({payload: 'sync', _msgid: ''})
8183
sinon.assert.calledWith(stubs.node.status, {
8284
fill: 'yellow',
8385
shape: 'dot',
8486
text: 'No action today - off time is before on time',
8587
})
88+
89+
})
90+
91+
it('should handle no action today, due to minimum on time not met', () => {
92+
const stubs = setupTest()
93+
stubs.stubbedTimeCalc.getTimeToNextStartEvent.returns(10)
94+
stubs.stubbedTimeCalc.getTimeToNextEndEvent.returns(20)
95+
stubs.stubbedTimeCalc.operationToday.returns('minimumOnTimeNotMet')
96+
97+
const runner = new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
98+
99+
runner.onMessage({payload: 'sync', _msgid: ''})
100+
sinon.assert.calledWith(stubs.node.status, {
101+
fill: 'yellow',
102+
shape: 'dot',
103+
text: 'No action today - minimum on time not met',
104+
})
86105
})
87106

88107
it('should handle temporary on and use timeout to calculate next change', () => {
@@ -138,7 +157,9 @@ describe('lib/small-timer-runner', () => {
138157
stubs.stubbedTimeCalc.getTimeToNextEndEvent.returns(120.6)
139158
stubs.stubbedTimeCalc.getOnState.returns(false)
140159

141-
new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
160+
const runner = new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
161+
162+
runner.onMessage({payload: 'sync', _msgid: ''})
142163
sinon.clock.tick(60000)
143164

144165
sinon.assert.calledWithExactly(stubs.status, { fill: 'red', shape: 'dot', text: 'OFF for 00mins 00secs' })
@@ -184,8 +205,9 @@ describe('lib/small-timer-runner', () => {
184205
stubs.stubbedTimeCalc.getTimeToNextEndEvent.returns(120.6)
185206
stubs.stubbedTimeCalc.getOnState.returns(false)
186207

187-
new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
208+
const runner = new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
188209

210+
runner.onMessage({payload: 'sync', _msgid: ''})
189211
sinon.clock.tick(2000)
190212
sinon.assert.calledWith(stubs.send, [
191213
{
@@ -200,8 +222,8 @@ describe('lib/small-timer-runner', () => {
200222
},
201223
{ debug: 'this is debug', override: 'auto', topic: 'debug' },
202224
])
203-
204225
})
226+
205227
it('should stop timer, and not advance anything after cleanup has been called', async () => {
206228
const stubs = setupTest({
207229
topic: 'test-topic',
@@ -297,8 +319,6 @@ describe('lib/small-timer-runner', () => {
297319
payload: '0',
298320
topic: 'test-topic',
299321
})
300-
301-
302322
})
303323

304324
it('should output node status when sync message is received, without changing properties', () => {

src/lib/small-timer-runner.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*eslint complexity: ["error", 12]*/
1+
/*eslint complexity: ["error", 13]*/
22
import {
33
Node,
44
NodeMessage,
@@ -41,7 +41,6 @@ export class SmallTimerRunner {
4141
private repeat: boolean
4242
private onTimeout: number
4343
private offTimeout: number
44-
// private currentTimeout = 0
4544

4645
private timeCalc: TimeCalc
4746
private debugMode = false
@@ -61,6 +60,7 @@ export class SmallTimerRunner {
6160
Number(configuration.endTime),
6261
Number(configuration.startOffset),
6362
Number(configuration.endOffset),
63+
Number(configuration.minimumOnTime),
6464
)
6565

6666
this.topic = configuration.topic
@@ -205,7 +205,7 @@ export class SmallTimerRunner {
205205
if (minutes >= 2 || hour) {
206206
str.push(`${pad(minutes)}mins`)
207207
}
208-
if (minutes<2 && !hour) {
208+
if (minutes < 2 && !hour) {
209209
str.push(`${pad(Math.floor(minutes))}mins`)
210210
str.push(`${pad(seconds)}secs`)
211211
}
@@ -219,14 +219,18 @@ export class SmallTimerRunner {
219219
let fill: NodeStatusFill = 'yellow'
220220
const text: string[] = []
221221

222-
const activeToday = !this.timeCalc.noOnStateToday() && (this.isDayOk() || this.currentState)
222+
const activeToday = this.timeCalc.operationToday() === 'normal' && (this.isDayOk() || this.currentState)
223223

224224
if (!activeToday) {
225225
text.push('No action today')
226226
}
227227

228-
if (this.timeCalc.noOnStateToday()) {
228+
switch (this.timeCalc.operationToday()) {
229+
case 'noMidnightWrap':
229230
text.push('off time is before on time')
231+
break
232+
case 'minimumOnTimeNotMet':
233+
text.push('minimum on time not met')
230234
}
231235

232236
if (activeToday || this.override !== 'auto') {

src/lib/time-calculation.spec.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ describe('lib/time-calculation', () => {
4242
minutes + 120, // 12:00
4343
0,
4444
0,
45+
0,
4546
)
4647

4748
sinon.assert.calledWith(stubs.getTimes, currentTime, 10, 10)
@@ -67,15 +68,16 @@ describe('lib/time-calculation', () => {
6768
5006,
6869
0,
6970
0,
71+
0,
7072
)
7173

7274
expect(timeCalc.getOnState()).to.equal(true)
7375
expect(timeCalc.getTimeToNextEndEvent()).to.equal(180)
7476
expect(timeCalc.getTimeToNextStartEvent()).to.equal(22 * 60)
75-
expect(timeCalc.noOnStateToday()).to.equal(false)
77+
expect(timeCalc.operationToday()).to.equal('normal')
7678
})
7779

78-
it('should not signal on, if off time is before on time', () => {
80+
it('should indicate that on time wraps midnight, if off time is before on time', () => {
7981
setupTest()
8082
const currentTime = new Date('2023-01-01 01:00')
8183
sinon.clock.setSystemTime(currentTime)
@@ -87,12 +89,34 @@ describe('lib/time-calculation', () => {
8789
5006,
8890
0,
8991
0,
92+
0,
9093
)
9194

9295
expect(timeCalc.getOnState()).to.equal(false)
9396
expect(timeCalc.getTimeToNextEndEvent()).to.equal(180)
9497
expect(timeCalc.getTimeToNextStartEvent()).to.equal(22 * 60)
95-
expect(timeCalc.noOnStateToday()).to.equal(true)
98+
expect(timeCalc.operationToday()).to.equal('noMidnightWrap')
99+
})
100+
101+
it('should indicate that on time is lower than minimum on time', () => {
102+
setupTest()
103+
const currentTime = new Date('2023-01-01 01:00')
104+
sinon.clock.setSystemTime(currentTime)
105+
const timeCalc = new TimeCalc(
106+
10,
107+
10,
108+
false,
109+
45,
110+
75,
111+
0,
112+
0,
113+
60,
114+
)
115+
116+
expect(timeCalc.getOnState()).to.equal(false)
117+
expect(timeCalc.getTimeToNextEndEvent()).to.equal(15)
118+
expect(timeCalc.getTimeToNextStartEvent()).to.equal(1425)
119+
expect(timeCalc.operationToday()).to.equal('minimumOnTimeNotMet')
96120
})
97121

98122
const testData: {
@@ -127,6 +151,7 @@ describe('lib/time-calculation', () => {
127151
data.endTime,
128152
0,
129153
0,
154+
0,
130155
)
131156

132157
expect(timeCalc.getTimeToNextStartEvent()).to.equal(data.expectedStart, 'startEvent')
@@ -140,7 +165,7 @@ describe('lib/time-calculation', () => {
140165
stubs.getMoonTimes.returns({
141166
rise: false,
142167
set: false,
143-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
168+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
144169
} as any)
145170

146171
const timeCalc = new TimeCalc(
@@ -151,6 +176,7 @@ describe('lib/time-calculation', () => {
151176
5008,
152177
0,
153178
0,
179+
0,
154180
)
155181

156182
expect(timeCalc.getTimeToNextStartEvent()).to.equal(715, 'startEvent')
@@ -169,6 +195,7 @@ describe('lib/time-calculation', () => {
169195
5001,
170196
0,
171197
0,
198+
0,
172199
)
173200

174201
expect(timeCalc.setStartEndTime.bind(timeCalc, 6001, 6002))
@@ -186,6 +213,7 @@ describe('lib/time-calculation', () => {
186213
5002,
187214
0,
188215
0,
216+
0,
189217
)
190218

191219
expect(timeCalc.debug()).to.deep.equal({
@@ -205,7 +233,7 @@ describe('lib/time-calculation', () => {
205233
},
206234
nextEnd: 1320,
207235
nextStart: 480,
208-
noOnStateToday: false,
236+
operationToday: 'normal',
209237

210238
actualEnd: 660,
211239
actualStart: 1260,

src/lib/time-calculation.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class TimeCalc {
6969
private endTime: number,
7070
private startOffset: number,
7171
private endOffset: number,
72+
private minimumOnTime: number,
7273
) {
7374
this.eventCalculation()
7475
}
@@ -106,7 +107,7 @@ export class TimeCalc {
106107
nextStart: this.getTimeToNextStartEvent(),
107108
nextEnd: this.getTimeToNextEndEvent(),
108109
onState: this.getOnState(),
109-
noOnStateToday: this.noOnStateToday(),
110+
operationToday: this.operationToday(),
110111
}
111112
}
112113

@@ -159,6 +160,16 @@ export class TimeCalc {
159160
return nextEvent >= 0 ? nextEvent : (nextEvent + wholeDay)
160161
}
161162

163+
private onTime(): number {
164+
const onTime = this.actualEnd - this.actualStart
165+
166+
if (this.wrapMidnight && onTime < 0) {
167+
return onTime + wholeDay
168+
}
169+
170+
return onTime
171+
}
172+
162173
/**
163174
* Returns the actual on state, according to the current time
164175
*
@@ -169,20 +180,30 @@ export class TimeCalc {
169180
const currentTime = this.getTime(date)
170181

171182
this.eventCalculation(false, date)
172-
183+
184+
if (this.onTime() < this.minimumOnTime) {
185+
return false
186+
}
187+
173188
if (this.actualEnd < this.actualStart) {
174189
return this.wrapMidnight && ((currentTime < this.actualEnd) || (currentTime > this.actualStart))
175190
}
176191
return (currentTime > this.actualStart) && (currentTime < this.actualEnd)
177192
}
178193

179194
/**
180-
* Check if we will have an on event today,
181-
* returns false if wrapMidnight is false, and actualEnd is before actualStart
195+
* Check how the operational status is today,
196+
* returns 'normal' in case of normal operation or 'noMinomumOnTime' / 'noMidnightWrap' if we do not turn on today
182197
* @returns
183198
*/
184-
public noOnStateToday(): boolean {
185-
return !this.wrapMidnight && (this.actualEnd < this.actualStart)
199+
public operationToday(): 'normal' | 'minimumOnTimeNotMet' | 'noMidnightWrap' {
200+
const onTime = this.onTime()
201+
202+
if (onTime >= 0 && onTime < this.minimumOnTime) {
203+
return 'minimumOnTimeNotMet'
204+
}
205+
206+
return !this.wrapMidnight && (this.actualEnd < this.actualStart) ? 'noMidnightWrap' : 'normal'
186207
}
187208

188209
private eventCalculation(forceUpdate = false, now = new Date()) {

src/nodes/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ export interface ISmallTimerProperties extends NodeDef {
4343
offTimeout: number,
4444
wrapMidnight: boolean,
4545
debugEnable: boolean,
46+
minimumOnTime: number,
4647
}

src/nodes/small-timer.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ <h3>References</h3>
135135
<input type="text" id="node-input-offTimeout" placeholder="1440">
136136
</div>
137137
</div>
138+
<div class="form-row">
139+
<label for="node-input-minimumOnTime">Minimum on time</label>
140+
<input style="width:80px" type="text" id="node-input-minimumOnTime" placeholder="0">
141+
</div>
138142
<div class="form-row">
139143
<label for="node-input-name"><i class="fa fa-tasks"></i> Topic</label>
140144
<input type="text" id="node-input-topic" placeholder="Topic">
@@ -336,6 +340,7 @@ <h3>References</h3>
336340
onMsg: { value: '1' },
337341
onMsgType: { value: 'str' },
338342
onTimeout: { value: 1440, required: true },
343+
minimumOnTime: { value: 0, required: true},
339344

340345
offMsg: { value: '0' },
341346
offMsgType: { value: 'str' },

0 commit comments

Comments
 (0)