Skip to content

Commit af3938f

Browse files
tbowmoThomas Bowman Mørch
andauthored
Make position optional (#39)
* Make position optional * Update readme file and other minor things * Upgrade dependencies * Maintainability fixes * Bump version for release --------- Co-authored-by: Thomas Bowman Mørch <[email protected]>
1 parent c6ea187 commit af3938f

13 files changed

+1818
-1310
lines changed

.eslintignore

Whitespace-only changes.

.eslintrc.js

Lines changed: 0 additions & 77 deletions
This file was deleted.

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ Once installed, restart your node-red server, and you will have a set of new nod
1717

1818
# Setup
1919
![image of configuration](images/small-timer-config.png)<br>
20-
Start by configuring a position, this is used to calculate the correct sunrise/sunset times for your area. You can either let your browser do it by pressing the button "Get position from browser" or use other means like Google Maps to find the latitude/longitude for your area.
20+
_Optional_: Start by configuring your location, which is used to calculate the correct sunrise and sunset times for your area. You can either let your browser do this by clicking the "Get position from browser" button, or manually enter the latitude and longitude using a service like Google Maps.
21+
22+
If you don't configure a location, you'll only be able to use fixed times to send on/off messages.
2123

2224
## On and Off time
2325
Configures the on and off events<br>

eslint.config.mjs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import typescriptEslint from '@typescript-eslint/eslint-plugin'
2+
import _import from 'eslint-plugin-import'
3+
import importNewlines from 'eslint-plugin-import-newlines'
4+
import { fixupPluginRules } from '@eslint/compat'
5+
import globals from 'globals'
6+
import tsParser from '@typescript-eslint/parser'
7+
import path from 'node:path'
8+
import { fileURLToPath } from 'node:url'
9+
import js from '@eslint/js'
10+
import { FlatCompat } from '@eslint/eslintrc'
11+
12+
const __filename = fileURLToPath(import.meta.url)
13+
const __dirname = path.dirname(__filename)
14+
const compat = new FlatCompat({
15+
baseDirectory: __dirname,
16+
recommendedConfig: js.configs.recommended,
17+
allConfig: js.configs.all,
18+
})
19+
20+
export default [
21+
...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended'),
22+
{
23+
plugins: {
24+
'@typescript-eslint': typescriptEslint,
25+
import: fixupPluginRules(_import),
26+
'import-newlines': importNewlines,
27+
},
28+
29+
languageOptions: {
30+
globals: {
31+
...globals.node,
32+
},
33+
34+
parser: tsParser,
35+
ecmaVersion: 'latest',
36+
sourceType: 'module',
37+
},
38+
39+
rules: {
40+
'import/order': 'off',
41+
'eol-last': 'error',
42+
'comma-dangle': ['warn', 'always-multiline'],
43+
44+
indent: ['error', 4, {
45+
SwitchCase: 1,
46+
}],
47+
48+
'linebreak-style': ['error', 'unix'],
49+
50+
quotes: ['warn', 'single', {
51+
avoidEscape: true,
52+
}],
53+
54+
semi: ['warn', 'never'],
55+
56+
'max-len': ['error', {
57+
code: 180,
58+
}],
59+
60+
'no-console': ['warn'],
61+
curly: ['error'],
62+
eqeqeq: ['error'],
63+
complexity: ['error', 11],
64+
65+
'import-newlines/enforce': ['error', {
66+
items: 2,
67+
'max-len': 180,
68+
semi: false,
69+
}],
70+
},
71+
},
72+
]

package.json

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"author": {
44
"name": "Thomas Bowman Mørch"
55
},
6-
"version": "0.18.0",
6+
"version": "0.19.0",
77
"engines": {
88
"node": ">=14.0.0"
99
},
@@ -57,34 +57,38 @@
5757
"url": "git+https://github.com/tbowmo/node-red-small-timer.git"
5858
},
5959
"dependencies": {
60-
"@node-red/util": "^3.1.8",
61-
"date-fns": "^3.6.0",
60+
"@node-red/util": "^3.1.14",
61+
"date-fns": "^4.1.0",
6262
"suncalc": "^1.9.0"
6363
},
6464
"devDependencies": {
65-
"@types/chai": "^4.3.14",
66-
"@types/mocha": "^10.0.6",
67-
"@types/node": "^20.12.2",
65+
"@eslint/compat": "1.2.1",
66+
"@eslint/eslintrc": "3.1.0",
67+
"@eslint/js": "9.13.0",
68+
"@types/chai": "^4.3.20",
69+
"@types/mocha": "^10.0.9",
70+
"@types/node": "^20.16.13",
6871
"@types/node-red": "^1.3.5",
6972
"@types/node-red-node-test-helper": "^0.3.4",
7073
"@types/suncalc": "^1.9.2",
71-
"@typescript-eslint/eslint-plugin": "^7.4.0",
72-
"@typescript-eslint/parser": "^7.4.0",
73-
"chai": "^4.4.1",
74-
"eslint": "^8.57.0",
75-
"eslint-plugin-import": "^2.29.1",
74+
"@typescript-eslint/eslint-plugin": "^8.10.0",
75+
"@typescript-eslint/parser": "^8.10.0",
76+
"chai": "^4.5.0",
77+
"eslint": "^9.13.0",
78+
"eslint-plugin-import": "^2.31.0",
7679
"eslint-plugin-import-newlines": "^1.4.0",
77-
"husky": "^9.0.11",
78-
"lint-staged": "^15.2.2",
79-
"mocha": "^10.4.0",
80-
"node-red": "^3.1.8",
81-
"node-red-node-test-helper": "^0.3.3",
82-
"nyc": "^15.1.0",
83-
"prettier": "^3.2.5",
84-
"sinon": "^17.0.1",
80+
"globals": "15.11.0",
81+
"husky": "^9.1.6",
82+
"lint-staged": "^15.2.10",
83+
"mocha": "^10.7.3",
84+
"node-red": "^3.1.14",
85+
"node-red-node-test-helper": "^0.3.4",
86+
"nyc": "^17.1.0",
87+
"prettier": "^3.3.3",
88+
"sinon": "^19.0.2",
8589
"source-map-support": "^0.5.21",
8690
"ts-node": "^10.9.2",
87-
"typescript": "^5.4.3"
91+
"typescript": "^5.6.3"
8892
},
8993
"packageManager": "[email protected]"
9094
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ describe('lib/small-timer-runner', () => {
442442
stubs.stubbedTimeCalc.getTimeToNextEndEvent.returns(20)
443443
stubs.stubbedTimeCalc.getOnState.returns(false)
444444

445-
const runner = new SmallTimerRunner(stubs.position, stubs.configuration, stubs.node)
445+
const runner = new SmallTimerRunner(undefined, stubs.configuration, stubs.node)
446446
sinon.clock.tick(80000)
447447

448448
sinon.assert.calledWithExactly(stubs.status, { fill: 'red', shape: 'dot', text: 'OFF for 00secs' })

src/lib/small-timer-runner.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const SecondsTick = 1000
2929

3030
export class SmallTimerRunner {
3131

32-
private startupTock: ReturnType<typeof setTimeout> | undefined = undefined
32+
private readonly startupTock: ReturnType<typeof setTimeout> | undefined = undefined
3333

3434
// Timing variables
3535
private tickTimer: ReturnType<typeof setInterval> | undefined = undefined
@@ -38,34 +38,34 @@ export class SmallTimerRunner {
3838
private override: State = 'auto'
3939
private currentState = false
4040

41-
private topic: string
42-
private onMsg: string
43-
private offMsg: string
44-
private onMsgType: string
45-
private offMsgType: string
46-
private rules: Rule[]
47-
private repeat: boolean
48-
private repeatInterval: number
49-
private onTimeout: number
50-
private offTimeout: number
41+
private readonly topic: string
42+
private readonly onMsg: string
43+
private readonly offMsg: string
44+
private readonly onMsgType: string
45+
private readonly offMsgType: string
46+
private readonly rules: Rule[]
47+
private readonly repeat: boolean
48+
private readonly repeatInterval: number
49+
private readonly onTimeout: number
50+
private readonly offTimeout: number
5151

52-
private timeCalc: TimeCalc
53-
private debugMode = false
52+
private readonly timeCalc: TimeCalc
53+
private readonly debugMode: boolean
5454

55-
private timer = new Timer()
56-
private sendEmptyPayload: boolean
55+
private readonly timer = new Timer()
56+
private readonly sendEmptyPayload: boolean
5757

5858
// Default to 20 seconds between ticks (update of state / node)
59-
private defaultTickTimer = SecondsTick * 20
59+
private readonly defaultTickTimer = SecondsTick * 20
6060

6161
constructor(
62-
position: Position,
62+
position: Position | undefined,
6363
configuration: ISmallTimerProperties,
64-
private node: NodeFunctions,
64+
private readonly node: NodeFunctions,
6565
) {
6666
this.timeCalc = new TimeCalc(
67-
Number(position.latitude),
68-
Number(position.longitude),
67+
position ? Number(position.latitude) : undefined,
68+
position ? Number(position.longitude) : undefined,
6969
configuration.wrapMidnight,
7070
Number(configuration.startTime),
7171
Number(configuration.endTime),

src/lib/sun-and-moon.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ export class SunAndMoon {
5353
* @param wrapMidnight
5454
*/
5555
constructor(
56-
protected latitude: number,
57-
protected longitude: number,
56+
protected readonly latitude?: number,
57+
protected readonly longitude?: number,
5858
) {
5959
}
6060

6161
public getTimes(now = new Date()): {id: string, label: string, date: Date}[] {
62+
if (!this.latitude || !this.longitude) {
63+
return []
64+
}
6265
this.sunTimes = SunCalc.getTimes(now, this.latitude, this.longitude)
6366
this.moonTimes = SunCalc.getMoonTimes(now, this.latitude, this.longitude)
6467

@@ -67,7 +70,7 @@ export class SunAndMoon {
6770
return
6871
}
6972
const labelParts = key.split(/(?=[A-Z])/)
70-
let label = labelParts.join(' ').toLocaleLowerCase()
73+
let label = labelParts.join(' ').toLocaleLowerCase()
7174
if (this.sunTimes && key in this.sunTimes) {
7275
label = capitalizeFirstLetter(label)
7376
const date = this.sunTimes[(key as SunTimes)]
@@ -106,7 +109,7 @@ export class SunAndMoon {
106109

107110
protected updateSunCalc(now = new Date()) {
108111
// Only necessary to do the calculations once a day
109-
if (this.lastSunCalcUpdate === now.getDay()) {
112+
if (this.lastSunCalcUpdate === now.getDay() || !this.latitude || !this.longitude) {
110113
return
111114
}
112115

src/lib/time-calculation.spec.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ describe('lib/time-calculation', () => {
3535
const minutes = currentTime.getHours() * 60 + currentTime.getMinutes()
3636

3737
const timeCalc = new TimeCalc(
38-
10,
39-
10,
38+
undefined,
39+
undefined,
4040
false,
4141
minutes - 120, // 09:00
4242
minutes + 120, // 12:00
@@ -45,7 +45,7 @@ describe('lib/time-calculation', () => {
4545
0,
4646
)
4747

48-
sinon.assert.calledWith(stubs.getTimes, currentTime, 10, 10)
48+
sinon.assert.notCalled(stubs.getTimes)
4949
expect(timeCalc.getOnState()).to.equal(true)
5050
expect(timeCalc.getTimeToNextStartEvent()).to.equal(1320)
5151
expect(timeCalc.getTimeToNextEndEvent()).to.equal(120)
@@ -202,6 +202,25 @@ describe('lib/time-calculation', () => {
202202
.to.throw('Can\'t look up the correct time \'6001\' \'0\'')
203203
})
204204

205+
it.skip('should throw error if position is not set and dynamic time is requested', () => {
206+
setupTest()
207+
sinon.clock.setSystemTime(new Date('2023-01-01 13:00'))
208+
209+
const timeCalc = new TimeCalc(
210+
undefined,
211+
undefined,
212+
true,
213+
5000,
214+
5001,
215+
0,
216+
0,
217+
0,
218+
)
219+
220+
expect(timeCalc.setStartEndTime.bind(timeCalc, 5101, 5102))
221+
.to.throw('Something went wrong, latitude and longitude not specified')
222+
})
223+
205224
it('should create data suitable for debug', () => {
206225
setupTest()
207226
sinon.clock.setSystemTime(new Date('2023-01-01 13:00'))

0 commit comments

Comments
 (0)