1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.AdaptiveLightingController = exports.AdaptiveLightingControllerEvents = exports.AdaptiveLightingControllerMode = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
6 | const color_utils_1 = require("../util/color-utils");
|
7 | const hapStatusError_1 = require("../util/hapStatusError");
|
8 | const time_1 = require("../util/time");
|
9 | const uuid = tslib_1.__importStar(require("../util/uuid"));
|
10 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
11 | const events_1 = require("events");
|
12 | const Characteristic_1 = require("../Characteristic");
|
13 | const tlv = tslib_1.__importStar(require("../util/tlv"));
|
14 | const debug = (0, debug_1.default)("HAP-NodeJS:Controller:TransitionControl");
|
15 | var SupportedCharacteristicValueTransitionConfigurationsTypes;
|
16 | (function (SupportedCharacteristicValueTransitionConfigurationsTypes) {
|
17 | SupportedCharacteristicValueTransitionConfigurationsTypes[SupportedCharacteristicValueTransitionConfigurationsTypes["SUPPORTED_TRANSITION_CONFIGURATION"] = 1] = "SUPPORTED_TRANSITION_CONFIGURATION";
|
18 | })(SupportedCharacteristicValueTransitionConfigurationsTypes || (SupportedCharacteristicValueTransitionConfigurationsTypes = {}));
|
19 | var SupportedValueTransitionConfigurationTypes;
|
20 | (function (SupportedValueTransitionConfigurationTypes) {
|
21 | SupportedValueTransitionConfigurationTypes[SupportedValueTransitionConfigurationTypes["CHARACTERISTIC_IID"] = 1] = "CHARACTERISTIC_IID";
|
22 | SupportedValueTransitionConfigurationTypes[SupportedValueTransitionConfigurationTypes["TRANSITION_TYPE"] = 2] = "TRANSITION_TYPE";
|
23 | })(SupportedValueTransitionConfigurationTypes || (SupportedValueTransitionConfigurationTypes = {}));
|
24 | var TransitionType;
|
25 | (function (TransitionType) {
|
26 | TransitionType[TransitionType["BRIGHTNESS"] = 1] = "BRIGHTNESS";
|
27 | TransitionType[TransitionType["COLOR_TEMPERATURE"] = 2] = "COLOR_TEMPERATURE";
|
28 | })(TransitionType || (TransitionType = {}));
|
29 | var TransitionControlTypes;
|
30 | (function (TransitionControlTypes) {
|
31 | TransitionControlTypes[TransitionControlTypes["READ_CURRENT_VALUE_TRANSITION_CONFIGURATION"] = 1] = "READ_CURRENT_VALUE_TRANSITION_CONFIGURATION";
|
32 | TransitionControlTypes[TransitionControlTypes["UPDATE_VALUE_TRANSITION_CONFIGURATION"] = 2] = "UPDATE_VALUE_TRANSITION_CONFIGURATION";
|
33 | })(TransitionControlTypes || (TransitionControlTypes = {}));
|
34 | var ReadValueTransitionConfiguration;
|
35 | (function (ReadValueTransitionConfiguration) {
|
36 | ReadValueTransitionConfiguration[ReadValueTransitionConfiguration["CHARACTERISTIC_IID"] = 1] = "CHARACTERISTIC_IID";
|
37 | })(ReadValueTransitionConfiguration || (ReadValueTransitionConfiguration = {}));
|
38 | var UpdateValueTransitionConfigurationsTypes;
|
39 | (function (UpdateValueTransitionConfigurationsTypes) {
|
40 | UpdateValueTransitionConfigurationsTypes[UpdateValueTransitionConfigurationsTypes["VALUE_TRANSITION_CONFIGURATION"] = 1] = "VALUE_TRANSITION_CONFIGURATION";
|
41 | })(UpdateValueTransitionConfigurationsTypes || (UpdateValueTransitionConfigurationsTypes = {}));
|
42 | var ValueTransitionConfigurationTypes;
|
43 | (function (ValueTransitionConfigurationTypes) {
|
44 |
|
45 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["CHARACTERISTIC_IID"] = 1] = "CHARACTERISTIC_IID";
|
46 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["TRANSITION_PARAMETERS"] = 2] = "TRANSITION_PARAMETERS";
|
47 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["UNKNOWN_3"] = 3] = "UNKNOWN_3";
|
48 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["UNKNOWN_4"] = 4] = "UNKNOWN_4";
|
49 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["TRANSITION_CURVE_CONFIGURATION"] = 5] = "TRANSITION_CURVE_CONFIGURATION";
|
50 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["UPDATE_INTERVAL"] = 6] = "UPDATE_INTERVAL";
|
51 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["UNKNOWN_7"] = 7] = "UNKNOWN_7";
|
52 | ValueTransitionConfigurationTypes[ValueTransitionConfigurationTypes["NOTIFY_INTERVAL_THRESHOLD"] = 8] = "NOTIFY_INTERVAL_THRESHOLD";
|
53 | })(ValueTransitionConfigurationTypes || (ValueTransitionConfigurationTypes = {}));
|
54 | var ValueTransitionParametersTypes;
|
55 | (function (ValueTransitionParametersTypes) {
|
56 | ValueTransitionParametersTypes[ValueTransitionParametersTypes["TRANSITION_ID"] = 1] = "TRANSITION_ID";
|
57 | ValueTransitionParametersTypes[ValueTransitionParametersTypes["START_TIME"] = 2] = "START_TIME";
|
58 | ValueTransitionParametersTypes[ValueTransitionParametersTypes["UNKNOWN_3"] = 3] = "UNKNOWN_3";
|
59 | })(ValueTransitionParametersTypes || (ValueTransitionParametersTypes = {}));
|
60 | var TransitionCurveConfigurationTypes;
|
61 | (function (TransitionCurveConfigurationTypes) {
|
62 | TransitionCurveConfigurationTypes[TransitionCurveConfigurationTypes["TRANSITION_ENTRY"] = 1] = "TRANSITION_ENTRY";
|
63 | TransitionCurveConfigurationTypes[TransitionCurveConfigurationTypes["ADJUSTMENT_CHARACTERISTIC_IID"] = 2] = "ADJUSTMENT_CHARACTERISTIC_IID";
|
64 | TransitionCurveConfigurationTypes[TransitionCurveConfigurationTypes["ADJUSTMENT_MULTIPLIER_RANGE"] = 3] = "ADJUSTMENT_MULTIPLIER_RANGE";
|
65 | })(TransitionCurveConfigurationTypes || (TransitionCurveConfigurationTypes = {}));
|
66 | var TransitionEntryTypes;
|
67 | (function (TransitionEntryTypes) {
|
68 | TransitionEntryTypes[TransitionEntryTypes["ADJUSTMENT_FACTOR"] = 1] = "ADJUSTMENT_FACTOR";
|
69 | TransitionEntryTypes[TransitionEntryTypes["VALUE"] = 2] = "VALUE";
|
70 | TransitionEntryTypes[TransitionEntryTypes["TRANSITION_OFFSET"] = 3] = "TRANSITION_OFFSET";
|
71 | TransitionEntryTypes[TransitionEntryTypes["DURATION"] = 4] = "DURATION";
|
72 | })(TransitionEntryTypes || (TransitionEntryTypes = {}));
|
73 | var TransitionAdjustmentMultiplierRange;
|
74 | (function (TransitionAdjustmentMultiplierRange) {
|
75 | TransitionAdjustmentMultiplierRange[TransitionAdjustmentMultiplierRange["MINIMUM_ADJUSTMENT_MULTIPLIER"] = 1] = "MINIMUM_ADJUSTMENT_MULTIPLIER";
|
76 | TransitionAdjustmentMultiplierRange[TransitionAdjustmentMultiplierRange["MAXIMUM_ADJUSTMENT_MULTIPLIER"] = 2] = "MAXIMUM_ADJUSTMENT_MULTIPLIER";
|
77 | })(TransitionAdjustmentMultiplierRange || (TransitionAdjustmentMultiplierRange = {}));
|
78 | var ValueTransitionConfigurationResponseTypes;
|
79 | (function (ValueTransitionConfigurationResponseTypes) {
|
80 | ValueTransitionConfigurationResponseTypes[ValueTransitionConfigurationResponseTypes["VALUE_CONFIGURATION_STATUS"] = 1] = "VALUE_CONFIGURATION_STATUS";
|
81 | })(ValueTransitionConfigurationResponseTypes || (ValueTransitionConfigurationResponseTypes = {}));
|
82 | var ValueTransitionConfigurationStatusTypes;
|
83 | (function (ValueTransitionConfigurationStatusTypes) {
|
84 | ValueTransitionConfigurationStatusTypes[ValueTransitionConfigurationStatusTypes["CHARACTERISTIC_IID"] = 1] = "CHARACTERISTIC_IID";
|
85 | ValueTransitionConfigurationStatusTypes[ValueTransitionConfigurationStatusTypes["TRANSITION_PARAMETERS"] = 2] = "TRANSITION_PARAMETERS";
|
86 | ValueTransitionConfigurationStatusTypes[ValueTransitionConfigurationStatusTypes["TIME_SINCE_START"] = 3] = "TIME_SINCE_START";
|
87 | })(ValueTransitionConfigurationStatusTypes || (ValueTransitionConfigurationStatusTypes = {}));
|
88 |
|
89 | function isAdaptiveLightingContext(context) {
|
90 | return context && "controller" in context;
|
91 | }
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | var AdaptiveLightingControllerMode;
|
97 | (function (AdaptiveLightingControllerMode) {
|
98 | |
99 |
|
100 |
|
101 | AdaptiveLightingControllerMode[AdaptiveLightingControllerMode["AUTOMATIC"] = 1] = "AUTOMATIC";
|
102 | |
103 |
|
104 |
|
105 |
|
106 | AdaptiveLightingControllerMode[AdaptiveLightingControllerMode["MANUAL"] = 2] = "MANUAL";
|
107 | })(AdaptiveLightingControllerMode || (exports.AdaptiveLightingControllerMode = AdaptiveLightingControllerMode = {}));
|
108 |
|
109 |
|
110 |
|
111 | var AdaptiveLightingControllerEvents;
|
112 | (function (AdaptiveLightingControllerEvents) {
|
113 | |
114 |
|
115 |
|
116 |
|
117 |
|
118 | AdaptiveLightingControllerEvents["UPDATE"] = "update";
|
119 | |
120 |
|
121 |
|
122 |
|
123 |
|
124 | AdaptiveLightingControllerEvents["DISABLED"] = "disable";
|
125 | })(AdaptiveLightingControllerEvents || (exports.AdaptiveLightingControllerEvents = AdaptiveLightingControllerEvents = {}));
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | class AdaptiveLightingController extends events_1.EventEmitter {
|
226 | stateChangeDelegate;
|
227 | lightbulb;
|
228 | mode;
|
229 | customTemperatureAdjustment;
|
230 | adjustmentFactorChangedListener;
|
231 | characteristicManualWrittenChangeListener;
|
232 | supportedTransitionConfiguration;
|
233 | transitionControl;
|
234 | activeTransitionCount;
|
235 | colorTemperatureCharacteristic;
|
236 | brightnessCharacteristic;
|
237 | saturationCharacteristic;
|
238 | hueCharacteristic;
|
239 | activeTransition;
|
240 | didRunFirstInitializationStep = false;
|
241 | updateTimeout;
|
242 | lastTransitionPointInfo;
|
243 | lastEventNotificationSent = 0;
|
244 | lastNotifiedTemperatureValue = 0;
|
245 | lastNotifiedSaturationValue = 0;
|
246 | lastNotifiedHueValue = 0;
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | constructor(service, options) {
|
255 | super();
|
256 | this.lightbulb = service;
|
257 | this.mode = options?.controllerMode ?? 1 ;
|
258 | this.customTemperatureAdjustment = options?.customTemperatureAdjustment ?? 0;
|
259 | (0, assert_1.default)(this.lightbulb.testCharacteristic(Characteristic_1.Characteristic.ColorTemperature), "Lightbulb must have the ColorTemperature characteristic added!");
|
260 | (0, assert_1.default)(this.lightbulb.testCharacteristic(Characteristic_1.Characteristic.Brightness), "Lightbulb must have the Brightness characteristic added!");
|
261 | this.adjustmentFactorChangedListener = this.handleAdjustmentFactorChanged.bind(this);
|
262 | this.characteristicManualWrittenChangeListener = this.handleCharacteristicManualWritten.bind(this);
|
263 | }
|
264 | |
265 |
|
266 |
|
267 | controllerId() {
|
268 | return "characteristic-transition" + "-" + this.lightbulb.getServiceId();
|
269 | }
|
270 |
|
271 | |
272 |
|
273 |
|
274 | isAdaptiveLightingActive() {
|
275 | return !!this.activeTransition;
|
276 | }
|
277 | |
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 | disableAdaptiveLighting() {
|
285 | if (this.updateTimeout) {
|
286 | clearTimeout(this.updateTimeout);
|
287 | this.updateTimeout = undefined;
|
288 | }
|
289 | if (this.activeTransition) {
|
290 | this.colorTemperatureCharacteristic?.removeListener("change" , this.characteristicManualWrittenChangeListener);
|
291 | this.brightnessCharacteristic?.removeListener("change" , this.adjustmentFactorChangedListener);
|
292 | this.hueCharacteristic?.removeListener("change" , this.characteristicManualWrittenChangeListener);
|
293 | this.saturationCharacteristic?.removeListener("change" , this.characteristicManualWrittenChangeListener);
|
294 | this.activeTransition = undefined;
|
295 | this.stateChangeDelegate?.();
|
296 | }
|
297 | this.colorTemperatureCharacteristic = undefined;
|
298 | this.brightnessCharacteristic = undefined;
|
299 | this.hueCharacteristic = undefined;
|
300 | this.saturationCharacteristic = undefined;
|
301 | this.lastTransitionPointInfo = undefined;
|
302 | this.lastEventNotificationSent = 0;
|
303 | this.lastNotifiedTemperatureValue = 0;
|
304 | this.lastNotifiedSaturationValue = 0;
|
305 | this.lastNotifiedHueValue = 0;
|
306 | this.didRunFirstInitializationStep = false;
|
307 | this.activeTransitionCount?.sendEventNotification(0);
|
308 | debug("[%s] Disabling adaptive lighting", this.lightbulb.displayName);
|
309 | }
|
310 | |
311 |
|
312 |
|
313 |
|
314 |
|
315 | getAdaptiveLightingStartTimeOfTransition() {
|
316 | if (!this.activeTransition) {
|
317 | throw new Error("There is no active transition!");
|
318 | }
|
319 | return this.activeTransition.transitionStartMillis;
|
320 | }
|
321 | |
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | getAdaptiveLightingTimeOffset() {
|
330 | if (!this.activeTransition) {
|
331 | throw new Error("There is no active transition!");
|
332 | }
|
333 | return this.activeTransition.timeMillisOffset;
|
334 | }
|
335 | getAdaptiveLightingTransitionCurve() {
|
336 | if (!this.activeTransition) {
|
337 | throw new Error("There is no active transition!");
|
338 | }
|
339 | return this.activeTransition.transitionCurve;
|
340 | }
|
341 | getAdaptiveLightingBrightnessMultiplierRange() {
|
342 | if (!this.activeTransition) {
|
343 | throw new Error("There is no active transition!");
|
344 | }
|
345 | return this.activeTransition.brightnessAdjustmentRange;
|
346 | }
|
347 | |
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | getAdaptiveLightingUpdateInterval() {
|
355 | if (!this.activeTransition) {
|
356 | throw new Error("There is no active transition!");
|
357 | }
|
358 | return this.activeTransition.updateInterval;
|
359 | }
|
360 | |
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | getAdaptiveLightingNotifyIntervalThreshold() {
|
368 | if (!this.activeTransition) {
|
369 | throw new Error("There is no active transition!");
|
370 | }
|
371 | return this.activeTransition.notifyIntervalThreshold;
|
372 | }
|
373 |
|
374 | handleActiveTransitionUpdated(calledFromDeserializer = false) {
|
375 | if (this.activeTransitionCount) {
|
376 | if (!calledFromDeserializer) {
|
377 | this.activeTransitionCount.sendEventNotification(1);
|
378 | }
|
379 | else {
|
380 | this.activeTransitionCount.value = 1;
|
381 | }
|
382 | }
|
383 | if (this.mode === 1 ) {
|
384 | this.scheduleNextUpdate();
|
385 | }
|
386 | else if (this.mode === 2 ) {
|
387 | if (!this.activeTransition) {
|
388 | throw new Error("There is no active transition!");
|
389 | }
|
390 | const update = {
|
391 | transitionStartMillis: this.activeTransition.transitionStartMillis,
|
392 | timeMillisOffset: this.activeTransition.timeMillisOffset,
|
393 | transitionCurve: this.activeTransition.transitionCurve,
|
394 | brightnessAdjustmentRange: this.activeTransition.brightnessAdjustmentRange,
|
395 | updateInterval: this.activeTransition.updateInterval,
|
396 | notifyIntervalThreshold: this.activeTransition.notifyIntervalThreshold,
|
397 | };
|
398 | this.emit("update" , update);
|
399 | }
|
400 | else {
|
401 | throw new Error("Unsupported adaptive lighting controller mode: " + this.mode);
|
402 | }
|
403 | if (!calledFromDeserializer) {
|
404 | this.stateChangeDelegate?.();
|
405 | }
|
406 | }
|
407 | handleAdaptiveLightingEnabled() {
|
408 | if (!this.activeTransition) {
|
409 | throw new Error("There is no active transition!");
|
410 | }
|
411 | this.colorTemperatureCharacteristic = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.ColorTemperature);
|
412 | this.brightnessCharacteristic = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.Brightness);
|
413 | this.colorTemperatureCharacteristic.on("change" , this.characteristicManualWrittenChangeListener);
|
414 | this.brightnessCharacteristic.on("change" , this.adjustmentFactorChangedListener);
|
415 | if (this.lightbulb.testCharacteristic(Characteristic_1.Characteristic.Hue)) {
|
416 | this.hueCharacteristic = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.Hue)
|
417 | .on("change" , this.characteristicManualWrittenChangeListener);
|
418 | }
|
419 | if (this.lightbulb.testCharacteristic(Characteristic_1.Characteristic.Saturation)) {
|
420 | this.saturationCharacteristic = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.Saturation)
|
421 | .on("change" , this.characteristicManualWrittenChangeListener);
|
422 | }
|
423 | }
|
424 | handleAdaptiveLightingDisabled() {
|
425 | if (this.mode === 2 && this.activeTransition) {
|
426 | this.emit("disable" );
|
427 | }
|
428 | this.disableAdaptiveLighting();
|
429 | }
|
430 | handleAdjustmentFactorChanged(change) {
|
431 | if (change.newValue === change.oldValue) {
|
432 | return;
|
433 | }
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 | if (change.reason === "read" ) {
|
444 |
|
445 |
|
446 |
|
447 | setTimeout(() => {
|
448 | if (!this.activeTransition) {
|
449 | return;
|
450 | }
|
451 | this.scheduleNextUpdate(true);
|
452 | }, 1000).unref();
|
453 | }
|
454 | else {
|
455 | this.scheduleNextUpdate(true);
|
456 | }
|
457 | }
|
458 | |
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 | handleCharacteristicManualWritten(change) {
|
465 | if (change.reason === "write" && !(isAdaptiveLightingContext(change.context) && change.context.controller === this)) {
|
466 |
|
467 |
|
468 |
|
469 | debug("[%s] Received a manual write to an characteristic (newValue: %d, oldValue: %d, reason: %s). Thus disabling adaptive lighting!", this.lightbulb.displayName, change.newValue, change.oldValue, change.reason);
|
470 | this.disableAdaptiveLighting();
|
471 | }
|
472 | }
|
473 | |
474 |
|
475 |
|
476 |
|
477 | getCurrentAdaptiveLightingTransitionPoint() {
|
478 | if (!this.activeTransition) {
|
479 | throw new Error("Cannot calculate current transition point if no transition is active!");
|
480 | }
|
481 |
|
482 | const adjustedNow = Date.now() - this.activeTransition.timeMillisOffset;
|
483 |
|
484 | const offset = adjustedNow - this.activeTransition.transitionStartMillis;
|
485 | let i = this.lastTransitionPointInfo?.curveIndex ?? 0;
|
486 | let lowerBoundTimeOffset = this.lastTransitionPointInfo?.lowerBoundTimeOffset ?? 0;
|
487 | let lowerBound = undefined;
|
488 | let upperBound = undefined;
|
489 | for (; i + 1 < this.activeTransition.transitionCurve.length; i++) {
|
490 | const lowerBound0 = this.activeTransition.transitionCurve[i];
|
491 | const upperBound0 = this.activeTransition.transitionCurve[i + 1];
|
492 | const lowerBoundDuration = lowerBound0.duration ?? 0;
|
493 | lowerBoundTimeOffset += lowerBound0.transitionTime;
|
494 | if (offset >= lowerBoundTimeOffset) {
|
495 | if (offset <= lowerBoundTimeOffset + lowerBoundDuration + upperBound0.transitionTime) {
|
496 | lowerBound = lowerBound0;
|
497 | upperBound = upperBound0;
|
498 | break;
|
499 | }
|
500 | }
|
501 | else if (this.lastTransitionPointInfo) {
|
502 |
|
503 |
|
504 |
|
505 | this.lastTransitionPointInfo = undefined;
|
506 | return this.getCurrentAdaptiveLightingTransitionPoint();
|
507 | }
|
508 | lowerBoundTimeOffset += lowerBoundDuration;
|
509 | }
|
510 | if (!lowerBound || !upperBound) {
|
511 | this.lastTransitionPointInfo = undefined;
|
512 | return undefined;
|
513 | }
|
514 | this.lastTransitionPointInfo = {
|
515 | curveIndex: i,
|
516 |
|
517 |
|
518 |
|
519 | lowerBoundTimeOffset: lowerBoundTimeOffset - lowerBound.transitionTime,
|
520 | };
|
521 | return {
|
522 | lowerBoundTimeOffset: lowerBoundTimeOffset,
|
523 | transitionOffset: offset - lowerBoundTimeOffset,
|
524 | lowerBound: lowerBound,
|
525 | upperBound: upperBound,
|
526 | };
|
527 | }
|
528 | scheduleNextUpdate(dryRun = false) {
|
529 | if (!this.activeTransition) {
|
530 | throw new Error("tried scheduling transition when no transition was active!");
|
531 | }
|
532 | if (!dryRun) {
|
533 | this.updateTimeout = undefined;
|
534 | }
|
535 | if (!this.didRunFirstInitializationStep) {
|
536 | this.didRunFirstInitializationStep = true;
|
537 | this.handleAdaptiveLightingEnabled();
|
538 | }
|
539 | const transitionPoint = this.getCurrentAdaptiveLightingTransitionPoint();
|
540 | if (!transitionPoint) {
|
541 | debug("[%s] Reached end of transition curve!", this.lightbulb.displayName);
|
542 | if (!dryRun) {
|
543 |
|
544 | this.disableAdaptiveLighting();
|
545 | }
|
546 | return;
|
547 | }
|
548 | const lowerBound = transitionPoint.lowerBound;
|
549 | const upperBound = transitionPoint.upperBound;
|
550 | let interpolatedTemperature;
|
551 | let interpolatedAdjustmentFactor;
|
552 | if (lowerBound.duration && transitionPoint.transitionOffset <= lowerBound.duration) {
|
553 | interpolatedTemperature = lowerBound.temperature;
|
554 | interpolatedAdjustmentFactor = lowerBound.brightnessAdjustmentFactor;
|
555 | }
|
556 | else {
|
557 | const timePercentage = (transitionPoint.transitionOffset - (lowerBound.duration ?? 0)) / upperBound.transitionTime;
|
558 | interpolatedTemperature = lowerBound.temperature + (upperBound.temperature - lowerBound.temperature) * timePercentage;
|
559 | interpolatedAdjustmentFactor = lowerBound.brightnessAdjustmentFactor
|
560 | + (upperBound.brightnessAdjustmentFactor - lowerBound.brightnessAdjustmentFactor) * timePercentage;
|
561 | }
|
562 | const adjustmentMultiplier = Math.max(this.activeTransition.brightnessAdjustmentRange.minBrightnessValue, Math.min(this.activeTransition.brightnessAdjustmentRange.maxBrightnessValue, this.brightnessCharacteristic?.value));
|
563 | let temperature = Math.round(interpolatedTemperature + interpolatedAdjustmentFactor * adjustmentMultiplier);
|
564 |
|
565 | temperature += this.customTemperatureAdjustment;
|
566 | const min = this.colorTemperatureCharacteristic?.props.minValue ?? 140;
|
567 | const max = this.colorTemperatureCharacteristic?.props.maxValue ?? 500;
|
568 | temperature = Math.max(min, Math.min(max, temperature));
|
569 | const color = color_utils_1.ColorUtils.colorTemperatureToHueAndSaturation(temperature);
|
570 | debug("[%s] Next temperature value is %d (for brightness %d adj: %s)", this.lightbulb.displayName, temperature, adjustmentMultiplier, this.customTemperatureAdjustment);
|
571 | const context = {
|
572 | controller: this,
|
573 | omitEventUpdate: true,
|
574 | };
|
575 | |
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 |
|
590 | if (this.saturationCharacteristic) {
|
591 | this.saturationCharacteristic.value = color.saturation;
|
592 | }
|
593 | if (this.hueCharacteristic) {
|
594 | this.hueCharacteristic.value = color.hue;
|
595 | }
|
596 | this.colorTemperatureCharacteristic?.handleSetRequest(temperature, undefined, context).catch(reason => {
|
597 | debug("[%s] Failed to next adaptive lighting transition point: %d", this.lightbulb.displayName, reason);
|
598 | });
|
599 | if (!this.activeTransition) {
|
600 | console.warn("[" + this.lightbulb.displayName + "] Adaptive Lighting was probably disable my mistake by some call in " +
|
601 | "the SET handler of the ColorTemperature characteristic! " +
|
602 | "Please check that you don't call setValue/setCharacteristic on the Hue, Saturation or ColorTemperature characteristic!");
|
603 | return;
|
604 | }
|
605 | const now = Date.now();
|
606 | if (!dryRun && now - this.lastEventNotificationSent >= this.activeTransition.notifyIntervalThreshold) {
|
607 | debug("[%s] Sending event notifications for current transition!", this.lightbulb.displayName);
|
608 | this.lastEventNotificationSent = now;
|
609 | const eventContext = {
|
610 | controller: this,
|
611 | };
|
612 | if (this.lastNotifiedTemperatureValue !== temperature) {
|
613 | this.colorTemperatureCharacteristic?.sendEventNotification(temperature, eventContext);
|
614 | this.lastNotifiedTemperatureValue = temperature;
|
615 | }
|
616 | if (this.saturationCharacteristic && this.lastNotifiedSaturationValue !== color.saturation) {
|
617 | this.saturationCharacteristic.sendEventNotification(color.saturation, eventContext);
|
618 | this.lastNotifiedSaturationValue = color.saturation;
|
619 | }
|
620 | if (this.hueCharacteristic && this.lastNotifiedHueValue !== color.hue) {
|
621 | this.hueCharacteristic.sendEventNotification(color.hue, eventContext);
|
622 | this.lastNotifiedHueValue = color.hue;
|
623 | }
|
624 | }
|
625 | if (!dryRun) {
|
626 | this.updateTimeout = setTimeout(this.scheduleNextUpdate.bind(this), this.activeTransition.updateInterval);
|
627 | }
|
628 | }
|
629 | |
630 |
|
631 |
|
632 | constructServices() {
|
633 | return {};
|
634 | }
|
635 | |
636 |
|
637 |
|
638 |
|
639 | initWithServices(serviceMap) {
|
640 |
|
641 | }
|
642 | |
643 |
|
644 |
|
645 | configureServices() {
|
646 | this.supportedTransitionConfiguration = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.SupportedCharacteristicValueTransitionConfiguration);
|
647 | this.transitionControl = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.CharacteristicValueTransitionControl)
|
648 | .updateValue("");
|
649 | this.activeTransitionCount = this.lightbulb.getCharacteristic(Characteristic_1.Characteristic.CharacteristicValueActiveTransitionCount)
|
650 | .updateValue(0);
|
651 | this.supportedTransitionConfiguration
|
652 | .onGet(this.handleSupportedTransitionConfigurationRead.bind(this));
|
653 | this.transitionControl
|
654 | .onGet(() => {
|
655 | return this.buildTransitionControlResponseBuffer().toString("base64");
|
656 | })
|
657 | .onSet(value => {
|
658 | try {
|
659 | return this.handleTransitionControlWrite(value);
|
660 | }
|
661 | catch (error) {
|
662 | console.warn(`[%s] DEBUG: '${value}'`);
|
663 | console.warn("[%s] Encountered error on CharacteristicValueTransitionControl characteristic: " + error.stack);
|
664 | this.disableAdaptiveLighting();
|
665 | throw new hapStatusError_1.HapStatusError(-70402 );
|
666 | }
|
667 | });
|
668 | }
|
669 | |
670 |
|
671 |
|
672 | handleControllerRemoved() {
|
673 | this.lightbulb.removeCharacteristic(this.supportedTransitionConfiguration);
|
674 | this.lightbulb.removeCharacteristic(this.transitionControl);
|
675 | this.lightbulb.removeCharacteristic(this.activeTransitionCount);
|
676 | this.supportedTransitionConfiguration = undefined;
|
677 | this.transitionControl = undefined;
|
678 | this.activeTransitionCount = undefined;
|
679 | this.removeAllListeners();
|
680 | }
|
681 | |
682 |
|
683 |
|
684 | handleFactoryReset() {
|
685 | this.handleAdaptiveLightingDisabled();
|
686 | }
|
687 | |
688 |
|
689 |
|
690 | serialize() {
|
691 | if (!this.activeTransition) {
|
692 | return undefined;
|
693 | }
|
694 | return {
|
695 | activeTransition: this.activeTransition,
|
696 | };
|
697 | }
|
698 | |
699 |
|
700 |
|
701 | deserialize(serialized) {
|
702 | this.activeTransition = serialized.activeTransition;
|
703 |
|
704 | if (!this.activeTransition.transitionId) {
|
705 |
|
706 | this.activeTransition.transitionId = this.activeTransition.id1;
|
707 |
|
708 | delete this.activeTransition.id1;
|
709 | }
|
710 | if (!this.activeTransition.timeMillisOffset) {
|
711 | this.activeTransition.timeMillisOffset = 0;
|
712 | }
|
713 | this.handleActiveTransitionUpdated(true);
|
714 | }
|
715 | |
716 |
|
717 |
|
718 | setupStateChangeDelegate(delegate) {
|
719 | this.stateChangeDelegate = delegate;
|
720 | }
|
721 | handleSupportedTransitionConfigurationRead() {
|
722 | const brightnessIID = this.lightbulb?.getCharacteristic(Characteristic_1.Characteristic.Brightness).iid;
|
723 | const temperatureIID = this.lightbulb?.getCharacteristic(Characteristic_1.Characteristic.ColorTemperature).iid;
|
724 | (0, assert_1.default)(brightnessIID, "iid for brightness characteristic is undefined");
|
725 | (0, assert_1.default)(temperatureIID, "iid for temperature characteristic is undefined");
|
726 | return tlv.encode(1 , [
|
727 | tlv.encode(1 , tlv.writeVariableUIntLE(brightnessIID), 2 , 1 ),
|
728 | tlv.encode(1 , tlv.writeVariableUIntLE(temperatureIID), 2 , 2 ),
|
729 | ]).toString("base64");
|
730 | }
|
731 | buildTransitionControlResponseBuffer(time) {
|
732 | if (!this.activeTransition) {
|
733 | return Buffer.alloc(0);
|
734 | }
|
735 | const active = this.activeTransition;
|
736 | const timeSinceStart = time ?? (Date.now() - active.timeMillisOffset - active.transitionStartMillis);
|
737 | const timeSinceStartBuffer = tlv.writeVariableUIntLE(timeSinceStart);
|
738 | let parameters = tlv.encode(1 , uuid.write(active.transitionId), 2 , Buffer.from(active.transitionStartBuffer, "hex"));
|
739 | if (active.id3) {
|
740 | parameters = Buffer.concat([
|
741 | parameters,
|
742 | tlv.encode(3 , Buffer.from(active.id3, "hex")),
|
743 | ]);
|
744 | }
|
745 | const status = tlv.encode(1 , tlv.writeVariableUIntLE(active.iid), 2 , parameters, 3 , timeSinceStartBuffer);
|
746 | return tlv.encode(1 , status);
|
747 | }
|
748 | handleTransitionControlWrite(value) {
|
749 | if (typeof value !== "string") {
|
750 | throw new hapStatusError_1.HapStatusError(-70410 );
|
751 | }
|
752 | const tlvData = tlv.decode(Buffer.from(value, "base64"));
|
753 | const responseBuffers = [];
|
754 | const readTransition = tlvData[1 ];
|
755 | if (readTransition) {
|
756 | const readTransitionResponse = this.handleTransitionControlReadTransition(readTransition);
|
757 | if (readTransitionResponse) {
|
758 | responseBuffers.push(readTransitionResponse);
|
759 | }
|
760 | }
|
761 | const updateTransition = tlvData[2 ];
|
762 | if (updateTransition) {
|
763 | const updateTransitionResponse = this.handleTransitionControlUpdateTransition(updateTransition);
|
764 | if (updateTransitionResponse) {
|
765 | responseBuffers.push(updateTransitionResponse);
|
766 | }
|
767 | }
|
768 | return Buffer.concat(responseBuffers).toString("base64");
|
769 | }
|
770 | handleTransitionControlReadTransition(buffer) {
|
771 | const readTransition = tlv.decode(buffer);
|
772 | const iid = tlv.readVariableUIntLE(readTransition[1 ]);
|
773 | if (this.activeTransition) {
|
774 | if (this.activeTransition.iid !== iid) {
|
775 | console.warn("[" + this.lightbulb.displayName + "] iid of current adaptive lighting transition (" + this.activeTransition.iid
|
776 | + ") doesn't match the requested one " + iid);
|
777 | throw new hapStatusError_1.HapStatusError(-70410 );
|
778 | }
|
779 | let parameters = tlv.encode(1 , uuid.write(this.activeTransition.transitionId), 2 , Buffer.from(this.activeTransition.transitionStartBuffer, "hex"));
|
780 | if (this.activeTransition.id3) {
|
781 | parameters = Buffer.concat([
|
782 | parameters,
|
783 | tlv.encode(3 , Buffer.from(this.activeTransition.id3, "hex")),
|
784 | ]);
|
785 | }
|
786 | return tlv.encode(1 , tlv.encode(1 , tlv.writeVariableUIntLE(this.activeTransition.iid), 2 , parameters, 3 , 1, 5 , tlv.encode(1 , this.activeTransition.transitionCurve.map((entry, index, array) => {
|
787 | const duration = array[index - 1]?.duration ?? 0;
|
788 | return tlv.encode(1 , tlv.writeFloat32LE(entry.brightnessAdjustmentFactor), 2 , tlv.writeFloat32LE(entry.temperature), 3 , tlv.writeVariableUIntLE(entry.transitionTime), 4 , tlv.writeVariableUIntLE(duration));
|
789 | }), 2 , tlv.writeVariableUIntLE(this.activeTransition.brightnessCharacteristicIID), 3 , tlv.encode(1 , tlv.writeUInt32(this.activeTransition.brightnessAdjustmentRange.minBrightnessValue), 2 , tlv.writeUInt32(this.activeTransition.brightnessAdjustmentRange.maxBrightnessValue))), 6 , tlv.writeVariableUIntLE(this.activeTransition.updateInterval), 8 , tlv.writeVariableUIntLE(this.activeTransition.notifyIntervalThreshold)));
|
790 | }
|
791 | else {
|
792 | return undefined;
|
793 | }
|
794 | }
|
795 | handleTransitionControlUpdateTransition(buffer) {
|
796 | const updateTransition = tlv.decode(buffer);
|
797 | const transitionConfiguration = tlv.decode(updateTransition[1 ]);
|
798 | const iid = tlv.readVariableUIntLE(transitionConfiguration[1 ]);
|
799 | if (!this.lightbulb.getCharacteristicByIID(iid)) {
|
800 | throw new hapStatusError_1.HapStatusError(-70410 );
|
801 | }
|
802 | const param3 = transitionConfiguration[3 ]?.readUInt8(0);
|
803 | if (!param3) {
|
804 | this.handleAdaptiveLightingDisabled();
|
805 | return tlv.encode(2 , Buffer.alloc(0));
|
806 | }
|
807 | const parametersTLV = tlv.decode(transitionConfiguration[2 ]);
|
808 | const curveConfiguration = tlv.decodeWithLists(transitionConfiguration[5 ]);
|
809 | const updateInterval = transitionConfiguration[6 ]?.readUInt16LE(0);
|
810 | const notifyIntervalThreshold = transitionConfiguration[8 ].readUInt32LE(0);
|
811 | const transitionId = parametersTLV[1 ];
|
812 | const startTime = parametersTLV[2 ];
|
813 | const id3 = parametersTLV[3 ];
|
814 | const startTimeMillis = (0, time_1.epochMillisFromMillisSince2001_01_01Buffer)(startTime);
|
815 | const timeMillisOffset = Date.now() - startTimeMillis;
|
816 | const transitionCurve = [];
|
817 | let previous = undefined;
|
818 | const transitions = curveConfiguration[1 ];
|
819 | for (const entry of transitions) {
|
820 | const tlvEntry = tlv.decode(entry);
|
821 | const adjustmentFactor = tlvEntry[1 ].readFloatLE(0);
|
822 | const value = tlvEntry[2 ].readFloatLE(0);
|
823 | const transitionOffset = tlv.readVariableUIntLE(tlvEntry[3 ]);
|
824 | const duration = tlvEntry[4 ] ? tlv.readVariableUIntLE(tlvEntry[4 ]) : undefined;
|
825 | if (previous) {
|
826 | previous.duration = duration;
|
827 | }
|
828 | previous = {
|
829 | temperature: value,
|
830 | brightnessAdjustmentFactor: adjustmentFactor,
|
831 | transitionTime: transitionOffset,
|
832 | };
|
833 | transitionCurve.push(previous);
|
834 | }
|
835 | const adjustmentIID = tlv.readVariableUIntLE(curveConfiguration[2 ]);
|
836 | const adjustmentMultiplierRange = tlv.decode(curveConfiguration[3 ]);
|
837 | const minAdjustmentMultiplier = adjustmentMultiplierRange[1 ].readUInt32LE(0);
|
838 | const maxAdjustmentMultiplier = adjustmentMultiplierRange[2 ].readUInt32LE(0);
|
839 | this.activeTransition = {
|
840 | iid: iid,
|
841 | transitionStartMillis: startTimeMillis,
|
842 | timeMillisOffset: timeMillisOffset,
|
843 | transitionId: uuid.unparse(transitionId),
|
844 | transitionStartBuffer: startTime.toString("hex"),
|
845 | id3: id3?.toString("hex"),
|
846 | brightnessCharacteristicIID: adjustmentIID,
|
847 | brightnessAdjustmentRange: {
|
848 | minBrightnessValue: minAdjustmentMultiplier,
|
849 | maxBrightnessValue: maxAdjustmentMultiplier,
|
850 | },
|
851 | transitionCurve: transitionCurve,
|
852 | updateInterval: updateInterval ?? 60000,
|
853 | notifyIntervalThreshold: notifyIntervalThreshold,
|
854 | };
|
855 | if (this.updateTimeout) {
|
856 | clearTimeout(this.updateTimeout);
|
857 | this.updateTimeout = undefined;
|
858 | debug("[%s] Adaptive lighting was renewed.", this.lightbulb.displayName);
|
859 | }
|
860 | else {
|
861 | debug("[%s] Adaptive lighting was enabled.", this.lightbulb.displayName);
|
862 | }
|
863 | this.handleActiveTransitionUpdated();
|
864 | return tlv.encode(2 , this.buildTransitionControlResponseBuffer(0));
|
865 | }
|
866 | }
|
867 | exports.AdaptiveLightingController = AdaptiveLightingController;
|
868 |
|
\ | No newline at end of file |