1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Service = exports.ServiceEventTypes = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const assert_1 = tslib_1.__importDefault(require("assert"));
|
6 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
7 | const events_1 = require("events");
|
8 | const Characteristic_1 = require("./Characteristic");
|
9 | const uuid_1 = require("./util/uuid");
|
10 | const checkName_1 = require("./util/checkName");
|
11 | const debug = (0, debug_1.default)("HAP-NodeJS:Service");
|
12 | /**
|
13 | * HAP spec allows a maximum of 100 characteristics per service!
|
14 | */
|
15 | const MAX_CHARACTERISTICS = 100;
|
16 | /**
|
17 | * @group Service
|
18 | */
|
19 | var ServiceEventTypes;
|
20 | (function (ServiceEventTypes) {
|
21 | ServiceEventTypes["CHARACTERISTIC_CHANGE"] = "characteristic-change";
|
22 | ServiceEventTypes["SERVICE_CONFIGURATION_CHANGE"] = "service-configurationChange";
|
23 | ServiceEventTypes["CHARACTERISTIC_WARNING"] = "characteristic-warning";
|
24 | })(ServiceEventTypes || (exports.ServiceEventTypes = ServiceEventTypes = {}));
|
25 | /**
|
26 | * Service represents a set of grouped values necessary to provide a logical function. For instance, a
|
27 | * "Door Lock Mechanism" service might contain two values, one for the "desired lock state" and one for the
|
28 | * "current lock state". A particular Service is distinguished from others by its "type", which is a UUID.
|
29 | * HomeKit provides a set of known Service UUIDs defined in HomeKit.ts along with a corresponding
|
30 | * concrete subclass that you can instantiate directly to set up the necessary values. These natively-supported
|
31 | * Services are expected to contain a particular set of Characteristics.
|
32 | *
|
33 | * Unlike Characteristics, where you cannot have two Characteristics with the same UUID in the same Service,
|
34 | * you can actually have multiple Services with the same UUID in a single Accessory. For instance, imagine
|
35 | * a Garage Door Opener with both a "security light" and a "backlight" for the display. Each light could be
|
36 | * a "Lightbulb" Service with the same UUID. To account for this situation, we define an extra "subtype"
|
37 | * property on Service, that can be a string or other string-convertible object that uniquely identifies the
|
38 | * Service among its peers in an Accessory. For instance, you might have `service1.subtype = 'security_light'`
|
39 | * for one and `service2.subtype = 'backlight'` for the other.
|
40 | *
|
41 | * You can also define custom Services by providing your own UUID for the type that you generate yourself.
|
42 | * Custom Services can contain an arbitrary set of Characteristics, but Siri will likely not be able to
|
43 | * work with these.
|
44 | *
|
45 | * @group Service
|
46 | */
|
47 | // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
48 | class Service extends events_1.EventEmitter {
|
49 | // Service MUST NOT have any other static variables
|
50 | // Pattern below is for automatic detection of the section of defined services. Used by the generator
|
51 | // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
52 | /**
|
53 | * @group Service Definitions
|
54 | */
|
55 | static AccessCode;
|
56 | /**
|
57 | * @group Service Definitions
|
58 | */
|
59 | static AccessControl;
|
60 | /**
|
61 | * @group Service Definitions
|
62 | */
|
63 | static AccessoryInformation;
|
64 | /**
|
65 | * @group Service Definitions
|
66 | */
|
67 | static AccessoryMetrics;
|
68 | /**
|
69 | * @group Service Definitions
|
70 | */
|
71 | static AccessoryRuntimeInformation;
|
72 | /**
|
73 | * @group Service Definitions
|
74 | */
|
75 | static AirPurifier;
|
76 | /**
|
77 | * @group Service Definitions
|
78 | */
|
79 | static AirQualitySensor;
|
80 | /**
|
81 | * @group Service Definitions
|
82 | */
|
83 | static AssetUpdate;
|
84 | /**
|
85 | * @group Service Definitions
|
86 | */
|
87 | static Assistant;
|
88 | /**
|
89 | * @group Service Definitions
|
90 | */
|
91 | static AudioStreamManagement;
|
92 | /**
|
93 | * @group Service Definitions
|
94 | */
|
95 | static Battery;
|
96 | /**
|
97 | * @group Service Definitions
|
98 | */
|
99 | static CameraOperatingMode;
|
100 | /**
|
101 | * @group Service Definitions
|
102 | */
|
103 | static CameraRecordingManagement;
|
104 | /**
|
105 | * @group Service Definitions
|
106 | */
|
107 | static CameraRTPStreamManagement;
|
108 | /**
|
109 | * @group Service Definitions
|
110 | */
|
111 | static CarbonDioxideSensor;
|
112 | /**
|
113 | * @group Service Definitions
|
114 | */
|
115 | static CarbonMonoxideSensor;
|
116 | /**
|
117 | * @group Service Definitions
|
118 | */
|
119 | static CloudRelay;
|
120 | /**
|
121 | * @group Service Definitions
|
122 | */
|
123 | static ContactSensor;
|
124 | /**
|
125 | * @group Service Definitions
|
126 | */
|
127 | static DataStreamTransportManagement;
|
128 | /**
|
129 | * @group Service Definitions
|
130 | */
|
131 | static Diagnostics;
|
132 | /**
|
133 | * @group Service Definitions
|
134 | */
|
135 | static Door;
|
136 | /**
|
137 | * @group Service Definitions
|
138 | */
|
139 | static Doorbell;
|
140 | /**
|
141 | * @group Service Definitions
|
142 | */
|
143 | static Fan;
|
144 | /**
|
145 | * @group Service Definitions
|
146 | */
|
147 | static Fanv2;
|
148 | /**
|
149 | * @group Service Definitions
|
150 | */
|
151 | static Faucet;
|
152 | /**
|
153 | * @group Service Definitions
|
154 | */
|
155 | static FilterMaintenance;
|
156 | /**
|
157 | * @group Service Definitions
|
158 | */
|
159 | static FirmwareUpdate;
|
160 | /**
|
161 | * @group Service Definitions
|
162 | */
|
163 | static GarageDoorOpener;
|
164 | /**
|
165 | * @group Service Definitions
|
166 | */
|
167 | static HeaterCooler;
|
168 | /**
|
169 | * @group Service Definitions
|
170 | */
|
171 | static HumidifierDehumidifier;
|
172 | /**
|
173 | * @group Service Definitions
|
174 | */
|
175 | static HumiditySensor;
|
176 | /**
|
177 | * @group Service Definitions
|
178 | */
|
179 | static InputSource;
|
180 | /**
|
181 | * @group Service Definitions
|
182 | */
|
183 | static IrrigationSystem;
|
184 | /**
|
185 | * @group Service Definitions
|
186 | */
|
187 | static LeakSensor;
|
188 | /**
|
189 | * @group Service Definitions
|
190 | */
|
191 | static Lightbulb;
|
192 | /**
|
193 | * @group Service Definitions
|
194 | */
|
195 | static LightSensor;
|
196 | /**
|
197 | * @group Service Definitions
|
198 | */
|
199 | static LockManagement;
|
200 | /**
|
201 | * @group Service Definitions
|
202 | */
|
203 | static LockMechanism;
|
204 | /**
|
205 | * @group Service Definitions
|
206 | */
|
207 | static Microphone;
|
208 | /**
|
209 | * @group Service Definitions
|
210 | */
|
211 | static MotionSensor;
|
212 | /**
|
213 | * @group Service Definitions
|
214 | */
|
215 | static NFCAccess;
|
216 | /**
|
217 | * @group Service Definitions
|
218 | */
|
219 | static OccupancySensor;
|
220 | /**
|
221 | * @group Service Definitions
|
222 | */
|
223 | static Outlet;
|
224 | /**
|
225 | * @group Service Definitions
|
226 | */
|
227 | static Pairing;
|
228 | /**
|
229 | * @group Service Definitions
|
230 | */
|
231 | static PowerManagement;
|
232 | /**
|
233 | * @group Service Definitions
|
234 | */
|
235 | static ProtocolInformation;
|
236 | /**
|
237 | * @group Service Definitions
|
238 | */
|
239 | static SecuritySystem;
|
240 | /**
|
241 | * @group Service Definitions
|
242 | */
|
243 | static ServiceLabel;
|
244 | /**
|
245 | * @group Service Definitions
|
246 | */
|
247 | static Siri;
|
248 | /**
|
249 | * @group Service Definitions
|
250 | */
|
251 | static SiriEndpoint;
|
252 | /**
|
253 | * @group Service Definitions
|
254 | */
|
255 | static Slats;
|
256 | /**
|
257 | * @group Service Definitions
|
258 | */
|
259 | static SmartSpeaker;
|
260 | /**
|
261 | * @group Service Definitions
|
262 | */
|
263 | static SmokeSensor;
|
264 | /**
|
265 | * @group Service Definitions
|
266 | */
|
267 | static Speaker;
|
268 | /**
|
269 | * @group Service Definitions
|
270 | */
|
271 | static StatefulProgrammableSwitch;
|
272 | /**
|
273 | * @group Service Definitions
|
274 | */
|
275 | static StatelessProgrammableSwitch;
|
276 | /**
|
277 | * @group Service Definitions
|
278 | */
|
279 | static Switch;
|
280 | /**
|
281 | * @group Service Definitions
|
282 | */
|
283 | static TapManagement;
|
284 | /**
|
285 | * @group Service Definitions
|
286 | */
|
287 | static TargetControl;
|
288 | /**
|
289 | * @group Service Definitions
|
290 | */
|
291 | static TargetControlManagement;
|
292 | /**
|
293 | * @group Service Definitions
|
294 | */
|
295 | static Television;
|
296 | /**
|
297 | * @group Service Definitions
|
298 | */
|
299 | static TelevisionSpeaker;
|
300 | /**
|
301 | * @group Service Definitions
|
302 | */
|
303 | static TemperatureSensor;
|
304 | /**
|
305 | * @group Service Definitions
|
306 | */
|
307 | static Thermostat;
|
308 | /**
|
309 | * @group Service Definitions
|
310 | */
|
311 | static ThreadTransport;
|
312 | /**
|
313 | * @group Service Definitions
|
314 | */
|
315 | static TransferTransportManagement;
|
316 | /**
|
317 | * @group Service Definitions
|
318 | */
|
319 | static Tunnel;
|
320 | /**
|
321 | * @group Service Definitions
|
322 | */
|
323 | static Valve;
|
324 | /**
|
325 | * @group Service Definitions
|
326 | */
|
327 | static WiFiRouter;
|
328 | /**
|
329 | * @group Service Definitions
|
330 | */
|
331 | static WiFiSatellite;
|
332 | /**
|
333 | * @group Service Definitions
|
334 | */
|
335 | static WiFiTransport;
|
336 | /**
|
337 | * @group Service Definitions
|
338 | */
|
339 | static Window;
|
340 | /**
|
341 | * @group Service Definitions
|
342 | */
|
343 | static WindowCovering;
|
344 | // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
345 | // NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions
|
346 | displayName;
|
347 | UUID;
|
348 | subtype;
|
349 | iid = null; // assigned later by our containing Accessory
|
350 | name = null;
|
351 | characteristics = [];
|
352 | optionalCharacteristics = [];
|
353 | /**
|
354 | * @private
|
355 | */
|
356 | isHiddenService = false;
|
357 | /**
|
358 | * @private
|
359 | */
|
360 | isPrimaryService = false; // do not write to this directly
|
361 | /**
|
362 | * @private
|
363 | */
|
364 | linkedServices = [];
|
365 | constructor(displayName = "", UUID, subtype) {
|
366 | super();
|
367 | (0, assert_1.default)(UUID, "Services must be created with a valid UUID.");
|
368 | this.displayName = displayName;
|
369 | this.UUID = UUID;
|
370 | this.subtype = subtype;
|
371 | // every service has an optional Characteristic.Name property - we'll set it to our displayName
|
372 | // if one was given
|
373 | // if you don't provide a display name, some HomeKit apps may choose to hide the device.
|
374 | if (displayName) {
|
375 | // create the characteristic if necessary
|
376 | (0, checkName_1.checkName)(this.displayName, "Name", displayName);
|
377 | const nameCharacteristic = this.getCharacteristic(Characteristic_1.Characteristic.Name) ||
|
378 | this.addCharacteristic(Characteristic_1.Characteristic.Name);
|
379 | nameCharacteristic.updateValue(displayName);
|
380 | }
|
381 | }
|
382 | /**
|
383 | * Returns an id which uniquely identifies a service on the associated accessory.
|
384 | * The serviceId is a concatenation of the UUID for the service (defined by HAP) and the subtype (could be empty)
|
385 | * which is programmatically defined by the programmer.
|
386 | *
|
387 | * @returns the serviceId
|
388 | */
|
389 | getServiceId() {
|
390 | return this.UUID + (this.subtype || "");
|
391 | }
|
392 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
393 | addCharacteristic(input, ...constructorArgs) {
|
394 | // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance of Characteristic. Coerce if necessary.
|
395 | const characteristic = typeof input === "function" ? new input(...constructorArgs) : input;
|
396 | // check for UUID conflict
|
397 | for (const existing of this.characteristics) {
|
398 | if (existing.UUID === characteristic.UUID) {
|
399 | if (characteristic.UUID === "00000052-0000-1000-8000-0026BB765291") {
|
400 | //This is a special workaround for the Firmware Revision characteristic.
|
401 | return existing;
|
402 | }
|
403 | throw new Error("Cannot add a Characteristic with the same UUID as another Characteristic in this Service: " + existing.UUID);
|
404 | }
|
405 | }
|
406 | if (this.characteristics.length >= MAX_CHARACTERISTICS) {
|
407 | throw new Error("Cannot add more than " + MAX_CHARACTERISTICS + " characteristics to a single service!");
|
408 | }
|
409 | this.setupCharacteristicEventHandlers(characteristic);
|
410 | this.characteristics.push(characteristic);
|
411 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
412 | return characteristic;
|
413 | }
|
414 | /**
|
415 | * Sets this service as the new primary service.
|
416 | * Any currently active primary service will be reset to be not primary.
|
417 | * This will happen immediately, if the service was already added to an accessory, or later
|
418 | * when the service gets added to an accessory.
|
419 | *
|
420 | * @param isPrimary - optional boolean (default true) if the service should be the primary service
|
421 | */
|
422 | setPrimaryService(isPrimary = true) {
|
423 | this.isPrimaryService = isPrimary;
|
424 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
425 | }
|
426 | /**
|
427 | * Marks the service as hidden
|
428 | *
|
429 | * @param isHidden - optional boolean (default true) if the service should be marked hidden
|
430 | */
|
431 | setHiddenService(isHidden = true) {
|
432 | this.isHiddenService = isHidden;
|
433 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
434 | }
|
435 | /**
|
436 | * Adds a new link to the specified service. The service MUST be already added to
|
437 | * the SAME accessory.
|
438 | *
|
439 | * @param service - The service this service should link to
|
440 | */
|
441 | addLinkedService(service) {
|
442 | //TODO: Add a check if the service is on the same accessory.
|
443 | if (!this.linkedServices.includes(service)) {
|
444 | this.linkedServices.push(service);
|
445 | }
|
446 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
447 | }
|
448 | /**
|
449 | * Removes a link to the specified service which was previously added with {@link addLinkedService}
|
450 | *
|
451 | * @param service - Previously linked service
|
452 | */
|
453 | removeLinkedService(service) {
|
454 | //TODO: Add a check if the service is on the same accessory.
|
455 | const index = this.linkedServices.indexOf(service);
|
456 | if (index !== -1) {
|
457 | this.linkedServices.splice(index, 1);
|
458 | }
|
459 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
460 | }
|
461 | removeCharacteristic(characteristic) {
|
462 | const index = this.characteristics.indexOf(characteristic);
|
463 | if (index !== -1) {
|
464 | this.characteristics.splice(index, 1);
|
465 | characteristic.removeAllListeners();
|
466 | this.emit("service-configurationChange" /* ServiceEventTypes.SERVICE_CONFIGURATION_CHANGE */);
|
467 | }
|
468 | }
|
469 | getCharacteristic(name) {
|
470 | // returns a characteristic object from the service
|
471 | // If Service.prototype.getCharacteristic(Characteristic.Type) does not find the characteristic,
|
472 | // but the type is in optionalCharacteristics, it adds the characteristic.type to the service and returns it.
|
473 | for (const characteristic of this.characteristics) {
|
474 | if (typeof name === "string" && characteristic.displayName === name) {
|
475 | return characteristic;
|
476 | }
|
477 | else {
|
478 | // @ts-expect-error ('UUID' does not exist on type 'never')
|
479 | if (typeof name === "function" && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
|
480 | return characteristic;
|
481 | }
|
482 | }
|
483 | }
|
484 | if (typeof name === "function") {
|
485 | for (const characteristic of this.optionalCharacteristics) {
|
486 | // @ts-expect-error ('UUID' does not exist on type 'never')
|
487 | if ((characteristic instanceof name) || (name.UUID === characteristic.UUID)) {
|
488 | return this.addCharacteristic(name);
|
489 | }
|
490 | }
|
491 | const instance = this.addCharacteristic(name);
|
492 | // Not found in optional Characteristics. Adding anyway, but warning about it if it isn't the Name.
|
493 | if (name.UUID !== Characteristic_1.Characteristic.Name.UUID) {
|
494 | this.emitCharacteristicWarningEvent(instance, "warn-message" /* CharacteristicWarningType.WARN_MESSAGE */, "Characteristic not in required or optional characteristic section for service " + this.constructor.name + ". Adding anyway.");
|
495 | }
|
496 | return instance;
|
497 | }
|
498 | }
|
499 | testCharacteristic(name) {
|
500 | // checks for the existence of a characteristic object in the service
|
501 | for (const characteristic of this.characteristics) {
|
502 | if (typeof name === "string" && characteristic.displayName === name) {
|
503 | return true;
|
504 | }
|
505 | else {
|
506 | // @ts-expect-error ('UUID' does not exist on type 'never')
|
507 | if (typeof name === "function" && ((characteristic instanceof name) || (name.UUID === characteristic.UUID))) {
|
508 | return true;
|
509 | }
|
510 | }
|
511 | }
|
512 | return false;
|
513 | }
|
514 | setCharacteristic(name, value) {
|
515 | // @ts-expect-error: We know that both overloads exists individually. There is just no publicly exposed type for that!
|
516 | this.getCharacteristic(name).setValue(value);
|
517 | return this; // for chaining
|
518 | }
|
519 | updateCharacteristic(name, value) {
|
520 | this.getCharacteristic(name).updateValue(value);
|
521 | return this;
|
522 | }
|
523 | addOptionalCharacteristic(characteristic) {
|
524 | // characteristic might be a constructor like `Characteristic.Brightness` instead of an instance
|
525 | // of Characteristic. Coerce if necessary.
|
526 | if (typeof characteristic === "function") {
|
527 | characteristic = new characteristic();
|
528 | }
|
529 | this.optionalCharacteristics.push(characteristic);
|
530 | }
|
531 | // noinspection JSUnusedGlobalSymbols
|
532 | /**
|
533 | * This method was created to copy all characteristics from another service to this.
|
534 | * It's only adopting is currently in homebridge to merge the AccessoryInformation service. So some things
|
535 | * may be explicitly tailored towards this use case.
|
536 | *
|
537 | * It will not remove characteristics which are present currently but not added on the other characteristic.
|
538 | * It will not replace the characteristic if the value is falsy (except of '0' or 'false')
|
539 | * @param service
|
540 | * @private used by homebridge
|
541 | */
|
542 | replaceCharacteristicsFromService(service) {
|
543 | if (this.UUID !== service.UUID) {
|
544 | throw new Error(`Incompatible services. Tried replacing characteristics of ${this.UUID} with characteristics from ${service.UUID}`);
|
545 | }
|
546 | const foreignCharacteristics = {}; // index foreign characteristics by UUID
|
547 | service.characteristics.forEach(characteristic => foreignCharacteristics[characteristic.UUID] = characteristic);
|
548 | this.characteristics.forEach(characteristic => {
|
549 | const foreignCharacteristic = foreignCharacteristics[characteristic.UUID];
|
550 | if (foreignCharacteristic) {
|
551 | delete foreignCharacteristics[characteristic.UUID];
|
552 | if (!foreignCharacteristic.value && foreignCharacteristic.value !== 0 && foreignCharacteristic.value !== false) {
|
553 | return; // ignore falsy values except if it's the number zero or literally false
|
554 | }
|
555 | characteristic.replaceBy(foreignCharacteristic);
|
556 | }
|
557 | });
|
558 | // add all additional characteristics which where not present already
|
559 | Object.values(foreignCharacteristics).forEach(characteristic => this.addCharacteristic(characteristic));
|
560 | }
|
561 | /**
|
562 | * @private
|
563 | */
|
564 | getCharacteristicByIID(iid) {
|
565 | for (const characteristic of this.characteristics) {
|
566 | if (characteristic.iid === iid) {
|
567 | return characteristic;
|
568 | }
|
569 | }
|
570 | }
|
571 | /**
|
572 | * @private
|
573 | */
|
574 | _assignIDs(identifierCache, accessoryName, baseIID = 0) {
|
575 | // the Accessory Information service must have a (reserved by IdentifierCache) ID of 1
|
576 | if (this.UUID === "0000003E-0000-1000-8000-0026BB765291") {
|
577 | this.iid = 1;
|
578 | }
|
579 | else {
|
580 | // assign our own ID based on our UUID
|
581 | this.iid = baseIID + identifierCache.getIID(accessoryName, this.UUID, this.subtype);
|
582 | }
|
583 | // assign IIDs to our Characteristics
|
584 | for (const characteristic of this.characteristics) {
|
585 | characteristic._assignID(identifierCache, accessoryName, this.UUID, this.subtype);
|
586 | }
|
587 | }
|
588 | /**
|
589 | * Returns a JSON representation of this service suitable for delivering to HAP clients.
|
590 | * @private used to generate response to /accessories query
|
591 | */
|
592 | toHAP(connection, contactGetHandlers = true) {
|
593 | return new Promise(resolve => {
|
594 | (0, assert_1.default)(this.iid, "iid cannot be undefined for service '" + this.displayName + "'");
|
595 | (0, assert_1.default)(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!");
|
596 | const service = {
|
597 | type: (0, uuid_1.toShortForm)(this.UUID),
|
598 | iid: this.iid,
|
599 | characteristics: [],
|
600 | hidden: this.isHiddenService ? true : undefined,
|
601 | primary: this.isPrimaryService ? true : undefined,
|
602 | };
|
603 | if (this.linkedServices.length) {
|
604 | service.linked = [];
|
605 | for (const linked of this.linkedServices) {
|
606 | if (!linked.iid) {
|
607 | // we got a linked service which is not added to the accessory
|
608 | // as it doesn't "exists" we just ignore it.
|
609 | // we have some (at least one) plugins on homebridge which link to the AccessoryInformation service.
|
610 | // homebridge always creates its own AccessoryInformation service and ignores the user supplied one
|
611 | // thus the link is automatically broken.
|
612 | debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`);
|
613 | continue;
|
614 | }
|
615 | service.linked.push(linked.iid);
|
616 | }
|
617 | }
|
618 | const missingCharacteristics = new Set();
|
619 | let timeout = setTimeout(() => {
|
620 | for (const characteristic of missingCharacteristics) {
|
621 | this.emitCharacteristicWarningEvent(characteristic, "slow-read" /* CharacteristicWarningType.SLOW_READ */, `The read handler for the characteristic '${characteristic.displayName}' was slow to respond!`);
|
622 | }
|
623 | timeout = setTimeout(() => {
|
624 | timeout = undefined;
|
625 | for (const characteristic of missingCharacteristics) {
|
626 | this.emitCharacteristicWarningEvent(characteristic, "timeout-read" /* CharacteristicWarningType.TIMEOUT_READ */, "The read handler for the characteristic '" + characteristic?.displayName +
|
627 | "' didn't respond at all!. Please check that you properly call the callback!");
|
628 | service.characteristics.push(characteristic.internalHAPRepresentation()); // value is set to null
|
629 | }
|
630 | missingCharacteristics.clear();
|
631 | resolve(service);
|
632 | }, 6000);
|
633 | }, 3000);
|
634 | for (const characteristic of this.characteristics) {
|
635 | missingCharacteristics.add(characteristic);
|
636 | characteristic.toHAP(connection, contactGetHandlers).then(value => {
|
637 | if (!timeout) {
|
638 | return; // if timeout is undefined, response was already sent out
|
639 | }
|
640 | missingCharacteristics.delete(characteristic);
|
641 | service.characteristics.push(value);
|
642 | if (missingCharacteristics.size === 0) {
|
643 | if (timeout) {
|
644 | clearTimeout(timeout);
|
645 | timeout = undefined;
|
646 | }
|
647 | resolve(service);
|
648 | }
|
649 | });
|
650 | }
|
651 | });
|
652 | }
|
653 | /**
|
654 | * Returns a JSON representation of this service without characteristic values.
|
655 | * @private used to generate the config hash
|
656 | */
|
657 | internalHAPRepresentation() {
|
658 | (0, assert_1.default)(this.iid, "iid cannot be undefined for service '" + this.displayName + "'");
|
659 | (0, assert_1.default)(this.characteristics.length, "service '" + this.displayName + "' does not have any characteristics!");
|
660 | const service = {
|
661 | type: (0, uuid_1.toShortForm)(this.UUID),
|
662 | iid: this.iid,
|
663 | characteristics: this.characteristics.map(characteristic => characteristic.internalHAPRepresentation()),
|
664 | hidden: this.isHiddenService ? true : undefined,
|
665 | primary: this.isPrimaryService ? true : undefined,
|
666 | };
|
667 | if (this.linkedServices.length) {
|
668 | service.linked = [];
|
669 | for (const linked of this.linkedServices) {
|
670 | if (!linked.iid) {
|
671 | // we got a linked service which is not added to the accessory
|
672 | // as it doesn't "exists" we just ignore it.
|
673 | // we have some (at least one) plugins on homebridge which link to the AccessoryInformation service.
|
674 | // homebridge always creates its own AccessoryInformation service and ignores the user supplied one
|
675 | // thus the link is automatically broken.
|
676 | debug(`iid of linked service '${linked.displayName}' ${linked.UUID} is undefined on service '${this.displayName}'`);
|
677 | continue;
|
678 | }
|
679 | service.linked.push(linked.iid);
|
680 | }
|
681 | }
|
682 | return service;
|
683 | }
|
684 | /**
|
685 | * @private
|
686 | */
|
687 | setupCharacteristicEventHandlers(characteristic) {
|
688 | // listen for changes in characteristics and bubble them up
|
689 | characteristic.on("change" /* CharacteristicEventTypes.CHANGE */, (change) => {
|
690 | this.emit("characteristic-change" /* ServiceEventTypes.CHARACTERISTIC_CHANGE */, { ...change, characteristic: characteristic });
|
691 | });
|
692 | characteristic.on("characteristic-warning" /* CharacteristicEventTypes.CHARACTERISTIC_WARNING */, this.emitCharacteristicWarningEvent.bind(this, characteristic));
|
693 | }
|
694 | /**
|
695 | * @private
|
696 | */
|
697 | emitCharacteristicWarningEvent(characteristic, type, message, stack) {
|
698 | this.emit("characteristic-warning" /* ServiceEventTypes.CHARACTERISTIC_WARNING */, {
|
699 | characteristic: characteristic,
|
700 | type: type,
|
701 | message: message,
|
702 | originatorChain: [this.displayName, characteristic.displayName],
|
703 | stack: stack,
|
704 | });
|
705 | }
|
706 | /**
|
707 | * @private
|
708 | */
|
709 | _sideloadCharacteristics(targetCharacteristics) {
|
710 | for (const target of targetCharacteristics) {
|
711 | this.setupCharacteristicEventHandlers(target);
|
712 | }
|
713 | this.characteristics = targetCharacteristics.slice();
|
714 | }
|
715 | /**
|
716 | * @private
|
717 | */
|
718 | static serialize(service) {
|
719 | let constructorName;
|
720 | if (service.constructor.name !== "Service") {
|
721 | constructorName = service.constructor.name;
|
722 | }
|
723 | return {
|
724 | displayName: service.displayName,
|
725 | UUID: service.UUID,
|
726 | subtype: service.subtype,
|
727 | constructorName: constructorName,
|
728 | hiddenService: service.isHiddenService,
|
729 | primaryService: service.isPrimaryService,
|
730 | characteristics: service.characteristics.map(characteristic => Characteristic_1.Characteristic.serialize(characteristic)),
|
731 | optionalCharacteristics: service.optionalCharacteristics.map(characteristic => Characteristic_1.Characteristic.serialize(characteristic)),
|
732 | };
|
733 | }
|
734 | /**
|
735 | * @private
|
736 | */
|
737 | static deserialize(json) {
|
738 | let service;
|
739 | if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0)
|
740 | && Service[json.constructorName]) { // MUST start with uppercase character and must exist on Service object
|
741 | const constructor = Service[json.constructorName];
|
742 | service = new constructor(json.displayName, json.subtype);
|
743 | }
|
744 | else {
|
745 | service = new Service(json.displayName, json.UUID, json.subtype);
|
746 | }
|
747 | service.isHiddenService = !!json.hiddenService;
|
748 | service.isPrimaryService = !!json.primaryService;
|
749 | const characteristics = json.characteristics.map(serialized => Characteristic_1.Characteristic.deserialize(serialized));
|
750 | service._sideloadCharacteristics(characteristics);
|
751 | if (json.optionalCharacteristics) {
|
752 | service.optionalCharacteristics = json.optionalCharacteristics.map(serialized => Characteristic_1.Characteristic.deserialize(serialized));
|
753 | }
|
754 | return service;
|
755 | }
|
756 | }
|
757 | exports.Service = Service;
|
758 | // We have a cyclic dependency problem. Within this file we have the definitions of "./definitions" as
|
759 | // type imports only (in order to define the static properties). Setting those properties is done outside
|
760 | // this file, within the definition files. Therefore, we import it at the end of this file. Seems weird, but is important.
|
761 | require("./definitions/ServiceDefinitions");
|
762 | //# sourceMappingURL=Service.js.map |
\ | No newline at end of file |