1 | import { EventEmitter } from "events";
|
2 | import { Lightbulb } from "../definitions";
|
3 | import { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from "./Controller";
|
4 | /**
|
5 | * @group Adaptive Lighting
|
6 | */
|
7 | export interface ActiveAdaptiveLightingTransition {
|
8 | /**
|
9 | * The instance id for the characteristic for which this transition applies to (aka the ColorTemperature characteristic).
|
10 | */
|
11 | iid: number;
|
12 | /**
|
13 | * Start of the transition in epoch time millis (as sent from the HomeKit controller).
|
14 | * Additionally see {@link timeMillisOffset}.
|
15 | */
|
16 | transitionStartMillis: number;
|
17 | /**
|
18 | * It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller
|
19 | * who set up the transition schedule.
|
20 | * Thus we record the delta between our current time and the the time sent with the setup request.
|
21 | * <code>timeMillisOffset</code> is defined as <code>Date.now() - transitionStartMillis;</code>.
|
22 | * So in the case were we actually have a correct local time, it most likely will be positive (due to network latency).
|
23 | * But of course it can also be negative.
|
24 | */
|
25 | timeMillisOffset: number;
|
26 | /**
|
27 | * Value is the same for ALL control write requests I have seen (even on other homes).
|
28 | * @private
|
29 | */
|
30 | transitionId: string;
|
31 | /**
|
32 | * Start of transition in milliseconds from 2001-01-01 00:00:00; unsigned 64 bit LE integer
|
33 | * @private as it is a 64 bit integer, we just store the buffer to not have the struggle to encode/decode 64 bit int in JavaScript
|
34 | */
|
35 | transitionStartBuffer: string;
|
36 | /**
|
37 | * Hex string of 8 bytes. Some kind of id (?). Sometimes it isn't supplied. Don't know the use for that.
|
38 | * @private
|
39 | */
|
40 | id3?: string;
|
41 | transitionCurve: AdaptiveLightingTransitionCurveEntry[];
|
42 | brightnessCharacteristicIID: number;
|
43 | brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange;
|
44 | /**
|
45 | * Interval in milliseconds specifies how often the accessory should update the color temperature (internally).
|
46 | * Typically this is 60000 aka 60 seconds aka 1 minute.
|
47 | * Note {@link notifyIntervalThreshold}
|
48 | */
|
49 | updateInterval: number;
|
50 | /**
|
51 | * Defines the interval in milliseconds on how often the accessory may send even notifications
|
52 | * to subscribed HomeKit controllers (aka call {@link Characteristic.updateValue}.
|
53 | * Typically this is 600000 aka 600 seconds aka 10 minutes or 300000 aka 300 seconds aka 5 minutes.
|
54 | */
|
55 | notifyIntervalThreshold: number;
|
56 | }
|
57 | /**
|
58 | * @group Adaptive Lighting
|
59 | */
|
60 | export interface AdaptiveLightingTransitionPoint {
|
61 | /**
|
62 | * This is the time offset from the transition start to the {@link lowerBound}.
|
63 | */
|
64 | lowerBoundTimeOffset: number;
|
65 | transitionOffset: number;
|
66 | lowerBound: AdaptiveLightingTransitionCurveEntry;
|
67 | upperBound: AdaptiveLightingTransitionCurveEntry;
|
68 | }
|
69 | /**
|
70 | * @group Adaptive Lighting
|
71 | */
|
72 | export interface AdaptiveLightingTransitionCurveEntry {
|
73 | /**
|
74 | * The color temperature in mired.
|
75 | */
|
76 | temperature: number;
|
77 | /**
|
78 | * The color temperature actually set to the color temperature characteristic is dependent
|
79 | * on the current brightness value of the lightbulb.
|
80 | * This means you will always need to query the current brightness when updating the color temperature
|
81 | * for the next transition step.
|
82 | * Additionally you will also need to correct the color temperature when the end user changes the
|
83 | * brightness of the Lightbulb.
|
84 | *
|
85 | * The brightnessAdjustmentFactor is always a negative floating point value.
|
86 | *
|
87 | * To calculate the resulting color temperature you will need to do the following.
|
88 | *
|
89 | * In short: temperature + brightnessAdjustmentFactor * currentBrightness
|
90 | *
|
91 | * Complete example:
|
92 | * ```js
|
93 | * const temperature = ...; // next transition value, the above property
|
94 | * // below query the current brightness while staying the the min/max brightness range (typically between 10-100 percent)
|
95 | * const currentBrightness = Math.max(minBrightnessValue, Math.min(maxBrightnessValue, CHARACTERISTIC_BRIGHTNESS_VALUE));
|
96 | *
|
97 | * // as both temperature and brightnessAdjustmentFactor are floating point values it is advised to round to the next integer
|
98 | * const resultTemperature = Math.round(temperature + brightnessAdjustmentFactor * currentBrightness);
|
99 | * ```
|
100 | */
|
101 | brightnessAdjustmentFactor: number;
|
102 | /**
|
103 | * The duration in milliseconds this exact temperature value stays the same.
|
104 | * When we transition to to the temperature value represented by this entry, it stays for the specified
|
105 | * duration on the exact same value (with respect to brightness adjustment) until we transition
|
106 | * to the next entry (see {@link transitionTime}).
|
107 | */
|
108 | duration?: number;
|
109 | /**
|
110 | * The time in milliseconds the color temperature should transition from the previous
|
111 | * entry to this one.
|
112 | * For example if we got the two values A and B, with A.temperature = 300 and B.temperature = 400 and
|
113 | * for the current time we are at temperature value 300. Then we need to transition smoothly
|
114 | * within the B.transitionTime to the B.temperature value.
|
115 | * If this is the first entry in the Curve (this value is probably zero) and is the offset to the transitionStartMillis
|
116 | * (the Date/Time were this transition curve was set up).
|
117 | */
|
118 | transitionTime: number;
|
119 | }
|
120 | /**
|
121 | * @group Adaptive Lighting
|
122 | */
|
123 | export interface BrightnessAdjustmentMultiplierRange {
|
124 | minBrightnessValue: number;
|
125 | maxBrightnessValue: number;
|
126 | }
|
127 | /**
|
128 | * @group Adaptive Lighting
|
129 | */
|
130 | export interface AdaptiveLightingOptions {
|
131 | /**
|
132 | * Defines how the controller will operate.
|
133 | * You can choose between automatic and manual mode.
|
134 | * See {@link AdaptiveLightingControllerMode}.
|
135 | */
|
136 | controllerMode?: AdaptiveLightingControllerMode;
|
137 | /**
|
138 | * Defines a custom temperature adjustment factor.
|
139 | *
|
140 | * This can be used to define a linear deviation from the HomeKit Controller defined
|
141 | * ColorTemperature schedule.
|
142 | *
|
143 | * For example supplying a value of `-10` will reduce the ColorTemperature, which is
|
144 | * calculated from the transition schedule, by 10 mired for every change.
|
145 | */
|
146 | customTemperatureAdjustment?: number;
|
147 | }
|
148 | /**
|
149 | * Defines in which mode the {@link AdaptiveLightingController} will operate in.
|
150 | * @group Adaptive Lighting
|
151 | */
|
152 | export declare const enum AdaptiveLightingControllerMode {
|
153 | /**
|
154 | * In automatic mode pretty much everything from setup to transition scheduling is done by the controller.
|
155 | */
|
156 | AUTOMATIC = 1,
|
157 | /**
|
158 | * In manual mode setup is done by the controller but the actual transition must be done by the user.
|
159 | * This is useful for lights which natively support transitions.
|
160 | */
|
161 | MANUAL = 2
|
162 | }
|
163 | /**
|
164 | * @group Adaptive Lighting
|
165 | */
|
166 | export declare const enum AdaptiveLightingControllerEvents {
|
167 | /**
|
168 | * This event is called once a HomeKit controller enables Adaptive Lighting
|
169 | * or a HomeHub sends an updated transition schedule for the next 24 hours.
|
170 | * This is also called on startup when AdaptiveLighting was previously enabled.
|
171 | */
|
172 | UPDATE = "update",
|
173 | /**
|
174 | * In yet unknown circumstances HomeKit may also send a dedicated disable command
|
175 | * via the control point characteristic. You may want to handle that in manual mode as well.
|
176 | * The current transition will still be associated with the controller object when this event is called.
|
177 | */
|
178 | DISABLED = "disable"
|
179 | }
|
180 | /**
|
181 | * @group Adaptive Lighting
|
182 | * see {@link ActiveAdaptiveLightingTransition}.
|
183 | */
|
184 | export interface AdaptiveLightingControllerUpdate {
|
185 | transitionStartMillis: number;
|
186 | timeMillisOffset: number;
|
187 | transitionCurve: AdaptiveLightingTransitionCurveEntry[];
|
188 | brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange;
|
189 | updateInterval: number;
|
190 | notifyIntervalThreshold: number;
|
191 | }
|
192 | /**
|
193 | * @group Adaptive Lighting
|
194 | */
|
195 | export declare interface AdaptiveLightingController {
|
196 | /**
|
197 | * See {@link AdaptiveLightingControllerEvents.UPDATE}
|
198 | * Also see {@link AdaptiveLightingControllerUpdate}
|
199 | *
|
200 | * @param event
|
201 | * @param listener
|
202 | */
|
203 | on(event: "update", listener: (update: AdaptiveLightingControllerUpdate) => void): this;
|
204 | /**
|
205 | * See {@link AdaptiveLightingControllerEvents.DISABLED}
|
206 | *
|
207 | * @param event
|
208 | * @param listener
|
209 | */
|
210 | on(event: "disable", listener: () => void): this;
|
211 | /**
|
212 | * See {@link AdaptiveLightingControllerUpdate}
|
213 | */
|
214 | emit(event: "update", update: AdaptiveLightingControllerUpdate): boolean;
|
215 | emit(event: "disable"): boolean;
|
216 | }
|
217 | /**
|
218 | * @group Adaptive Lighting
|
219 | */
|
220 | export interface SerializedAdaptiveLightingControllerState {
|
221 | activeTransition: ActiveAdaptiveLightingTransition;
|
222 | }
|
223 | /**
|
224 | * This class allows adding Adaptive Lighting support to Lightbulb services.
|
225 | * The Lightbulb service MUST have the {@link Characteristic.ColorTemperature} characteristic AND
|
226 | * the {@link Characteristic.Brightness} characteristic added.
|
227 | * The light may also expose {@link Characteristic.Hue} and {@link Characteristic.Saturation} characteristics
|
228 | * (though additional work is required to keep them in sync with the color temperature characteristic. see below)
|
229 | *
|
230 | * How Adaptive Lighting works:
|
231 | * When enabling AdaptiveLighting the iDevice will send a transition schedule for the next 24 hours.
|
232 | * This schedule will be renewed all 24 hours by a HomeHub in your home
|
233 | * (updating the schedule according to your current day/night situation).
|
234 | * Once enabled the lightbulb will execute the provided transitions. The color temperature value set is always
|
235 | * dependent on the current brightness value. Meaning brighter light will be colder and darker light will be warmer.
|
236 | * HomeKit considers Adaptive Lighting to be disabled as soon a write happens to either the
|
237 | * Hue/Saturation or the ColorTemperature characteristics.
|
238 | * The AdaptiveLighting state must persist across reboots.
|
239 | *
|
240 | * The AdaptiveLightingController can be operated in two modes: {@link AdaptiveLightingControllerMode.AUTOMATIC} and
|
241 | * {@link AdaptiveLightingControllerMode.MANUAL} with AUTOMATIC being the default.
|
242 | * The goal would be that the color transition is done DIRECTLY on the light itself, thus not creating any
|
243 | * additional/heavy traffic on the network.
|
244 | * So if your light hardware/API supports transitions please go the extra mile and use MANUAL mode.
|
245 | *
|
246 | *
|
247 | *
|
248 | * Below is an overview what you need to or consider when enabling AdaptiveLighting (categorized by mode).
|
249 | * The {@link AdaptiveLightingControllerMode} can be defined with the second constructor argument.
|
250 | *
|
251 | * <b>AUTOMATIC (Default mode):</b>
|
252 | *
|
253 | * This is the easiest mode to setup and needs less to no work form your side for AdaptiveLighting to work.
|
254 | * The AdaptiveLightingController will go through setup procedure with HomeKit and automatically update
|
255 | * the color temperature characteristic base on the current transition schedule.
|
256 | * It is also adjusting the color temperature when a write to the brightness characteristic happens.
|
257 | * Additionally, it will also handle turning off AdaptiveLighting, when it detects a write happening to the
|
258 | * ColorTemperature, Hue or Saturation characteristic (though it can only detect writes coming from HomeKit and
|
259 | * can't detect changes done to the physical devices directly! See below).
|
260 | *
|
261 | * So what do you need to consider in automatic mode:
|
262 | * - Brightness and ColorTemperature characteristics MUST be set up. Hue and Saturation may be added for color support.
|
263 | * - Color temperature will be updated all 60 seconds by calling the SET handler of the ColorTemperature characteristic.
|
264 | * So every transition behaves like a regular write to the ColorTemperature characteristic.
|
265 | * - Every transition step is dependent on the current brightness value. Try to keep the internal cache updated
|
266 | * as the controller won't call the GET handler every 60 seconds.
|
267 | * (The cached brightness value is updated on SET/GET operations or by manually calling {@link Characteristic.updateValue}
|
268 | * on the brightness characteristic).
|
269 | * - Detecting changes on the lightbulb side:
|
270 | * Any manual change to ColorTemperature or Hue/Saturation is considered as a signal to turn AdaptiveLighting off.
|
271 | * In order to notify the AdaptiveLightingController of such an event happening OUTSIDE of HomeKit
|
272 | * you must call {@link disableAdaptiveLighting} manually!
|
273 | * - Be aware that even when the light is turned off the transition will continue to call the SET handler
|
274 | * of the ColorTemperature characteristic.
|
275 | * - When using Hue/Saturation:
|
276 | * When using Hue/Saturation in combination with the ColorTemperature characteristic you need to update the
|
277 | * respective other in a particular way depending on if being in "color mode" or "color temperature mode".
|
278 | * When a write happens to Hue/Saturation characteristic in is advised to set the internal value of the
|
279 | * ColorTemperature to the minimal (NOT RAISING an event).
|
280 | * When a write happens to the ColorTemperature characteristic just MUST convert to a proper representation
|
281 | * in hue and saturation values, with RAISING an event.
|
282 | * As noted above you MUST NOT call the {@link Characteristic.setValue} method for this, as this will be considered
|
283 | * a write to the characteristic and will turn off AdaptiveLighting. Instead, you should use
|
284 | * {@link Characteristic.updateValue} for this.
|
285 | * You can and SHOULD use the supplied utility method {@link ColorUtils.colorTemperatureToHueAndSaturation}
|
286 | * for converting mired to hue and saturation values.
|
287 | *
|
288 | *
|
289 | * <b>MANUAL mode:</b>
|
290 | *
|
291 | * Manual mode is recommended for any accessories which support transitions natively on the devices end.
|
292 | * Like for example ZigBee lights which support sending transitions directly to the lightbulb which
|
293 | * then get executed ON the lightbulb itself reducing unnecessary network traffic.
|
294 | * Here is a quick overview what you have to consider to successfully implement AdaptiveLighting support.
|
295 | * The AdaptiveLightingController will also in manual mode do all the setup procedure.
|
296 | * It will also save the transition schedule to disk to keep AdaptiveLighting enabled across reboots.
|
297 | * The "only" thing you have to do yourself is handling the actual transitions, check that event notifications
|
298 | * are only sent in the defined interval threshold, adjust the color temperature when brightness is changed
|
299 | * and signal that Adaptive Lighting should be disabled if ColorTemperature, Hue or Saturation is changed manually.
|
300 | *
|
301 | * First step is to setup up an event handler for the {@link AdaptiveLightingControllerEvents.UPDATE}, which is called
|
302 | * when AdaptiveLighting is enabled, the HomeHub updates the schedule for the next 24 hours or AdaptiveLighting
|
303 | * is restored from disk on startup.
|
304 | * In the event handler you can get the current schedule via {@link AdaptiveLightingController.getAdaptiveLightingTransitionCurve},
|
305 | * retrieve current intervals like {@link AdaptiveLightingController.getAdaptiveLightingUpdateInterval} or
|
306 | * {@link AdaptiveLightingController.getAdaptiveLightingNotifyIntervalThreshold} and get the date in epoch millis
|
307 | * when the current transition curve started using {@link AdaptiveLightingController.getAdaptiveLightingStartTimeOfTransition}.
|
308 | * Additionally {@link AdaptiveLightingController.getAdaptiveLightingBrightnessMultiplierRange} can be used
|
309 | * to get the valid range for the brightness value to calculate the brightness adjustment factor.
|
310 | * The method {@link AdaptiveLightingController.isAdaptiveLightingActive} can be used to check if AdaptiveLighting is enabled.
|
311 | * Besides, actually running the transition (see {@link AdaptiveLightingTransitionCurveEntry}) you must correctly update
|
312 | * the color temperature when the brightness of the lightbulb changes (see {@link AdaptiveLightingTransitionCurveEntry.brightnessAdjustmentFactor}),
|
313 | * and signal when AdaptiveLighting got disabled by calling {@link AdaptiveLightingController.disableAdaptiveLighting}
|
314 | * when ColorTemperature, Hue or Saturation where changed manually.
|
315 | * Lastly you should set up a event handler for the {@link AdaptiveLightingControllerEvents.DISABLED} event.
|
316 | * In yet unknown circumstances HomeKit may also send a dedicated disable command via the control point characteristic.
|
317 | * Be prepared to handle that.
|
318 | *
|
319 | * @group Adaptive Lighting
|
320 | */
|
321 | export declare class AdaptiveLightingController extends EventEmitter implements SerializableController<ControllerServiceMap, SerializedAdaptiveLightingControllerState> {
|
322 | private stateChangeDelegate?;
|
323 | private readonly lightbulb;
|
324 | private readonly mode;
|
325 | private readonly customTemperatureAdjustment;
|
326 | private readonly adjustmentFactorChangedListener;
|
327 | private readonly characteristicManualWrittenChangeListener;
|
328 | private supportedTransitionConfiguration?;
|
329 | private transitionControl?;
|
330 | private activeTransitionCount?;
|
331 | private colorTemperatureCharacteristic?;
|
332 | private brightnessCharacteristic?;
|
333 | private saturationCharacteristic?;
|
334 | private hueCharacteristic?;
|
335 | private activeTransition?;
|
336 | private didRunFirstInitializationStep;
|
337 | private updateTimeout?;
|
338 | private lastTransitionPointInfo?;
|
339 | private lastEventNotificationSent;
|
340 | private lastNotifiedTemperatureValue;
|
341 | private lastNotifiedSaturationValue;
|
342 | private lastNotifiedHueValue;
|
343 | /**
|
344 | * Creates a new instance of the AdaptiveLightingController.
|
345 | * Refer to the {@link AdaptiveLightingController} documentation on how to use it.
|
346 | *
|
347 | * @param service - The lightbulb to which Adaptive Lighting support should be added.
|
348 | * @param options - Optional options to define the operating mode (automatic vs manual).
|
349 | */
|
350 | constructor(service: Lightbulb, options?: AdaptiveLightingOptions);
|
351 | /**
|
352 | * @private
|
353 | */
|
354 | controllerId(): ControllerIdentifier;
|
355 | /**
|
356 | * Returns if a Adaptive Lighting transition is currently active.
|
357 | */
|
358 | isAdaptiveLightingActive(): boolean;
|
359 | /**
|
360 | * This method can be called to manually disable the current active Adaptive Lighting transition.
|
361 | * When using {@link AdaptiveLightingControllerMode.AUTOMATIC} you won't need to call this method.
|
362 | * In {this method when Adaptive Lighting should be disabled.
AdaptiveLightingControllerMode.MANUAL} you must call |
363 | * This is the case when the user manually changes the value of Hue, Saturation or ColorTemperature characteristics
|
364 | * (or if any of those values is changed by physical interaction with the lightbulb).
|
365 | */
|
366 | disableAdaptiveLighting(): void;
|
367 | /**
|
368 | * Returns the time where the current transition curve was started in epoch time millis.
|
369 | * A transition curves is active for 24 hours typically and is renewed every 24 hours by a HomeHub.
|
370 | * Additionally see {@link getAdaptiveLightingTimeOffset}.
|
371 | */
|
372 | getAdaptiveLightingStartTimeOfTransition(): number;
|
373 | /**
|
374 | * It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller
|
375 | * who set up the transition schedule.
|
376 | * Thus we record the delta between our current time and the the time send with the setup request.
|
377 | * <code>timeOffset</code> is defined as <code>Date.now() - getAdaptiveLightingStartTimeOfTransition();</code>.
|
378 | * So in the case were we actually have a correct local time, it most likely will be positive (due to network latency).
|
379 | * But of course it can also be negative.
|
380 | */
|
381 | getAdaptiveLightingTimeOffset(): number;
|
382 | getAdaptiveLightingTransitionCurve(): AdaptiveLightingTransitionCurveEntry[];
|
383 | getAdaptiveLightingBrightnessMultiplierRange(): BrightnessAdjustmentMultiplierRange;
|
384 | /**
|
385 | * This method returns the interval (in milliseconds) in which the light should update its internal color temperature
|
386 | * (aka changes it physical color).
|
387 | * A lightbulb should ideally change this also when turned of in oder to have a smooth transition when turning the light on.
|
388 | *
|
389 | * Typically this evaluates to 60000 milliseconds (60 seconds).
|
390 | */
|
391 | getAdaptiveLightingUpdateInterval(): number;
|
392 | /**
|
393 | * Returns the minimum interval threshold (in milliseconds) a accessory may notify HomeKit controllers about a new
|
394 | * color temperature value via event notifications (what happens when you call {@link Characteristic.updateValue}).
|
395 | * Meaning the accessory should only send event notifications to subscribed HomeKit controllers at the specified interval.
|
396 | *
|
397 | * Typically this evaluates to 600000 milliseconds (10 minutes).
|
398 | */
|
399 | getAdaptiveLightingNotifyIntervalThreshold(): number;
|
400 | private handleActiveTransitionUpdated;
|
401 | private handleAdaptiveLightingEnabled;
|
402 | private handleAdaptiveLightingDisabled;
|
403 | private handleAdjustmentFactorChanged;
|
404 | /**
|
405 | * This method is called when a change happens to the Hue/Saturation or ColorTemperature characteristic.
|
406 | * When such a write happens (caused by the user changing the color/temperature) Adaptive Lighting must be disabled.
|
407 | *
|
408 | * @param change
|
409 | */
|
410 | private handleCharacteristicManualWritten;
|
411 | /**
|
412 | * Retrieve the {@link AdaptiveLightingTransitionPoint} for the current timestamp.
|
413 | * Returns undefined if the current transition schedule reached its end.
|
414 | */
|
415 | getCurrentAdaptiveLightingTransitionPoint(): AdaptiveLightingTransitionPoint | undefined;
|
416 | private scheduleNextUpdate;
|
417 | /**
|
418 | * @private
|
419 | */
|
420 | constructServices(): ControllerServiceMap;
|
421 | /**
|
422 | * @private
|
423 | */
|
424 | initWithServices(serviceMap: ControllerServiceMap): void | ControllerServiceMap;
|
425 | /**
|
426 | * @private
|
427 | */
|
428 | configureServices(): void;
|
429 | /**
|
430 | * @private
|
431 | */
|
432 | handleControllerRemoved(): void;
|
433 | /**
|
434 | * @private
|
435 | */
|
436 | handleFactoryReset(): void;
|
437 | /**
|
438 | * @private
|
439 | */
|
440 | serialize(): SerializedAdaptiveLightingControllerState | undefined;
|
441 | /**
|
442 | * @private
|
443 | */
|
444 | deserialize(serialized: SerializedAdaptiveLightingControllerState): void;
|
445 | /**
|
446 | * @private
|
447 | */
|
448 | setupStateChangeDelegate(delegate?: StateChangeDelegate): void;
|
449 | private handleSupportedTransitionConfigurationRead;
|
450 | private buildTransitionControlResponseBuffer;
|
451 | private handleTransitionControlWrite;
|
452 | private handleTransitionControlReadTransition;
|
453 | private handleTransitionControlUpdateTransition;
|
454 | }
|
455 | //# sourceMappingURL=AdaptiveLightingController.d.ts.map |
\ | No newline at end of file |