From 22f634b785e7ff515a95b637c1132bf56b2e085a Mon Sep 17 00:00:00 2001 From: Vitaliy Yakovchuk Date: Wed, 27 Sep 2023 09:05:30 +0300 Subject: [PATCH] Add scheduled device (#1) * Fix night temperature settings refresh * Add circadian plus timing device support * Fix prev circadian case * Make CircadianTimingDriver methods private * Fix typo/more validations * Fix search for the prev item --- app.json | 216 ++++++++++ drivers/circadian-timing-zone/device.ts | 390 ++++++++++++++++++ .../circadian-timing-zone/driver.compose.json | 31 ++ .../driver.flow.compose.json | 28 ++ .../driver.settings.compose.json | 151 +++++++ drivers/circadian-timing-zone/driver.ts | 38 ++ drivers/circadian-zone/device.ts | 4 +- drivers/circadian-zone/driver.ts | 10 +- locales/en.json | 4 +- 9 files changed, 866 insertions(+), 6 deletions(-) create mode 100644 drivers/circadian-timing-zone/device.ts create mode 100644 drivers/circadian-timing-zone/driver.compose.json create mode 100644 drivers/circadian-timing-zone/driver.flow.compose.json create mode 100644 drivers/circadian-timing-zone/driver.settings.compose.json create mode 100644 drivers/circadian-timing-zone/driver.ts diff --git a/app.json b/app.json index 9f46abb..dfae00a 100644 --- a/app.json +++ b/app.json @@ -36,6 +36,37 @@ "source": "https://github.com/alistairg/net.alistairs.circadian", "flow": { "triggers": [ + { + "id": "circadian_timing_changed", + "title": { + "en": "Circadian values changed" + }, + "tokens": [ + { + "name": "brightness", + "type": "number", + "title": { + "en": "Brightness" + }, + "example": 0.9 + }, + { + "name": "temperature", + "type": "number", + "title": { + "en": "Colour Temperature" + }, + "example": 0.5 + } + ], + "args": [ + { + "type": "device", + "name": "device", + "filter": "driver_id=circadian-timing-zone" + } + ] + }, { "id": "circadian_changed", "title": { @@ -70,6 +101,191 @@ ] }, "drivers": [ + { + "name": { + "en": "Circadian Timing Zone" + }, + "class": "light", + "capabilities": [ + "light_temperature", + "dim", + "adaptive_mode" + ], + "platforms": [ + "local" + ], + "connectivity": [], + "images": { + "small": "/drivers/circadian-timing-zone/assets/../../circadian-zone/assets/images/small.png", + "large": "/drivers/circadian-timing-zone/assets/../../circadian-zone/assets/images/large.png", + "xlarge": "/drivers/circadian-timing-zone/assets/../../circadian-zone/assets/images/xlarge.png" + }, + "pair": [ + { + "id": "list_devices", + "template": "list_devices", + "navigation": { + "next": "add_devices" + } + }, + { + "id": "add_devices", + "template": "add_devices" + } + ], + "id": "circadian-timing-zone", + "settings": [ + { + "type": "group", + "label": { + "en": "Colour Temperature" + }, + "children": [ + { + "id": "noon_temp", + "type": "number", + "label": { + "en": "Noon Warmth" + }, + "value": 40, + "min": 0, + "max": 100, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "units": { + "en": "%" + } + }, + { + "id": "sunset_temp", + "type": "number", + "label": { + "en": "Sunset Warmth" + }, + "value": 100, + "min": 0, + "max": 100, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "units": { + "en": "%" + } + } + ] + }, + { + "type": "group", + "label": { + "en": "Brightness" + }, + "children": [ + { + "id": "min_brightness", + "type": "number", + "label": { + "en": "Minimum" + }, + "value": 10, + "min": 1, + "max": 100, + "units": "%" + }, + { + "id": "max_brightness", + "type": "number", + "label": { + "en": "Maximum" + }, + "value": 100, + "min": 1, + "max": 100, + "units": "%" + } + ] + }, + { + "type": "group", + "label": { + "en": "Night Mode" + }, + "children": [ + { + "id": "night_brightness", + "type": "number", + "label": { + "en": "Night Brightness" + }, + "value": 10, + "min": 1, + "max": 100, + "units": "%" + }, + { + "id": "night_temp", + "type": "number", + "label": { + "en": "Night Temperature" + }, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "value": 0, + "min": 0, + "max": 100, + "units": "%" + } + ] + }, + { + "type": "group", + "label": { + "en": "Timing" + }, + "children": [ + { + "id": "fade_duration", + "type": "number", + "label": { + "en": "Fade Duration" + }, + "value": 60, + "min": 0, + "max": 1440, + "units": { + "en": "minutes" + }, + "hint": { + "en": "If zero, switch will not be gradual, if bigger than switch interval, switch interval will be used" + } + }, + { + "id": "timing", + "type": "textarea", + "label": { + "en": "Timing JSON" + }, + "value": "", + "hint": { + "en": "Sorted time key - object JSON (could be empty). Value should be \"circadian\" or number in [0,1] range" + } + }, + { + "id": "night_timing", + "type": "textarea", + "label": { + "en": "Timing JSON for Night Mode" + }, + "value": "", + "hint": { + "en": "Sorted time key - object JSON (could be empty). Value should be \"circadian\" or number in [0,1] range" + } + } + ] + } + ] + }, { "name": { "en": "Circadian Zone" diff --git a/drivers/circadian-timing-zone/device.ts b/drivers/circadian-timing-zone/device.ts new file mode 100644 index 0000000..67c7e16 --- /dev/null +++ b/drivers/circadian-timing-zone/device.ts @@ -0,0 +1,390 @@ +import { CircadianTimingDriver } from './driver' + +type TimeValue = { + brightness: string; + temperature: string; +}; + +interface Timing { + [index: string]: TimeValue +}; + +type Time = { + hours: number; + minutes: number; +}; + +interface TimingItem { + time: Time, + value: TimeValue, +} + +export class CircadianTimingZone extends require('../circadian-zone/device') { + + private _timings: TimingItem[] = []; + private _nightTimings: TimingItem[] = []; + private _fadeDuration: number = -1; + + async onSettings(event: { + newSettings: { timing: string, night_timing: string, fade_duration: number }, + changedKeys: string[] + }): Promise { + if (event.changedKeys.includes('timing')) { + if (!this.validateTiming(event.newSettings.timing)) { + return this.homey.__("json_timing_error"); + } + this._timings = this.parseTiming(event.newSettings.timing); + } + if (event.changedKeys.includes('night_timing')) { + if (!this.validateTiming(event.newSettings.night_timing)) { + return this.homey.__("json_timing_error"); + } + this._nightTimings = this.parseTiming(event.newSettings.night_timing); + } + if (event.changedKeys.includes('fade_duration')) { + this._fadeDuration = event.newSettings.fade_duration; + } + await super.onSettings(event); + } + + /** + * onInit is called when the device is initialized. + */ + async onInit() { + + this._timings = this.parseTiming(await this.getSetting("timing")); + this._nightTimings = this.parseTiming(await this.getSetting("night_timing")); + + // Mode Listener + this.registerCapabilityListener("adaptive_mode", async (value: any) => { + this.log(`Mode changed to ${value}`) + await this.setMode(value); + }); + + // Temperature Override Listener + this.registerCapabilityListener("light_temperature", async (value: any) => { + this.log(`Temperature override to ${value}`); + await this.overrideCurrentTemperature(value); + }); + + // Dim Override Listener + this.registerCapabilityListener("dim", async (value: any) => { + this.log(`Dim override to ${value}`); + await this.overrideCurrentBrightness(value); + }); + + this.log('CircadianTimingZone has been initialized'); + this.refreshZone(); + } + + /** + * refreshZone updates the zone values, based on mode and circadian progress + */ + async refreshZone() { + const mode = await this.getMode(); + if (mode == "adaptive") { + if (this._timings.length < 2) { + super.refreshZone(); + return; + } + } else if (mode == "night") { + if (this._nightTimings.length < 2) { + super.refreshZone(); + return; + } + } else { + return; + } + + let valuesChanged: boolean = false; + const date = new Date() + const currentTime: Time = this.dateToLocalTime(date); + const prevItem = this.findPrevItem(currentTime, mode); + const nextItem = this.findNextItem(currentTime, mode); + + let brightness: number = -1; + if (prevItem.value.brightness === 'circadian' && nextItem.value.brightness === 'circadian') { + brightness = await this.calcCircadianBrightness(mode); + } else { + const fade = await this.calcPrevNextFade(prevItem, nextItem) + const prevBrightness = await this.calcItemPrevBrightness(prevItem, mode); + const nextBrightness = await this.calcItemBrightness(nextItem, mode); + brightness = prevBrightness * (1 - fade) + nextBrightness * fade; + } + + let temperature: number = -1; + if (prevItem.value.temperature === 'circadian' && nextItem.value.temperature === 'circadian') { + temperature = await this.calcCircadianTemperature(mode); + } else { + const fade = await this.calcPrevNextFade(prevItem, nextItem) + const prevTemperature = await this.calcItemPrevTemperature(prevItem, mode); + const nextTemperature = await this.calcItemTemperature(nextItem, mode); + temperature = prevTemperature * (1 - fade) + nextTemperature * fade; + } + brightness = Math.round(brightness * 100) / 100; + let currentBrightness = await this.getCurrentBrightness(); + if (brightness != currentBrightness) { + this._currentBrightness = brightness; + await this.setCapabilityValue("dim", brightness); + valuesChanged = true; + } + const currentTemperature = await this.getCurrentTemperature(); + + temperature = Math.round(temperature * 100) / 100; + + if (temperature != currentTemperature) { + this._currentTemperature = temperature; + await this.setCapabilityValue("light_temperature", temperature); + valuesChanged = true; + } else { + this.log(`No change in temperature from ${this._currentTemperature}%`) + } + + // Trigger flow if appropriate + if (valuesChanged) { + await this.triggerValuesChangedFlow(brightness, temperature); + } + } + + async getFadeDuration(): Promise { + if (this._fadeDuration == -1) { + this._fadeDuration = await this.getSetting("fade_duration"); + } + return this._fadeDuration; + } + + private async calcCircadianBrightness(mode: string, date?: Date) { + if (mode === 'night') { + return await this.getNightBrightness(); + } + const percentage = (this.driver as CircadianTimingDriver).getPercentageForDate(date); + const minBrightness: number = await this.getMinBrightness(); + const maxBrightness: number = await this.getMaxBrightness(); + const brightnessDelta = maxBrightness - minBrightness; + return (percentage > 0) ? (brightnessDelta * percentage) + minBrightness : minBrightness; + } + + private async calcCircadianTemperature(mode: string, date?: Date) { + if (mode === 'night') { + return await this.getNightTemperature(); + } + const percentage = (this.driver as CircadianTimingDriver).getPercentageForDate(date); + const sunsetTemp: number = await this.getSunsetTemperature(); + const noonTemp: number = await this.getNoonTemperature(); + const tempDelta = sunsetTemp - noonTemp; + let calculatedTemperature = (tempDelta * (1 - percentage)) + noonTemp; + return (percentage > 0) ? calculatedTemperature : sunsetTemp; + } + + private async calcPrevNextFade(prevItem: TimingItem, nextItem: TimingItem) { + let diff = this.timeToInt(nextItem.time) - this.timeToInt(prevItem.time) + if (diff < 0) { + diff = 24 * 60 - diff; + } + + const fadeDuration = Math.min(await this.getFadeDuration(), diff); + + const currentTime: Time = this.dateToLocalTime(new Date()); + + diff = this.timeToInt(nextItem.time) - this.timeToInt(currentTime) + if (diff < 0) { + diff = 24 * 60 - diff; + } + + if (diff > fadeDuration) { + return 0; + } + return (fadeDuration - diff) / fadeDuration; + } + + private async calcItemBrightness(item: TimingItem, mode: string) { + if (item.value.brightness === 'circadian') { + const date = new Date() + const currentTime = this.dateToLocalTime(date); + + const diff = this.timeToInt(item.time) - this.timeToInt(currentTime) + date.setMinutes(date.getMinutes() + diff); + + return await this.calcCircadianBrightness(mode, date) + } + return parseFloat(item.value.brightness); + } + + private async calcItemPrevBrightness(item: TimingItem, mode: string) { + if (item.value.brightness === 'circadian') { + return await this.calcCircadianBrightness(mode) + } + return parseFloat(item.value.brightness); + } + + private async calcItemPrevTemperature(item: TimingItem, mode: string) { + if (item.value.temperature === 'circadian') { + return await this.calcCircadianTemperature(mode) + } + return parseFloat(item.value.temperature); + } + + private async calcItemTemperature(item: TimingItem, mode: string) { + if (item.value.temperature === 'circadian') { + const date = new Date() + const currentTime: Time = this.dateToLocalTime(date); + + const diff = this.timeToInt(item.time) - this.timeToInt(currentTime) + date.setMinutes(date.getMinutes() + diff); + + return await this.calcCircadianTemperature(mode, date) + } + return parseFloat(item.value.temperature); + } + + private timeToInt(time: Time) { + return time.hours * 60 + time.minutes; + } + + private findPrevItem(time: Time, mode: string) { + let arr = this.getTimingArray(mode); + let index = this.binarySearch(arr, time); + if (index < arr.length && this.timeToInt(arr[index].time) === this.timeToInt(time)) { + return arr[index]; + } + index--; + if (index >= 0) { + return arr[index]; + } + return arr[arr.length - 1]; + } + + private findNextItem(time: Time, mode: string) { + let arr = this.getTimingArray(mode); + let index = this.binarySearch(arr, time); + if (index >= arr.length) { + return arr[0]; + } + if (this.timeToInt(arr[index].time) === this.timeToInt(time)) { + index++; + } + if (index >= arr.length) { + return arr[0]; + } + return arr[index]; + } + + private getTimingArray(mode: string): TimingItem[] { + if (mode === 'adaptive') { + return this._timings; + } + return this._nightTimings; + } + + private binarySearch(arr: TimingItem[], target: Time): number { + let left = 0; + let right = arr.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + + if (arr[mid].time.hours === target.hours && arr[mid].time.minutes === target.minutes) { + return mid; + } + if (this.timeToInt(arr[mid].time) < this.timeToInt(target)) { + left = mid + 1; + } else { + right = mid - 1; + } + } + + return left; // Return the index where the item is supposed to stay + } + + + private validateTiming(timing: string): boolean { + if (timing === '') { + return true; + } + + let lastTime = -1; + + try { + const parsedTiming: Timing = JSON.parse(timing); + if (Object.keys(parsedTiming).length < 2){ + return false; + } + for (const time in parsedTiming) { + if (!this.isValidTime(time)) { + this.log(`Time value ${time} is not valid`); + return false; + } + + const tm = this.parseTime(time); + + const intTime = this.timeToInt(tm); + if (intTime <= lastTime) { + this.log(`Time value ${time} smaller than prev time`); + return false; + } + + lastTime = intTime; + + const value = parsedTiming[time]; + if (value.brightness !== 'circadian') { + const brightness = parseFloat(value.brightness); + + if (isNaN(brightness) || brightness < 0 || brightness > 1) { + return false; + } + } + + if (value.temperature !== 'circadian') { + const temperature = parseFloat(value.temperature); + if (isNaN(temperature) || temperature < 0 || temperature > 1) { + return false; + } + } + } + return true; + } catch (e) { + this.log(e) + return false; + } + } + + private isValidTime(time: string): boolean { + const timeRegExp = /^([0-9]|0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/; + return timeRegExp.test(time); + } + + private parseTiming(timing: any): TimingItem[] { + if (!timing) { + return []; + } + const rawTiming: Timing = JSON.parse(timing); + const result: TimingItem[] = []; + for (const time in rawTiming) { + result.push( + { + time: this.parseTime(time), + value: rawTiming[time] + } + ) + } + return result; + } + + private parseTime(time: string): Time { + const [hoursStr, minutesStr] = time.split(':'); + const hours = parseInt(hoursStr, 10); + const minutes = parseInt(minutesStr, 10); + + return { hours, minutes }; + } + + private dateToLocalTime(date: Date) { + return this.parseTime( + date.toLocaleString('en-UK', + { minute: 'numeric', hour: 'numeric', timeZone: this.homey.clock.getTimezone() } + ) + ); + } +} + +module.exports = CircadianTimingZone; diff --git a/drivers/circadian-timing-zone/driver.compose.json b/drivers/circadian-timing-zone/driver.compose.json new file mode 100644 index 0000000..3bf57c3 --- /dev/null +++ b/drivers/circadian-timing-zone/driver.compose.json @@ -0,0 +1,31 @@ +{ + "name": { + "en": "Circadian Timing Zone" + }, + "class": "light", + "capabilities": [ + "light_temperature", + "dim", + "adaptive_mode" + ], + "platforms": [ + "local" + ], + "connectivity": [], + "images": { + "small": "{{driverAssetsPath}}/../../circadian-zone/assets/images/small.png", + "large": "{{driverAssetsPath}}/../../circadian-zone/assets/images/large.png", + "xlarge": "{{driverAssetsPath}}/../../circadian-zone/assets/images/xlarge.png" + }, + "pair": [ + { + "id": "list_devices", + "template": "list_devices", + "navigation": { "next": "add_devices" } + }, + { + "id": "add_devices", + "template": "add_devices" + } + ] +} \ No newline at end of file diff --git a/drivers/circadian-timing-zone/driver.flow.compose.json b/drivers/circadian-timing-zone/driver.flow.compose.json new file mode 100644 index 0000000..265668a --- /dev/null +++ b/drivers/circadian-timing-zone/driver.flow.compose.json @@ -0,0 +1,28 @@ +{ + "triggers": [ + { + "id": "circadian_timing_changed", + "title": { + "en": "Circadian values changed" + }, + "tokens": [ + { + "name": "brightness", + "type": "number", + "title": { + "en": "Brightness" + }, + "example": 0.9 + }, + { + "name": "temperature", + "type": "number", + "title": { + "en": "Colour Temperature" + }, + "example": 0.5 + } + ] + } + ] +} \ No newline at end of file diff --git a/drivers/circadian-timing-zone/driver.settings.compose.json b/drivers/circadian-timing-zone/driver.settings.compose.json new file mode 100644 index 0000000..b57c54c --- /dev/null +++ b/drivers/circadian-timing-zone/driver.settings.compose.json @@ -0,0 +1,151 @@ +[ + { + "type": "group", + "label": { + "en": "Colour Temperature" + }, + "children": [ + { + "id": "noon_temp", + "type": "number", + "label": { + "en": "Noon Warmth" + }, + "value": 40, + "min": 0, + "max": 100, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "units": { + "en": "%" + } + }, + { + "id": "sunset_temp", + "type": "number", + "label": { + "en": "Sunset Warmth" + }, + "value": 100, + "min": 0, + "max": 100, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "units": { + "en": "%" + } + } + ] + }, + { + "type": "group", + "label": { + "en": "Brightness" + }, + "children": [ + { + "id": "min_brightness", + "type": "number", + "label": { + "en": "Minimum" + }, + "value": 10, + "min": 1, + "max": 100, + "units": "%" + }, + { + "id": "max_brightness", + "type": "number", + "label": { + "en": "Maximum" + }, + "value": 100, + "min": 1, + "max": 100, + "units": "%" + } + ] + }, + { + "type": "group", + "label": { + "en": "Night Mode" + }, + "children": [ + { + "id": "night_brightness", + "type": "number", + "label": { + "en": "Night Brightness" + }, + "value": 10, + "min": 1, + "max": 100, + "units": "%" + }, + { + "id": "night_temp", + "type": "number", + "label": { + "en": "Night Temperature" + }, + "hint": { + "en": "The coolest colour is 0, the warmest is 100." + }, + "value": 0, + "min": 0, + "max": 100, + "units": "%" + } + ] + }, + { + "type": "group", + "label": { + "en": "Timing" + }, + "children": [ + { + "id": "fade_duration", + "type": "number", + "label": { + "en": "Fade Duration" + }, + "value": 60, + "min": 0, + "max": 1440, + "units": { + "en": "minutes" + }, + "hint": { + "en": "If zero, switch will not be gradual, if bigger than switch interval, switch interval will be used" + } + }, + { + "id": "timing", + "type": "textarea", + "label": { + "en": "Timing JSON" + }, + "value": "", + "hint": { + "en": "Sorted time key - object JSON (could be empty). Value should be \"circadian\" or number in [0,1] range" + } + }, + { + "id": "night_timing", + "type": "textarea", + "label": { + "en": "Timing JSON for Night Mode" + }, + "value": "", + "hint": { + "en": "Sorted time key - object JSON (could be empty). Value should be \"circadian\" or number in [0,1] range" + } + } + ] + } +] \ No newline at end of file diff --git a/drivers/circadian-timing-zone/driver.ts b/drivers/circadian-timing-zone/driver.ts new file mode 100644 index 0000000..b8c3899 --- /dev/null +++ b/drivers/circadian-timing-zone/driver.ts @@ -0,0 +1,38 @@ +const { v4: uuidv4 } = require('uuid'); +import { CircadianTimingZone } from './device'; + +export class CircadianTimingDriver extends require('../circadian-zone/driver') { + + + /** + * onInit is called when the driver is initialized. + */ + async onInit() { + super.onInit() + this._circadianValuesChangedFlow = this.homey.flow.getDeviceTriggerCard("circadian_timing_changed"); + this.log('CircadianTimingDriver has been initialized'); + } + + async onPairListDevices() { + this.log('onPairListDevices'); + return [ + { + name: this.homey.__("circadian_timing_zone"), + data: { + id: uuidv4(), + }, + }, + ]; + } + + async _updateCircadianZones() { + + this.log("Circadian time zones"); + this.getDevices().forEach(async (device: CircadianTimingZone) => { + await device.refreshZone(); + }); + + } +} + +module.exports = CircadianTimingDriver; diff --git a/drivers/circadian-zone/device.ts b/drivers/circadian-zone/device.ts index 59a68ef..5497831 100644 --- a/drivers/circadian-zone/device.ts +++ b/drivers/circadian-zone/device.ts @@ -199,7 +199,7 @@ export class CircadianZone extends Homey.Device { * @param {string[]} event.changedKeys An array of keys changed since the previous version * @returns {Promise} return a custom message that will be displayed */ - async onSettings(event: { oldSettings: {}, newSettings: {max_brightness: number, min_brightness: number, night_brightness: number, night_temperature: number, sunset_temp: number, noon_temp:number}, changedKeys: [] }): Promise { + async onSettings(event: { oldSettings: {}, newSettings: {max_brightness: number, min_brightness: number, night_brightness: number, night_temp: number, sunset_temp: number, noon_temp:number}, changedKeys: [] }): Promise { // Sanity check if (!(event.newSettings.sunset_temp > event.newSettings.noon_temp)) { @@ -213,7 +213,7 @@ export class CircadianZone extends Homey.Device { this._noonTemp = event.newSettings.noon_temp / 100.0; this._sunsetTemp = event.newSettings.sunset_temp / 100.0; this._nightBrightness = event.newSettings.night_brightness / 100.0; - this._nightTemperature = event.newSettings.night_temperature / 100.0; + this._nightTemperature = event.newSettings.night_temp / 100.0; await this.refreshZone(); } diff --git a/drivers/circadian-zone/driver.ts b/drivers/circadian-zone/driver.ts index e0a6d79..b299429 100644 --- a/drivers/circadian-zone/driver.ts +++ b/drivers/circadian-zone/driver.ts @@ -9,7 +9,7 @@ export class CircadianDriver extends Homey.Driver { private _intervalId: NodeJS.Timer; private _circadianPercentage: number = -1; - private _circadianValuesChangedFlow: Homey.FlowCardTriggerDevice; + protected _circadianValuesChangedFlow: Homey.FlowCardTriggerDevice; /** * onInit is called when the driver is initialized. @@ -84,7 +84,7 @@ export class CircadianDriver extends Homey.Driver { * @returns {number} percentage progress through the day * */ - private _recalculateCircadianPercentage(): number { + private _recalculateCircadianPercentage(date?: Date): number { // Debug this.log("Recalculating..."); @@ -92,7 +92,7 @@ export class CircadianDriver extends Homey.Driver { // Get location const latitude: number = this.homey.geolocation.getLatitude(); const longitude: number = this.homey.geolocation.getLongitude(); - const now = new Date(); + const now = date || new Date(); // Calculate times let sunTools = new SunTools(now, latitude, longitude); @@ -137,6 +137,10 @@ export class CircadianDriver extends Homey.Driver { return this._circadianPercentage; } + getPercentageForDate(date:Date): number { + return this._recalculateCircadianPercentage(date); + } + // Handler for an open request triggerValuesChangedFlow(device: Homey.Device, tokens: any, state: any) { this._circadianValuesChangedFlow diff --git a/locales/en.json b/locales/en.json index bb769ed..55c0e90 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,4 +1,6 @@ { "circadian_zone": "Circadian Zone", - "temperature_error": "Sunset temperature must be greater than noon temperature" + "circadian_timing_zone": "Circadian Timing Zone", + "temperature_error": "Sunset temperature must be greater than noon temperature", + "json_timing_error": "JSON non valid or in the wrong structure" } \ No newline at end of file