import { EventEmitter } from "events"; import { Lightbulb } from "../definitions"; import { ControllerIdentifier, ControllerServiceMap, SerializableController, StateChangeDelegate } from "./Controller"; /** * @group Adaptive Lighting */ export interface ActiveAdaptiveLightingTransition { /** * The instance id for the characteristic for which this transition applies to (aka the ColorTemperature characteristic). */ iid: number; /** * Start of the transition in epoch time millis (as sent from the HomeKit controller). * Additionally see {@link timeMillisOffset}. */ transitionStartMillis: number; /** * It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller * who set up the transition schedule. * Thus we record the delta between our current time and the the time sent with the setup request. * timeMillisOffset is defined as Date.now() - transitionStartMillis;. * So in the case were we actually have a correct local time, it most likely will be positive (due to network latency). * But of course it can also be negative. */ timeMillisOffset: number; /** * Value is the same for ALL control write requests I have seen (even on other homes). * @private */ transitionId: string; /** * Start of transition in milliseconds from 2001-01-01 00:00:00; unsigned 64 bit LE integer * @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 */ transitionStartBuffer: string; /** * Hex string of 8 bytes. Some kind of id (?). Sometimes it isn't supplied. Don't know the use for that. * @private */ id3?: string; transitionCurve: AdaptiveLightingTransitionCurveEntry[]; brightnessCharacteristicIID: number; brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange; /** * Interval in milliseconds specifies how often the accessory should update the color temperature (internally). * Typically this is 60000 aka 60 seconds aka 1 minute. * Note {@link notifyIntervalThreshold} */ updateInterval: number; /** * Defines the interval in milliseconds on how often the accessory may send even notifications * to subscribed HomeKit controllers (aka call {@link Characteristic.updateValue}. * Typically this is 600000 aka 600 seconds aka 10 minutes or 300000 aka 300 seconds aka 5 minutes. */ notifyIntervalThreshold: number; } /** * @group Adaptive Lighting */ export interface AdaptiveLightingTransitionPoint { /** * This is the time offset from the transition start to the {@link lowerBound}. */ lowerBoundTimeOffset: number; transitionOffset: number; lowerBound: AdaptiveLightingTransitionCurveEntry; upperBound: AdaptiveLightingTransitionCurveEntry; } /** * @group Adaptive Lighting */ export interface AdaptiveLightingTransitionCurveEntry { /** * The color temperature in mired. */ temperature: number; /** * The color temperature actually set to the color temperature characteristic is dependent * on the current brightness value of the lightbulb. * This means you will always need to query the current brightness when updating the color temperature * for the next transition step. * Additionally you will also need to correct the color temperature when the end user changes the * brightness of the Lightbulb. * * The brightnessAdjustmentFactor is always a negative floating point value. * * To calculate the resulting color temperature you will need to do the following. * * In short: temperature + brightnessAdjustmentFactor * currentBrightness * * Complete example: * ```js * const temperature = ...; // next transition value, the above property * // below query the current brightness while staying the the min/max brightness range (typically between 10-100 percent) * const currentBrightness = Math.max(minBrightnessValue, Math.min(maxBrightnessValue, CHARACTERISTIC_BRIGHTNESS_VALUE)); * * // as both temperature and brightnessAdjustmentFactor are floating point values it is advised to round to the next integer * const resultTemperature = Math.round(temperature + brightnessAdjustmentFactor * currentBrightness); * ``` */ brightnessAdjustmentFactor: number; /** * The duration in milliseconds this exact temperature value stays the same. * When we transition to to the temperature value represented by this entry, it stays for the specified * duration on the exact same value (with respect to brightness adjustment) until we transition * to the next entry (see {@link transitionTime}). */ duration?: number; /** * The time in milliseconds the color temperature should transition from the previous * entry to this one. * For example if we got the two values A and B, with A.temperature = 300 and B.temperature = 400 and * for the current time we are at temperature value 300. Then we need to transition smoothly * within the B.transitionTime to the B.temperature value. * If this is the first entry in the Curve (this value is probably zero) and is the offset to the transitionStartMillis * (the Date/Time were this transition curve was set up). */ transitionTime: number; } /** * @group Adaptive Lighting */ export interface BrightnessAdjustmentMultiplierRange { minBrightnessValue: number; maxBrightnessValue: number; } /** * @group Adaptive Lighting */ export interface AdaptiveLightingOptions { /** * Defines how the controller will operate. * You can choose between automatic and manual mode. * See {@link AdaptiveLightingControllerMode}. */ controllerMode?: AdaptiveLightingControllerMode; /** * Defines a custom temperature adjustment factor. * * This can be used to define a linear deviation from the HomeKit Controller defined * ColorTemperature schedule. * * For example supplying a value of `-10` will reduce the ColorTemperature, which is * calculated from the transition schedule, by 10 mired for every change. */ customTemperatureAdjustment?: number; } /** * Defines in which mode the {@link AdaptiveLightingController} will operate in. * @group Adaptive Lighting */ export declare const enum AdaptiveLightingControllerMode { /** * In automatic mode pretty much everything from setup to transition scheduling is done by the controller. */ AUTOMATIC = 1, /** * In manual mode setup is done by the controller but the actual transition must be done by the user. * This is useful for lights which natively support transitions. */ MANUAL = 2 } /** * @group Adaptive Lighting */ export declare const enum AdaptiveLightingControllerEvents { /** * This event is called once a HomeKit controller enables Adaptive Lighting * or a HomeHub sends an updated transition schedule for the next 24 hours. * This is also called on startup when AdaptiveLighting was previously enabled. */ UPDATE = "update", /** * In yet unknown circumstances HomeKit may also send a dedicated disable command * via the control point characteristic. You may want to handle that in manual mode as well. * The current transition will still be associated with the controller object when this event is called. */ DISABLED = "disable" } /** * @group Adaptive Lighting * see {@link ActiveAdaptiveLightingTransition}. */ export interface AdaptiveLightingControllerUpdate { transitionStartMillis: number; timeMillisOffset: number; transitionCurve: AdaptiveLightingTransitionCurveEntry[]; brightnessAdjustmentRange: BrightnessAdjustmentMultiplierRange; updateInterval: number; notifyIntervalThreshold: number; } /** * @group Adaptive Lighting */ export declare interface AdaptiveLightingController { /** * See {@link AdaptiveLightingControllerEvents.UPDATE} * Also see {@link AdaptiveLightingControllerUpdate} * * @param event * @param listener */ on(event: "update", listener: (update: AdaptiveLightingControllerUpdate) => void): this; /** * See {@link AdaptiveLightingControllerEvents.DISABLED} * * @param event * @param listener */ on(event: "disable", listener: () => void): this; /** * See {@link AdaptiveLightingControllerUpdate} */ emit(event: "update", update: AdaptiveLightingControllerUpdate): boolean; emit(event: "disable"): boolean; } /** * @group Adaptive Lighting */ export interface SerializedAdaptiveLightingControllerState { activeTransition: ActiveAdaptiveLightingTransition; } /** * This class allows adding Adaptive Lighting support to Lightbulb services. * The Lightbulb service MUST have the {@link Characteristic.ColorTemperature} characteristic AND * the {@link Characteristic.Brightness} characteristic added. * The light may also expose {@link Characteristic.Hue} and {@link Characteristic.Saturation} characteristics * (though additional work is required to keep them in sync with the color temperature characteristic. see below) * * How Adaptive Lighting works: * When enabling AdaptiveLighting the iDevice will send a transition schedule for the next 24 hours. * This schedule will be renewed all 24 hours by a HomeHub in your home * (updating the schedule according to your current day/night situation). * Once enabled the lightbulb will execute the provided transitions. The color temperature value set is always * dependent on the current brightness value. Meaning brighter light will be colder and darker light will be warmer. * HomeKit considers Adaptive Lighting to be disabled as soon a write happens to either the * Hue/Saturation or the ColorTemperature characteristics. * The AdaptiveLighting state must persist across reboots. * * The AdaptiveLightingController can be operated in two modes: {@link AdaptiveLightingControllerMode.AUTOMATIC} and * {@link AdaptiveLightingControllerMode.MANUAL} with AUTOMATIC being the default. * The goal would be that the color transition is done DIRECTLY on the light itself, thus not creating any * additional/heavy traffic on the network. * So if your light hardware/API supports transitions please go the extra mile and use MANUAL mode. * * * * Below is an overview what you need to or consider when enabling AdaptiveLighting (categorized by mode). * The {@link AdaptiveLightingControllerMode} can be defined with the second constructor argument. * * AUTOMATIC (Default mode): * * This is the easiest mode to setup and needs less to no work form your side for AdaptiveLighting to work. * The AdaptiveLightingController will go through setup procedure with HomeKit and automatically update * the color temperature characteristic base on the current transition schedule. * It is also adjusting the color temperature when a write to the brightness characteristic happens. * Additionally, it will also handle turning off AdaptiveLighting, when it detects a write happening to the * ColorTemperature, Hue or Saturation characteristic (though it can only detect writes coming from HomeKit and * can't detect changes done to the physical devices directly! See below). * * So what do you need to consider in automatic mode: * - Brightness and ColorTemperature characteristics MUST be set up. Hue and Saturation may be added for color support. * - Color temperature will be updated all 60 seconds by calling the SET handler of the ColorTemperature characteristic. * So every transition behaves like a regular write to the ColorTemperature characteristic. * - Every transition step is dependent on the current brightness value. Try to keep the internal cache updated * as the controller won't call the GET handler every 60 seconds. * (The cached brightness value is updated on SET/GET operations or by manually calling {@link Characteristic.updateValue} * on the brightness characteristic). * - Detecting changes on the lightbulb side: * Any manual change to ColorTemperature or Hue/Saturation is considered as a signal to turn AdaptiveLighting off. * In order to notify the AdaptiveLightingController of such an event happening OUTSIDE of HomeKit * you must call {@link disableAdaptiveLighting} manually! * - Be aware that even when the light is turned off the transition will continue to call the SET handler * of the ColorTemperature characteristic. * - When using Hue/Saturation: * When using Hue/Saturation in combination with the ColorTemperature characteristic you need to update the * respective other in a particular way depending on if being in "color mode" or "color temperature mode". * When a write happens to Hue/Saturation characteristic in is advised to set the internal value of the * ColorTemperature to the minimal (NOT RAISING an event). * When a write happens to the ColorTemperature characteristic just MUST convert to a proper representation * in hue and saturation values, with RAISING an event. * As noted above you MUST NOT call the {@link Characteristic.setValue} method for this, as this will be considered * a write to the characteristic and will turn off AdaptiveLighting. Instead, you should use * {@link Characteristic.updateValue} for this. * You can and SHOULD use the supplied utility method {@link ColorUtils.colorTemperatureToHueAndSaturation} * for converting mired to hue and saturation values. * * * MANUAL mode: * * Manual mode is recommended for any accessories which support transitions natively on the devices end. * Like for example ZigBee lights which support sending transitions directly to the lightbulb which * then get executed ON the lightbulb itself reducing unnecessary network traffic. * Here is a quick overview what you have to consider to successfully implement AdaptiveLighting support. * The AdaptiveLightingController will also in manual mode do all the setup procedure. * It will also save the transition schedule to disk to keep AdaptiveLighting enabled across reboots. * The "only" thing you have to do yourself is handling the actual transitions, check that event notifications * are only sent in the defined interval threshold, adjust the color temperature when brightness is changed * and signal that Adaptive Lighting should be disabled if ColorTemperature, Hue or Saturation is changed manually. * * First step is to setup up an event handler for the {@link AdaptiveLightingControllerEvents.UPDATE}, which is called * when AdaptiveLighting is enabled, the HomeHub updates the schedule for the next 24 hours or AdaptiveLighting * is restored from disk on startup. * In the event handler you can get the current schedule via {@link AdaptiveLightingController.getAdaptiveLightingTransitionCurve}, * retrieve current intervals like {@link AdaptiveLightingController.getAdaptiveLightingUpdateInterval} or * {@link AdaptiveLightingController.getAdaptiveLightingNotifyIntervalThreshold} and get the date in epoch millis * when the current transition curve started using {@link AdaptiveLightingController.getAdaptiveLightingStartTimeOfTransition}. * Additionally {@link AdaptiveLightingController.getAdaptiveLightingBrightnessMultiplierRange} can be used * to get the valid range for the brightness value to calculate the brightness adjustment factor. * The method {@link AdaptiveLightingController.isAdaptiveLightingActive} can be used to check if AdaptiveLighting is enabled. * Besides, actually running the transition (see {@link AdaptiveLightingTransitionCurveEntry}) you must correctly update * the color temperature when the brightness of the lightbulb changes (see {@link AdaptiveLightingTransitionCurveEntry.brightnessAdjustmentFactor}), * and signal when AdaptiveLighting got disabled by calling {@link AdaptiveLightingController.disableAdaptiveLighting} * when ColorTemperature, Hue or Saturation where changed manually. * Lastly you should set up a event handler for the {@link AdaptiveLightingControllerEvents.DISABLED} event. * In yet unknown circumstances HomeKit may also send a dedicated disable command via the control point characteristic. * Be prepared to handle that. * * @group Adaptive Lighting */ export declare class AdaptiveLightingController extends EventEmitter implements SerializableController { private stateChangeDelegate?; private readonly lightbulb; private readonly mode; private readonly customTemperatureAdjustment; private readonly adjustmentFactorChangedListener; private readonly characteristicManualWrittenChangeListener; private supportedTransitionConfiguration?; private transitionControl?; private activeTransitionCount?; private colorTemperatureCharacteristic?; private brightnessCharacteristic?; private saturationCharacteristic?; private hueCharacteristic?; private activeTransition?; private didRunFirstInitializationStep; private updateTimeout?; private lastTransitionPointInfo?; private lastEventNotificationSent; private lastNotifiedTemperatureValue; private lastNotifiedSaturationValue; private lastNotifiedHueValue; /** * Creates a new instance of the AdaptiveLightingController. * Refer to the {@link AdaptiveLightingController} documentation on how to use it. * * @param service - The lightbulb to which Adaptive Lighting support should be added. * @param options - Optional options to define the operating mode (automatic vs manual). */ constructor(service: Lightbulb, options?: AdaptiveLightingOptions); /** * @private */ controllerId(): ControllerIdentifier; /** * Returns if a Adaptive Lighting transition is currently active. */ isAdaptiveLightingActive(): boolean; /** * This method can be called to manually disable the current active Adaptive Lighting transition. * When using {@link AdaptiveLightingControllerMode.AUTOMATIC} you won't need to call this method. * In {@link AdaptiveLightingControllerMode.MANUAL} you must call this method when Adaptive Lighting should be disabled. * This is the case when the user manually changes the value of Hue, Saturation or ColorTemperature characteristics * (or if any of those values is changed by physical interaction with the lightbulb). */ disableAdaptiveLighting(): void; /** * Returns the time where the current transition curve was started in epoch time millis. * A transition curves is active for 24 hours typically and is renewed every 24 hours by a HomeHub. * Additionally see {@link getAdaptiveLightingTimeOffset}. */ getAdaptiveLightingStartTimeOfTransition(): number; /** * It is not necessarily given, that we have the same time (or rather the correct time) as the HomeKit controller * who set up the transition schedule. * Thus we record the delta between our current time and the the time send with the setup request. * timeOffset is defined as Date.now() - getAdaptiveLightingStartTimeOfTransition();. * So in the case were we actually have a correct local time, it most likely will be positive (due to network latency). * But of course it can also be negative. */ getAdaptiveLightingTimeOffset(): number; getAdaptiveLightingTransitionCurve(): AdaptiveLightingTransitionCurveEntry[]; getAdaptiveLightingBrightnessMultiplierRange(): BrightnessAdjustmentMultiplierRange; /** * This method returns the interval (in milliseconds) in which the light should update its internal color temperature * (aka changes it physical color). * A lightbulb should ideally change this also when turned of in oder to have a smooth transition when turning the light on. * * Typically this evaluates to 60000 milliseconds (60 seconds). */ getAdaptiveLightingUpdateInterval(): number; /** * Returns the minimum interval threshold (in milliseconds) a accessory may notify HomeKit controllers about a new * color temperature value via event notifications (what happens when you call {@link Characteristic.updateValue}). * Meaning the accessory should only send event notifications to subscribed HomeKit controllers at the specified interval. * * Typically this evaluates to 600000 milliseconds (10 minutes). */ getAdaptiveLightingNotifyIntervalThreshold(): number; private handleActiveTransitionUpdated; private handleAdaptiveLightingEnabled; private handleAdaptiveLightingDisabled; private handleAdjustmentFactorChanged; /** * This method is called when a change happens to the Hue/Saturation or ColorTemperature characteristic. * When such a write happens (caused by the user changing the color/temperature) Adaptive Lighting must be disabled. * * @param change */ private handleCharacteristicManualWritten; /** * Retrieve the {@link AdaptiveLightingTransitionPoint} for the current timestamp. * Returns undefined if the current transition schedule reached its end. */ getCurrentAdaptiveLightingTransitionPoint(): AdaptiveLightingTransitionPoint | undefined; private scheduleNextUpdate; /** * @private */ constructServices(): ControllerServiceMap; /** * @private */ initWithServices(serviceMap: ControllerServiceMap): void | ControllerServiceMap; /** * @private */ configureServices(): void; /** * @private */ handleControllerRemoved(): void; /** * @private */ handleFactoryReset(): void; /** * @private */ serialize(): SerializedAdaptiveLightingControllerState | undefined; /** * @private */ deserialize(serialized: SerializedAdaptiveLightingControllerState): void; /** * @private */ setupStateChangeDelegate(delegate?: StateChangeDelegate): void; private handleSupportedTransitionConfigurationRead; private buildTransitionControlResponseBuffer; private handleTransitionControlWrite; private handleTransitionControlReadTransition; private handleTransitionControlUpdateTransition; } //# sourceMappingURL=AdaptiveLightingController.d.ts.map