UNPKG

22.7 kBTypeScriptView Raw
1import { EventEmitter } from "events";
2import { Lightbulb } from "../definitions";
3import { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from "./Controller";
4/**
5 * @group Adaptive Lighting
6 */
7export 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 */
60export 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 */
72export 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 */
123export interface BrightnessAdjustmentMultiplierRange {
124 minBrightnessValue: number;
125 maxBrightnessValue: number;
126}
127/**
128 * @group Adaptive Lighting
129 */
130export 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 */
152export 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 */
166export 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 */
184export 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 */
195export 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 */
220export 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 */
321export 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 {@link AdaptiveLightingControllerMode.MANUAL} you must call this method when Adaptive Lighting should be disabled.
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