Skip to content

Commit ce9b71c

Browse files
committed
WIP: add Circadian Light Group driver and run_test.ps1 helper
Adds new circadian-light-group driver with capabilities, flow cards, pairing views, profile/outdoor-light libs, locale strings across all languages, and a PowerShell helper to run "homey app run --remote" that waits for a keypress before closing on exit.
1 parent db77bd3 commit ce9b71c

35 files changed

Lines changed: 3599 additions & 557 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"type": "boolean",
3+
"title": {
4+
"en": "Paused",
5+
"no": "Pauset"
6+
},
7+
"desc": {
8+
"en": "True when circadian light adjustments are paused.",
9+
"no": "Sann naar dognrytmejustering av lys er pauset."
10+
},
11+
"getable": true,
12+
"setable": true,
13+
"uiComponent": "toggle"
14+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"type": "number",
3+
"title": {
4+
"en": "Outdoor Computed Lux",
5+
"no": "Beregnet utendors lux"
6+
},
7+
"desc": {
8+
"en": "The outdoor light value currently used by the circadian profile.",
9+
"no": "Utelystallet som brukes av dognrytmeprofilen."
10+
},
11+
"getable": true,
12+
"setable": false,
13+
"uiComponent": "sensor",
14+
"units": {
15+
"en": "lx"
16+
},
17+
"decimals": 0
18+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"id": "clg_apply_now",
3+
"title": {
4+
"en": "Apply current circadian profile",
5+
"no": "Bruk gjeldende dognrytmeprofil"
6+
},
7+
"hint": {
8+
"en": "Immediately applies the current circadian target to lights that are already on.",
9+
"no": "Bruker gjeldende dognrytmemal umiddelbart paa lys som allerede er paa."
10+
},
11+
"args": [
12+
{
13+
"type": "device",
14+
"name": "device",
15+
"filter": "driver_id=circadian-light-group"
16+
}
17+
],
18+
"titleFormatted": {
19+
"en": "Apply current circadian profile",
20+
"no": "Bruk gjeldende dognrytmeprofil"
21+
}
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"id": "clg_pause",
3+
"title": {
4+
"en": "Pause circadian light group",
5+
"no": "Pause dognrytme-lysgruppe"
6+
},
7+
"hint": {
8+
"en": "Pauses automatic adjustments. Use minutes 0 to pause until resumed manually.",
9+
"no": "Pauser automatiske justeringer. Bruk 0 minutter for pause til manuell gjenopptakelse."
10+
},
11+
"args": [
12+
{
13+
"type": "device",
14+
"name": "device",
15+
"filter": "driver_id=circadian-light-group"
16+
},
17+
{
18+
"type": "number",
19+
"name": "minutes",
20+
"title": {
21+
"en": "Minutes",
22+
"no": "Minutter"
23+
},
24+
"placeholder": {
25+
"en": "0",
26+
"no": "0"
27+
}
28+
}
29+
],
30+
"titleFormatted": {
31+
"en": "Pause circadian light group for [[minutes]] minutes",
32+
"no": "Pause dognrytme-lysgruppe i [[minutes]] minutter"
33+
}
34+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "clg_resume",
3+
"title": {
4+
"en": "Resume circadian light group",
5+
"no": "Gjenoppta dognrytme-lysgruppe"
6+
},
7+
"args": [
8+
{
9+
"type": "device",
10+
"name": "device",
11+
"filter": "driver_id=circadian-light-group"
12+
}
13+
],
14+
"titleFormatted": {
15+
"en": "Resume circadian light group",
16+
"no": "Gjenoppta dognrytme-lysgruppe"
17+
}
18+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"id": "clg_set_external_lux",
3+
"title": {
4+
"en": "Set outdoor computed lux",
5+
"no": "Sett beregnet utendors lux"
6+
},
7+
"hint": {
8+
"en": "Pushes an externally computed outdoor light value into the Circadian Light Group.",
9+
"no": "Sender en eksternt beregnet utelysverdi til dognrytme-lysgruppen."
10+
},
11+
"args": [
12+
{
13+
"type": "device",
14+
"name": "device",
15+
"filter": "driver_id=circadian-light-group"
16+
},
17+
{
18+
"type": "number",
19+
"name": "lux",
20+
"title": {
21+
"en": "Outdoor computed lux",
22+
"no": "Beregnet utendors lux"
23+
}
24+
},
25+
{
26+
"type": "number",
27+
"name": "valid_minutes",
28+
"title": {
29+
"en": "Valid for minutes",
30+
"no": "Gyldig i minutter"
31+
},
32+
"placeholder": {
33+
"en": "15",
34+
"no": "15"
35+
}
36+
},
37+
{
38+
"type": "text",
39+
"name": "source",
40+
"title": {
41+
"en": "Source",
42+
"no": "Kilde"
43+
},
44+
"placeholder": {
45+
"en": "my-weather-flow",
46+
"no": "min-vaerflow"
47+
}
48+
}
49+
],
50+
"titleFormatted": {
51+
"en": "Set outdoor computed lux to [[lux]] from [[source]] for [[valid_minutes]] minutes",
52+
"no": "Sett beregnet utendors lux til [[lux]] fra [[source]] i [[valid_minutes]] minutter"
53+
}
54+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"id": "clg_error_occurred",
3+
"title": {
4+
"en": "Circadian light group error occurred",
5+
"no": "Feil oppstod i dognrytme-lysgruppe"
6+
},
7+
"args": [
8+
{
9+
"type": "device",
10+
"name": "device",
11+
"filter": "driver_id=circadian-light-group"
12+
}
13+
],
14+
"tokens": [
15+
{
16+
"name": "error",
17+
"type": "string",
18+
"title": {
19+
"en": "Error",
20+
"no": "Feil"
21+
}
22+
}
23+
]
24+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"id": "clg_outdoor_light_requested",
3+
"title": {
4+
"en": "Outdoor light value requested",
5+
"no": "Utelysverdi forespurt"
6+
},
7+
"hint": {
8+
"en": "Use this trigger with the 'Set outdoor computed lux' action when outdoorLight.provider is external_value.",
9+
"no": "Bruk denne triggeren sammen med handlingen 'Sett beregnet utendors lux' naar outdoorLight.provider er external_value."
10+
},
11+
"args": [
12+
{
13+
"type": "device",
14+
"name": "device",
15+
"filter": "driver_id=circadian-light-group"
16+
}
17+
]
18+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"id": "clg_target_changed",
3+
"title": {
4+
"en": "Circadian target changed",
5+
"no": "Dognrytmemal endret"
6+
},
7+
"args": [
8+
{
9+
"type": "device",
10+
"name": "device",
11+
"filter": "driver_id=circadian-light-group"
12+
}
13+
],
14+
"tokens": [
15+
{
16+
"name": "phase",
17+
"type": "string",
18+
"title": {
19+
"en": "Phase",
20+
"no": "Fase"
21+
}
22+
},
23+
{
24+
"name": "dim",
25+
"type": "number",
26+
"title": {
27+
"en": "Dim",
28+
"no": "Dim"
29+
}
30+
},
31+
{
32+
"name": "temperature",
33+
"type": "number",
34+
"title": {
35+
"en": "Temperature",
36+
"no": "Temperatur"
37+
}
38+
},
39+
{
40+
"name": "outdoor_lux",
41+
"type": "number",
42+
"title": {
43+
"en": "Outdoor lux",
44+
"no": "Utendors lux"
45+
}
46+
}
47+
]
48+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const { calculateTarget } = require('./lib/CircadianProfile');
4+
const { createOutdoorValue, estimateAstronomicalLux, estimateLuxFromRadiation, isFreshOutdoorValue } = require('./lib/OutdoorLightProvider');
5+
6+
describe('CircadianProfile', () => {
7+
test('returns day settings during the day plateau', () => {
8+
const target = calculateTarget({}, null, new Date('2026-04-30T12:00:00'));
9+
10+
expect(target.phase).toBe('day');
11+
expect(target.mode).toBe('temperature');
12+
expect(target.dim).toBeCloseTo(1, 2);
13+
expect(target.temperature).toBeCloseTo(0.85, 2);
14+
});
15+
16+
test('uses red color mode at night', () => {
17+
const target = calculateTarget({}, null, new Date('2026-04-30T23:30:00'));
18+
19+
expect(target.phase).toBe('night');
20+
expect(target.mode).toBe('color');
21+
expect(target.hue).toBe(0);
22+
expect(target.saturation).toBe(1);
23+
});
24+
25+
test('outdoor lux increases dim within configured limits', () => {
26+
const dark = calculateTarget({}, { outdoorComputedLux: 0 }, new Date('2026-04-30T12:00:00'));
27+
const bright = calculateTarget({}, { outdoorComputedLux: 20000 }, new Date('2026-04-30T12:00:00'));
28+
29+
expect(dark.outdoorDimFactor).toBeLessThan(bright.outdoorDimFactor);
30+
expect(bright.dim).toBe(1);
31+
});
32+
});
33+
34+
describe('OutdoorLightProvider helpers', () => {
35+
test('creates expiring external values', () => {
36+
const value = createOutdoorValue(173, 'test', 15);
37+
38+
expect(value.outdoorComputedLux).toBe(173);
39+
expect(value.source).toBe('test');
40+
expect(isFreshOutdoorValue(value)).toBe(true);
41+
});
42+
43+
test('estimates more lux from more radiation', () => {
44+
expect(estimateLuxFromRadiation(200, 0)).toBeGreaterThan(estimateLuxFromRadiation(50, 0));
45+
});
46+
47+
test('astronomical lux is zero-ish during night', () => {
48+
expect(estimateAstronomicalLux(new Date('2026-01-15T00:00:00'), 60)).toBe(0);
49+
});
50+
});

0 commit comments

Comments
 (0)