• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

homebridge / HAP-NodeJS / 24964733939

26 Apr 2026 07:10PM UTC coverage: 64.646% (+1.2%) from 63.43%
24964733939

Pull #1113

github

web-flow
Merge 472e7d100 into 6098a0053
Pull Request #1113: v2.1.3

1863 of 3360 branches covered (55.45%)

Branch coverage included in aggregate %.

53 of 133 new or added lines in 8 files covered. (39.85%)

6413 of 9442 relevant lines covered (67.92%)

308.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.04
/src/lib/Characteristic.ts
1
import assert from "assert";
30✔
2
import createDebug from "debug";
30✔
3
import { EventEmitter } from "events";
30✔
4
import { CharacteristicJsonObject, CharacteristicValue, Nullable, PartialAllowingNull, VoidCallback } from "../types";
5
import { CharacteristicWarningType } from "./Accessory";
6
import type {
7
  AccessCodeControlPoint,
8
  AccessCodeSupportedConfiguration,
9
  AccessControlLevel,
10
  AccessoryFlags,
11
  AccessoryIdentifier,
12
  Active,
13
  ActiveIdentifier,
14
  ActivityInterval,
15
  AdministratorOnlyAccess,
16
  AirParticulateDensity,
17
  AirParticulateSize,
18
  AirPlayEnable,
19
  AirQuality,
20
  AppMatchingIdentifier,
21
  AssetUpdateReadiness,
22
  AudioFeedback,
23
  BatteryLevel,
24
  Brightness,
25
  ButtonEvent,
26
  CameraOperatingModeIndicator,
27
  CarbonDioxideDetected,
28
  CarbonDioxideLevel,
29
  CarbonDioxidePeakLevel,
30
  CarbonMonoxideDetected,
31
  CarbonMonoxideLevel,
32
  CarbonMonoxidePeakLevel,
33
  CCAEnergyDetectThreshold,
34
  CCASignalDetectThreshold,
35
  CharacteristicValueActiveTransitionCount,
36
  CharacteristicValueTransitionControl,
37
  ChargingState,
38
  ClosedCaptions,
39
  ColorTemperature,
40
  ConfigurationState,
41
  ConfiguredName,
42
  ConnectionHealthMonitor,
43
  ContactSensorState,
44
  CoolingThresholdTemperature,
45
  CryptoHash,
46
  CurrentAirPurifierState,
47
  CurrentAmbientLightLevel,
48
  CurrentDoorState,
49
  CurrentFanState,
50
  CurrentHeaterCoolerState,
51
  CurrentHeatingCoolingState,
52
  CurrentHorizontalTiltAngle,
53
  CurrentHumidifierDehumidifierState,
54
  CurrentMediaState,
55
  CurrentPosition,
56
  CurrentRelativeHumidity,
57
  CurrentSlatState,
58
  CurrentTemperature,
59
  CurrentTiltAngle,
60
  CurrentTransport,
61
  CurrentVerticalTiltAngle,
62
  CurrentVisibilityState,
63
  DataStreamHAPTransport,
64
  DataStreamHAPTransportInterrupt,
65
  DiagonalFieldOfView,
66
  DigitalZoom,
67
  DisplayOrder,
68
  EventRetransmissionMaximum,
69
  EventSnapshotsActive,
70
  EventTransmissionCounters,
71
  FilterChangeIndication,
72
  FilterLifeLevel,
73
  FirmwareRevision,
74
  FirmwareUpdateProtocolList,
75
  FirmwareUpdateReadiness,
76
  FirmwareUpdateStatus,
77
  HardwareFinish,
78
  HardwareRevision,
79
  HeartBeat,
80
  HeatingThresholdTemperature,
81
  HoldPosition,
82
  HomeKitCameraActive,
83
  Hue,
84
  Identifier,
85
  Identify,
86
  ImageMirroring,
87
  ImageRotation,
88
  InputDeviceType,
89
  InputSourceType,
90
  InUse,
91
  IsConfigured,
92
  LeakDetected,
93
  ListPairings,
94
  LockControlPoint,
95
  LockCurrentState,
96
  LockLastKnownAction,
97
  LockManagementAutoSecurityTimeout,
98
  LockPhysicalControls,
99
  LockTargetState,
100
  Logs,
101
  MACRetransmissionMaximum,
102
  MACTransmissionCounters,
103
  ManagedNetworkEnable,
104
  ManuallyDisabled,
105
  Manufacturer,
106
  MatterFirmwareRevisionNumber,
107
  MatterFirmwareUpdateStatus,
108
  MaximumTransmitPower,
109
  MediaAccessControlLevel,
110
  MetricsBufferFullState,
111
  Model,
112
  MotionDetected,
113
  MultifunctionButton,
114
  Mute,
115
  Name,
116
  NetworkAccessViolationControl,
117
  NetworkClientProfileControl,
118
  NetworkClientStatusControl,
119
  NFCAccessControlPoint,
120
  NFCAccessSupportedConfiguration,
121
  NightVision,
122
  NitrogenDioxideDensity,
123
  ObstructionDetected,
124
  OccupancyDetected,
125
  On,
126
  OperatingStateResponse,
127
  OpticalZoom,
128
  OutletInUse,
129
  OzoneDensity,
130
  PairingFeatures,
131
  PairSetup,
132
  PairVerify,
133
  PasswordSetting,
134
  PeriodicSnapshotsActive,
135
  PictureMode,
136
  Ping,
137
  PM10Density,
138
  PM2_5Density,
139
  PositionState,
140
  PowerModeSelection,
141
  PreferredWiFiList,
142
  ProductData,
143
  ProgrammableSwitchEvent,
144
  ProgrammableSwitchOutputState,
145
  ProgramMode,
146
  ReceivedSignalStrengthIndication,
147
  ReceiverSensitivity,
148
  RecordingAudioActive,
149
  RelativeHumidityDehumidifierThreshold,
150
  RelativeHumidityHumidifierThreshold,
151
  RelayControlPoint,
152
  RelayEnabled,
153
  RelayState,
154
  RemainingDuration,
155
  RemoteKey,
156
  ResetFilterIndication,
157
  RotationDirection,
158
  RotationSpeed,
159
  RouterStatus,
160
  Saturation,
161
  SecuritySystemAlarmType,
162
  SecuritySystemCurrentState,
163
  SecuritySystemTargetState,
164
  SelectedAudioStreamConfiguration,
165
  SelectedCameraRecordingConfiguration,
166
  SelectedDiagnosticsModes,
167
  SelectedRTPStreamConfiguration,
168
  SelectedSleepConfiguration,
169
  SerialNumber,
170
  ServiceLabelIndex,
171
  ServiceLabelNamespace,
172
  SetDuration,
173
  SetupDataStreamTransport,
174
  SetupEndpoints,
175
  SetupTransferTransport,
176
  SignalToNoiseRatio,
177
  SiriEnable,
178
  SiriEndpointSessionStatus,
179
  SiriEngineVersion,
180
  SiriInputType,
181
  SiriLightOnUse,
182
  SiriListening,
183
  SiriTouchToUse,
184
  SlatType,
185
  SleepDiscoveryMode,
186
  SleepInterval,
187
  SmokeDetected,
188
  SoftwareRevision,
189
  StagedFirmwareVersion,
190
  StatusActive,
191
  StatusFault,
192
  StatusJammed,
193
  StatusLowBattery,
194
  StatusTampered,
195
  StreamingStatus,
196
  SulphurDioxideDensity,
197
  SupportedAssetTypes,
198
  SupportedAudioRecordingConfiguration,
199
  SupportedAudioStreamConfiguration,
200
  SupportedCameraRecordingConfiguration,
201
  SupportedCharacteristicValueTransitionConfiguration,
202
  SupportedDataStreamTransportConfiguration,
203
  SupportedDiagnosticsModes,
204
  SupportedDiagnosticsSnapshot,
205
  SupportedFirmwareUpdateConfiguration,
206
  SupportedMetrics,
207
  SupportedRouterConfiguration,
208
  SupportedRTPConfiguration,
209
  SupportedSleepConfiguration,
210
  SupportedTransferTransportConfiguration,
211
  SupportedVideoRecordingConfiguration,
212
  SupportedVideoStreamConfiguration,
213
  SwingMode,
214
  TapType,
215
  TargetAirPurifierState,
216
  TargetControlList,
217
  TargetControlSupportedConfiguration,
218
  TargetDoorState,
219
  TargetFanState,
220
  TargetHeaterCoolerState,
221
  TargetHeatingCoolingState,
222
  TargetHorizontalTiltAngle,
223
  TargetHumidifierDehumidifierState,
224
  TargetMediaState,
225
  TargetPosition,
226
  TargetRelativeHumidity,
227
  TargetTemperature,
228
  TargetTiltAngle,
229
  TargetVerticalTiltAngle,
230
  TargetVisibilityState,
231
  TemperatureDisplayUnits,
232
  ThirdPartyCameraActive,
233
  ThreadControlPoint,
234
  ThreadNodeCapabilities,
235
  ThreadOpenThreadVersion,
236
  ThreadStatus,
237
  Token,
238
  TransmitPower,
239
  TunnelConnectionTimeout,
240
  TunneledAccessoryAdvertising,
241
  TunneledAccessoryConnected,
242
  TunneledAccessoryStateNumber,
243
  ValveType,
244
  Version,
245
  VideoAnalysisActive,
246
  VOCDensity,
247
  Volume,
248
  VolumeControlType,
249
  VolumeSelector,
250
  WakeConfiguration,
251
  WANConfigurationList,
252
  WANStatusList,
253
  WaterLevel,
254
  WiFiCapabilities,
255
  WiFiConfigurationControl,
256
  WiFiSatelliteStatus,
257
} from "./definitions";
258
import { HAPStatus, IsKnownHAPStatusError } from "./HAPServer";
30✔
259
import { IdentifierCache } from "./model/IdentifierCache";
260
import { clone } from "./util/clone";
30✔
261
import { HAPConnection } from "./util/eventedhttp";
262
import { HapStatusError } from "./util/hapStatusError";
30✔
263
import { once } from "./util/once";
30✔
264
import {
30✔
265
  formatOutgoingCharacteristicValue,
266
  isIntegerNumericFormat,
267
  isNumericFormat,
268
  isUnsignedNumericFormat,
269
  numericLowerBound,
270
  numericUpperBound,
271
} from "./util/request-util";
272
import { BASE_UUID, toShortForm } from "./util/uuid";
30✔
273
import { checkName } from "./util/checkName";
30✔
274

275
const debug = createDebug("HAP-NodeJS:Characteristic");
30✔
276

277
/**
278
 * @group Characteristic
279
 */
280
export const enum Formats {
30✔
281
  BOOL = "bool",
30✔
282
  /**
283
   * Signed 32-bit integer
284
   */
285
  INT = "int", // signed 32-bit int
30✔
286
  /**
287
   * Signed 64-bit floating point
288
   */
289
  FLOAT = "float",
30✔
290
  /**
291
   * String encoded in utf8
292
   */
293
  STRING = "string",
30✔
294
  /**
295
   * Unsigned 8-bit integer.
296
   */
297
  UINT8 = "uint8",
30✔
298
  /**
299
   * Unsigned 16-bit integer.
300
   */
301
  UINT16 = "uint16",
30✔
302
  /**
303
   * Unsigned 32-bit integer.
304
   */
305
  UINT32 = "uint32",
30✔
306
  /**
307
   * Unsigned 64-bit integer.
308
   */
309
  UINT64 = "uint64",
30✔
310
  /**
311
   * Data is base64 encoded string.
312
   */
313
  DATA = "data",
30✔
314
  /**
315
   * Base64 encoded tlv8 string.
316
   */
317
  TLV8 = "tlv8"
30✔
318
}
319

320
/**
321
 * @group Characteristic
322
 */
323
export const enum Units {
30✔
324
  /**
325
   * Celsius is the only temperature unit in the HomeKit Accessory Protocol.
326
   * Unit conversion is always done on the client side e.g. on the iPhone in the Home App depending on
327
   * the configured unit on the device itself.
328
   */
329
  CELSIUS = "celsius",
30✔
330
  PERCENTAGE = "percentage",
30✔
331
  ARC_DEGREE = "arcdegrees",
30✔
332
  LUX = "lux",
30✔
333
  SECONDS = "seconds",
30✔
334
}
335

336
/**
337
 * @group Characteristic
338
 */
339
export const enum Perms {
30✔
340
  PAIRED_READ = "pr",
30✔
341
  PAIRED_WRITE = "pw",
30✔
342
  NOTIFY = "ev",
30✔
343
  // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
344
  EVENTS = "ev",
30✔
345
  ADDITIONAL_AUTHORIZATION = "aa",
30✔
346
  TIMED_WRITE = "tw",
30✔
347
  HIDDEN = "hd",
30✔
348
  WRITE_RESPONSE = "wr",
30✔
349
}
350

351
/**
352
 * @group Characteristic
353
 */
354
export interface CharacteristicProps {
355
  format: Formats | string;
356
  perms: Perms[];
357
  unit?: Units | string;
358
  description?: string;
359
  /**
360
   * Defines the minimum value for a numeric characteristic
361
   */
362
  minValue?: number;
363
  /**
364
   * Defines the maximum value for a numeric characteristic
365
   */
366
  maxValue?: number;
367
  minStep?: number;
368
  /**
369
   * Maximum number of characters when format is {@link Formats.STRING}.
370
   * Default is 64 characters. Maximum allowed is 256 characters.
371
   */
372
  maxLen?: number;
373
  /**
374
   * Maximum number of characters when format is {@link Formats.DATA}.
375
   * Default is 2097152 characters.
376
   */
377
  maxDataLen?: number;
378
  /**
379
   * Defines an array of valid values to be used for the characteristic.
380
   */
381
  validValues?: number[];
382
  /**
383
   * Two element array where the first value specifies the lowest valid value and
384
   * the second element specifies the highest valid value.
385
   */
386
  validValueRanges?: [min: number, max: number];
387
  adminOnlyAccess?: Access[];
388
}
389

390
/**
391
 * Describes the abstract access to a {@link Characteristic}.
392
 * It abstracts the more granular access described by {@link Perms}.
393
 *
394
 * It is used in {@link CharacteristicProps.adminOnlyAccess}.
395
 *
396
 * @group Characteristic
397
 */
398
export const enum Access {
30✔
399
  READ = 0x00,
30✔
400
  WRITE = 0x01,
30✔
401
  NOTIFY = 0x02
30✔
402
}
403

404
/**
405
 * @group Characteristic
406
 */
407
export type CharacteristicChange = {
408
  originator?: HAPConnection,
409
  newValue: Nullable<CharacteristicValue>;
410
  oldValue: Nullable<CharacteristicValue>;
411
  reason: ChangeReason,
412
  context?: CharacteristicContext;
413
};
414

415
/**
416
 * @group Characteristic
417
 */
418
export const enum ChangeReason {
30✔
419
  /**
420
   * Reason used when HomeKit writes a value or the API user calls {@link Characteristic.setValue}.
421
   */
422
  WRITE = "write",
30✔
423
  /**
424
   * Reason used when the API user calls the method {@link Characteristic.updateValue}.
425
   */
426
  UPDATE = "update",
30✔
427
  /**
428
   * Used when HomeKit reads a value or the API user calls the deprecated method `Characteristic.getValue`.
429
   */
430
  READ = "read",
30✔
431
  /**
432
   * Used when call to {@link Characteristic.sendEventNotification} was made.
433
   */
434
  EVENT = "event",
30✔
435
}
436

437
/**
438
 * This format for a context object can be used to pass to any characteristic write operation.
439
 * It can contain additional information used by the internal event handlers of hap-nodejs.
440
 * The context object can be combined with any custom data for own use.
441
 *
442
 * @group Characteristic
443
 */
444
export interface CharacteristicOperationContext {
445
  /**
446
   * If set to true for any characteristic write operation
447
   * the Accessory won't send any event notifications to HomeKit controllers
448
   * for that particular change.
449
   */
450
  omitEventUpdate?: boolean;
451
}
452

453
/**
454
 * @group Characteristic
455
 */
456
export interface SerializedCharacteristic {
457
  displayName: string,
458
  UUID: string,
459
  eventOnlyCharacteristic: boolean,
460
  constructorName?: string,
461

462
  value: Nullable<CharacteristicValue>,
463
  props: CharacteristicProps,
464
}
465

466
/**
467
 * @group Characteristic
468
 */
469
export const enum CharacteristicEventTypes {
30✔
470
  /**
471
   * This event is thrown when a HomeKit controller wants to read the current value of the characteristic.
472
   * The event handler should call the supplied callback as fast as possible.
473
   *
474
   * HAP-NodeJS will complain about slow running get handlers after 3 seconds and terminate the request after 10 seconds.
475
   */
476
  GET = "get",
30✔
477
  /**
478
   * This event is thrown when a HomeKit controller wants to write a new value to the characteristic.
479
   * The event handler should call the supplied callback as fast as possible.
480
   *
481
   * HAP-NodeJS will complain about slow running set handlers after 3 seconds and terminate the request after 10 seconds.
482
   */
483
  SET = "set",
30✔
484
  /**
485
   * Emitted after a new value is set for the characteristic.
486
   * The new value can be set via a request by a HomeKit controller or via an API call.
487
   */
488
  CHANGE = "change",
30✔
489
  /**
490
   * @private
491
   */
492
  SUBSCRIBE = "subscribe",
30✔
493
  /**
494
   * @private
495
   */
496
  UNSUBSCRIBE = "unsubscribe",
30✔
497
  /**
498
   * @private
499
   */
500
  CHARACTERISTIC_WARNING = "characteristic-warning",
30✔
501
}
502

503
/**
504
 * @group Characteristic
505
 */
506
export type CharacteristicContext = any; // eslint-disable-line @typescript-eslint/no-explicit-any
507

508
/**
509
 * @group Characteristic
510
 */
511
export type CharacteristicGetCallback = (status?: HAPStatus | null | Error, value?: Nullable<CharacteristicValue>) => void;
512
/**
513
 * @group Characteristic
514
 */
515
export type CharacteristicSetCallback = (error?: HAPStatus | null | Error, writeResponse?: Nullable<CharacteristicValue>) => void;
516
/**
517
 * @group Characteristic
518
 */
519
export type CharacteristicGetHandler = (context: CharacteristicContext, connection?: HAPConnection)
520
  => Promise<Nullable<CharacteristicValue>> | Nullable<CharacteristicValue>;
521
/**
522
 * @group Characteristic
523
 */
524
export type CharacteristicSetHandler = (value: CharacteristicValue, context: CharacteristicContext, connection?: HAPConnection)
525
  => Promise<Nullable<CharacteristicValue> | void> | Nullable<CharacteristicValue> | void;
526

527
/**
528
 * @group Characteristic
529
 */
530
export type AdditionalAuthorizationHandler = (additionalAuthorizationData: string | undefined) => boolean;
531

532
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
533
export declare interface Characteristic {
534

535
  on(event: "get", listener: (callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection) => void): this;
536
  on(
537
    event: "set",
538
    listener: (value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicContext, connection?: HAPConnection) => void
539
  ): this
540
  on(event: "change", listener: (change: CharacteristicChange) => void): this;
541
  /**
542
   * @private
543
   */
544
  on(event: "subscribe", listener: VoidCallback): this;
545
  /**
546
   * @private
547
   */
548
  on(event: "unsubscribe", listener: VoidCallback): this;
549
  /**
550
   * @private
551
   */
552
  on(event: "characteristic-warning", listener: (type: CharacteristicWarningType, message: string, stack?: string) => void): this;
553

554
  /**
555
   * @private
556
   */
557
  emit(event: "get", callback: CharacteristicGetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean;
558
  /**
559
   * @private
560
   */
561
  emit(event: "set", value: CharacteristicValue, callback: CharacteristicSetCallback, context: CharacteristicContext, connection?: HAPConnection): boolean;
562
  /**
563
   * @private
564
   */
565
  emit(event: "change", change: CharacteristicChange): boolean;
566
  /**
567
   * @private
568
   */
569
  emit(event: "subscribe"): boolean;
570
  /**
571
   * @private
572
   */
573
  emit(event: "unsubscribe"): boolean;
574
  /**
575
   * @private
576
   */
577
  emit(event: "characteristic-warning", type: CharacteristicWarningType, message: string, stack?: string): boolean;
578

579
}
580

581
/**
582
 * @group Characteristic
583
 */
584
class ValidValuesIterable implements Iterable<number> {
585

586
  private readonly props: CharacteristicProps;
587

588
  constructor(props: CharacteristicProps) {
589
    assert(isNumericFormat(props.format), "Cannot instantiate valid values iterable when format is not numeric. Found " + props.format);
15✔
590
    this.props = props;
15✔
591
  }
592

593
  *[Symbol.iterator](): Iterator<number> {
594
    if (this.props.validValues) {
15✔
595
      for (const value of this.props.validValues) {
3✔
596
        yield value;
15✔
597
      }
598
    } else {
599
      let min = 0; // default is zero for all the uint types
12✔
600
      let max: number;
601
      let stepValue = 1;
12✔
602

603
      if (this.props.validValueRanges) {
12✔
604
        min = this.props.validValueRanges[0];
3✔
605
        max = this.props.validValueRanges[1];
3✔
606
      } else if (this.props.minValue != null && this.props.maxValue != null) {
9✔
607
        min = this.props.minValue;
6✔
608
        max = this.props.maxValue;
6✔
609
        if (this.props.minStep != null) {
6✔
610
          stepValue = this.props.minStep;
3✔
611
        }
612
      } else if (isUnsignedNumericFormat(this.props.format)) {
3!
613
        max = numericUpperBound(this.props.format);
3✔
614
      } else {
615
        throw new Error("Could not find valid iterator strategy for props: " + JSON.stringify(this.props));
×
616
      }
617

618
      for (let i = min; i <= max; i += stepValue) {
12✔
619
        yield i;
807✔
620
      }
621
    }
622
  }
623

624
}
625

626
const numberPattern = /^-?\d+$/;
30✔
627
function extractHAPStatusFromError(error: Error) {
628
  let errorValue = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
6✔
629

630
  if (numberPattern.test(error.message)) {
6!
631
    const value = parseInt(error.message, 10);
×
632

633
    if (IsKnownHAPStatusError(value)) {
×
634
      errorValue = value;
×
635
    }
636
  }
637

638
  return errorValue;
6✔
639
}
640

641
function maxWithUndefined(a?: number, b?: number): number | undefined {
642
  if (a == null) {
8,889✔
643
    return b;
3,249✔
644
  } else if (b == null) {
5,640!
645
    return a;
×
646
  } else {
647
    return Math.max(a, b);
5,640✔
648
  }
649
}
650

651
function minWithUndefined(a?: number, b?: number): number | undefined {
652
  if (a == null) {
4,638✔
653
    return b;
999✔
654
  } else if (b == null) {
3,639!
655
    return a;
×
656
  } else {
657
    return Math.min(a, b);
3,639✔
658
  }
659
}
660

661
/**
662
 * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a
663
 * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic
664
 * to a {@link Service} in order to store that value. A particular Characteristic is distinguished from others by its
665
 * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKit.ts along with a
666
 * corresponding concrete subclass.
667
 *
668
 * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added
669
 * to any native or custom Services, but Siri will likely not be able to work with these.
670
 *
671
 * @group Characteristic
672
 */
673
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
674
export class Characteristic extends EventEmitter {
30✔
675

676
  // Pattern below is for automatic detection of the section of defined characteristics. Used by the generator
677
  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-
678
  /**
679
   * @group Characteristic Definitions
680
   */
681
  public static AccessCodeControlPoint: typeof AccessCodeControlPoint;
682
  /**
683
   * @group Characteristic Definitions
684
   */
685
  public static AccessCodeSupportedConfiguration: typeof AccessCodeSupportedConfiguration;
686
  /**
687
   * @group Characteristic Definitions
688
   */
689
  public static AccessControlLevel: typeof AccessControlLevel;
690
  /**
691
   * @group Characteristic Definitions
692
   */
693
  public static AccessoryFlags: typeof AccessoryFlags;
694
  /**
695
   * @group Characteristic Definitions
696
   */
697
  public static AccessoryIdentifier: typeof AccessoryIdentifier;
698
  /**
699
   * @group Characteristic Definitions
700
   */
701
  public static Active: typeof Active;
702
  /**
703
   * @group Characteristic Definitions
704
   */
705
  public static ActiveIdentifier: typeof ActiveIdentifier;
706
  /**
707
   * @group Characteristic Definitions
708
   */
709
  public static ActivityInterval: typeof ActivityInterval;
710
  /**
711
   * @group Characteristic Definitions
712
   */
713
  public static AdministratorOnlyAccess: typeof AdministratorOnlyAccess;
714
  /**
715
   * @group Characteristic Definitions
716
   */
717
  public static AirParticulateDensity: typeof AirParticulateDensity;
718
  /**
719
   * @group Characteristic Definitions
720
   */
721
  public static AirParticulateSize: typeof AirParticulateSize;
722
  /**
723
   * @group Characteristic Definitions
724
   */
725
  public static AirPlayEnable: typeof AirPlayEnable;
726
  /**
727
   * @group Characteristic Definitions
728
   */
729
  public static AirQuality: typeof AirQuality;
730
  /**
731
   * @group Characteristic Definitions
732
   */
733
  public static AppMatchingIdentifier: typeof AppMatchingIdentifier;
734
  /**
735
   * @group Characteristic Definitions
736
   */
737
  public static AssetUpdateReadiness: typeof AssetUpdateReadiness;
738
  /**
739
   * @group Characteristic Definitions
740
   */
741
  public static AudioFeedback: typeof AudioFeedback;
742
  /**
743
   * @group Characteristic Definitions
744
   */
745
  public static BatteryLevel: typeof BatteryLevel;
746
  /**
747
   * @group Characteristic Definitions
748
   */
749
  public static Brightness: typeof Brightness;
750
  /**
751
   * @group Characteristic Definitions
752
   */
753
  public static ButtonEvent: typeof ButtonEvent;
754
  /**
755
   * @group Characteristic Definitions
756
   */
757
  public static CameraOperatingModeIndicator: typeof CameraOperatingModeIndicator;
758
  /**
759
   * @group Characteristic Definitions
760
   */
761
  public static CarbonDioxideDetected: typeof CarbonDioxideDetected;
762
  /**
763
   * @group Characteristic Definitions
764
   */
765
  public static CarbonDioxideLevel: typeof CarbonDioxideLevel;
766
  /**
767
   * @group Characteristic Definitions
768
   */
769
  public static CarbonDioxidePeakLevel: typeof CarbonDioxidePeakLevel;
770
  /**
771
   * @group Characteristic Definitions
772
   */
773
  public static CarbonMonoxideDetected: typeof CarbonMonoxideDetected;
774
  /**
775
   * @group Characteristic Definitions
776
   */
777
  public static CarbonMonoxideLevel: typeof CarbonMonoxideLevel;
778
  /**
779
   * @group Characteristic Definitions
780
   */
781
  public static CarbonMonoxidePeakLevel: typeof CarbonMonoxidePeakLevel;
782
  /**
783
   * @group Characteristic Definitions
784
   */
785
  public static CCAEnergyDetectThreshold: typeof CCAEnergyDetectThreshold;
786
  /**
787
   * @group Characteristic Definitions
788
   */
789
  public static CCASignalDetectThreshold: typeof CCASignalDetectThreshold;
790
  /**
791
   * @group Characteristic Definitions
792
   */
793
  public static CharacteristicValueActiveTransitionCount: typeof CharacteristicValueActiveTransitionCount;
794
  /**
795
   * @group Characteristic Definitions
796
   */
797
  public static CharacteristicValueTransitionControl: typeof CharacteristicValueTransitionControl;
798
  /**
799
   * @group Characteristic Definitions
800
   */
801
  public static ChargingState: typeof ChargingState;
802
  /**
803
   * @group Characteristic Definitions
804
   */
805
  public static ClosedCaptions: typeof ClosedCaptions;
806
  /**
807
   * @group Characteristic Definitions
808
   */
809
  public static ColorTemperature: typeof ColorTemperature;
810
  /**
811
   * @group Characteristic Definitions
812
   */
813
  public static ConfigurationState: typeof ConfigurationState;
814
  /**
815
   * @group Characteristic Definitions
816
   */
817
  public static ConfiguredName: typeof ConfiguredName;
818
  /**
819
   * @group Characteristic Definitions
820
   */
821
  public static ConnectionHealthMonitor: typeof ConnectionHealthMonitor;
822
  /**
823
   * @group Characteristic Definitions
824
   */
825
  public static ContactSensorState: typeof ContactSensorState;
826
  /**
827
   * @group Characteristic Definitions
828
   */
829
  public static CoolingThresholdTemperature: typeof CoolingThresholdTemperature;
830
  /**
831
   * @group Characteristic Definitions
832
   */
833
  public static CryptoHash: typeof CryptoHash;
834
  /**
835
   * @group Characteristic Definitions
836
   */
837
  public static CurrentAirPurifierState: typeof CurrentAirPurifierState;
838
  /**
839
   * @group Characteristic Definitions
840
   */
841
  public static CurrentAmbientLightLevel: typeof CurrentAmbientLightLevel;
842
  /**
843
   * @group Characteristic Definitions
844
   */
845
  public static CurrentDoorState: typeof CurrentDoorState;
846
  /**
847
   * @group Characteristic Definitions
848
   */
849
  public static CurrentFanState: typeof CurrentFanState;
850
  /**
851
   * @group Characteristic Definitions
852
   */
853
  public static CurrentHeaterCoolerState: typeof CurrentHeaterCoolerState;
854
  /**
855
   * @group Characteristic Definitions
856
   */
857
  public static CurrentHeatingCoolingState: typeof CurrentHeatingCoolingState;
858
  /**
859
   * @group Characteristic Definitions
860
   */
861
  public static CurrentHorizontalTiltAngle: typeof CurrentHorizontalTiltAngle;
862
  /**
863
   * @group Characteristic Definitions
864
   */
865
  public static CurrentHumidifierDehumidifierState: typeof CurrentHumidifierDehumidifierState;
866
  /**
867
   * @group Characteristic Definitions
868
   */
869
  public static CurrentMediaState: typeof CurrentMediaState;
870
  /**
871
   * @group Characteristic Definitions
872
   */
873
  public static CurrentPosition: typeof CurrentPosition;
874
  /**
875
   * @group Characteristic Definitions
876
   */
877
  public static CurrentRelativeHumidity: typeof CurrentRelativeHumidity;
878
  /**
879
   * @group Characteristic Definitions
880
   */
881
  public static CurrentSlatState: typeof CurrentSlatState;
882
  /**
883
   * @group Characteristic Definitions
884
   */
885
  public static CurrentTemperature: typeof CurrentTemperature;
886
  /**
887
   * @group Characteristic Definitions
888
   */
889
  public static CurrentTiltAngle: typeof CurrentTiltAngle;
890
  /**
891
   * @group Characteristic Definitions
892
   */
893
  public static CurrentTransport: typeof CurrentTransport;
894
  /**
895
   * @group Characteristic Definitions
896
   */
897
  public static CurrentVerticalTiltAngle: typeof CurrentVerticalTiltAngle;
898
  /**
899
   * @group Characteristic Definitions
900
   */
901
  public static CurrentVisibilityState: typeof CurrentVisibilityState;
902
  /**
903
   * @group Characteristic Definitions
904
   */
905
  public static DataStreamHAPTransport: typeof DataStreamHAPTransport;
906
  /**
907
   * @group Characteristic Definitions
908
   */
909
  public static DataStreamHAPTransportInterrupt: typeof DataStreamHAPTransportInterrupt;
910
  /**
911
   * @group Characteristic Definitions
912
   */
913
  public static DiagonalFieldOfView: typeof DiagonalFieldOfView;
914
  /**
915
   * @group Characteristic Definitions
916
   */
917
  public static DigitalZoom: typeof DigitalZoom;
918
  /**
919
   * @group Characteristic Definitions
920
   */
921
  public static DisplayOrder: typeof DisplayOrder;
922
  /**
923
   * @group Characteristic Definitions
924
   */
925
  public static EventRetransmissionMaximum: typeof EventRetransmissionMaximum;
926
  /**
927
   * @group Characteristic Definitions
928
   */
929
  public static EventSnapshotsActive: typeof EventSnapshotsActive;
930
  /**
931
   * @group Characteristic Definitions
932
   */
933
  public static EventTransmissionCounters: typeof EventTransmissionCounters;
934
  /**
935
   * @group Characteristic Definitions
936
   */
937
  public static FilterChangeIndication: typeof FilterChangeIndication;
938
  /**
939
   * @group Characteristic Definitions
940
   */
941
  public static FilterLifeLevel: typeof FilterLifeLevel;
942
  /**
943
   * @group Characteristic Definitions
944
   */
945
  public static FirmwareRevision: typeof FirmwareRevision;
946
  /**
947
   * @group Characteristic Definitions
948
   */
949
  public static FirmwareUpdateProtocolList: typeof FirmwareUpdateProtocolList;
950
  /**
951
   * @group Characteristic Definitions
952
   */
953
  public static FirmwareUpdateReadiness: typeof FirmwareUpdateReadiness;
954
  /**
955
   * @group Characteristic Definitions
956
   */
957
  public static FirmwareUpdateStatus: typeof FirmwareUpdateStatus;
958
  /**
959
   * @group Characteristic Definitions
960
   */
961
  public static HardwareFinish: typeof HardwareFinish;
962
  /**
963
   * @group Characteristic Definitions
964
   */
965
  public static HardwareRevision: typeof HardwareRevision;
966
  /**
967
   * @group Characteristic Definitions
968
   */
969
  public static HeartBeat: typeof HeartBeat;
970
  /**
971
   * @group Characteristic Definitions
972
   */
973
  public static HeatingThresholdTemperature: typeof HeatingThresholdTemperature;
974
  /**
975
   * @group Characteristic Definitions
976
   */
977
  public static HoldPosition: typeof HoldPosition;
978
  /**
979
   * @group Characteristic Definitions
980
   */
981
  public static HomeKitCameraActive: typeof HomeKitCameraActive;
982
  /**
983
   * @group Characteristic Definitions
984
   */
985
  public static Hue: typeof Hue;
986
  /**
987
   * @group Characteristic Definitions
988
   */
989
  public static Identifier: typeof Identifier;
990
  /**
991
   * @group Characteristic Definitions
992
   */
993
  public static Identify: typeof Identify;
994
  /**
995
   * @group Characteristic Definitions
996
   */
997
  public static ImageMirroring: typeof ImageMirroring;
998
  /**
999
   * @group Characteristic Definitions
1000
   */
1001
  public static ImageRotation: typeof ImageRotation;
1002
  /**
1003
   * @group Characteristic Definitions
1004
   */
1005
  public static InputDeviceType: typeof InputDeviceType;
1006
  /**
1007
   * @group Characteristic Definitions
1008
   */
1009
  public static InputSourceType: typeof InputSourceType;
1010
  /**
1011
   * @group Characteristic Definitions
1012
   */
1013
  public static InUse: typeof InUse;
1014
  /**
1015
   * @group Characteristic Definitions
1016
   */
1017
  public static IsConfigured: typeof IsConfigured;
1018
  /**
1019
   * @group Characteristic Definitions
1020
   */
1021
  public static LeakDetected: typeof LeakDetected;
1022
  /**
1023
   * @group Characteristic Definitions
1024
   */
1025
  public static ListPairings: typeof ListPairings;
1026
  /**
1027
   * @group Characteristic Definitions
1028
   */
1029
  public static LockControlPoint: typeof LockControlPoint;
1030
  /**
1031
   * @group Characteristic Definitions
1032
   */
1033
  public static LockCurrentState: typeof LockCurrentState;
1034
  /**
1035
   * @group Characteristic Definitions
1036
   */
1037
  public static LockLastKnownAction: typeof LockLastKnownAction;
1038
  /**
1039
   * @group Characteristic Definitions
1040
   */
1041
  public static LockManagementAutoSecurityTimeout: typeof LockManagementAutoSecurityTimeout;
1042
  /**
1043
   * @group Characteristic Definitions
1044
   */
1045
  public static LockPhysicalControls: typeof LockPhysicalControls;
1046
  /**
1047
   * @group Characteristic Definitions
1048
   */
1049
  public static LockTargetState: typeof LockTargetState;
1050
  /**
1051
   * @group Characteristic Definitions
1052
   */
1053
  public static Logs: typeof Logs;
1054
  /**
1055
   * @group Characteristic Definitions
1056
   */
1057
  public static MACRetransmissionMaximum: typeof MACRetransmissionMaximum;
1058
  /**
1059
   * @group Characteristic Definitions
1060
   */
1061
  public static MACTransmissionCounters: typeof MACTransmissionCounters;
1062
  /**
1063
   * @group Characteristic Definitions
1064
   */
1065
  public static ManagedNetworkEnable: typeof ManagedNetworkEnable;
1066
  /**
1067
   * @group Characteristic Definitions
1068
   */
1069
  public static ManuallyDisabled: typeof ManuallyDisabled;
1070
  /**
1071
   * @group Characteristic Definitions
1072
   */
1073
  public static Manufacturer: typeof Manufacturer;
1074
  /**
1075
   * @group Characteristic Definitions
1076
   */
1077
  public static MatterFirmwareRevisionNumber: typeof MatterFirmwareRevisionNumber;
1078
  /**
1079
   * @group Characteristic Definitions
1080
   */
1081
  public static MatterFirmwareUpdateStatus: typeof MatterFirmwareUpdateStatus;
1082
  /**
1083
   * @group Characteristic Definitions
1084
   */
1085
  public static MaximumTransmitPower: typeof MaximumTransmitPower;
1086
  /**
1087
   * @group Characteristic Definitions
1088
   */
1089
  public static MediaAccessControlLevel: typeof MediaAccessControlLevel;
1090
  /**
1091
   * @group Characteristic Definitions
1092
   */
1093
  public static MetricsBufferFullState: typeof MetricsBufferFullState;
1094
  /**
1095
   * @group Characteristic Definitions
1096
   */
1097
  public static Model: typeof Model;
1098
  /**
1099
   * @group Characteristic Definitions
1100
   */
1101
  public static MotionDetected: typeof MotionDetected;
1102
  /**
1103
   * @group Characteristic Definitions
1104
   */
1105
  public static MultifunctionButton: typeof MultifunctionButton;
1106
  /**
1107
   * @group Characteristic Definitions
1108
   */
1109
  public static Mute: typeof Mute;
1110
  /**
1111
   * @group Characteristic Definitions
1112
   */
1113
  public static Name: typeof Name;
1114
  /**
1115
   * @group Characteristic Definitions
1116
   */
1117
  public static NetworkAccessViolationControl: typeof NetworkAccessViolationControl;
1118
  /**
1119
   * @group Characteristic Definitions
1120
   */
1121
  public static NetworkClientProfileControl: typeof NetworkClientProfileControl;
1122
  /**
1123
   * @group Characteristic Definitions
1124
   */
1125
  public static NetworkClientStatusControl: typeof NetworkClientStatusControl;
1126
  /**
1127
   * @group Characteristic Definitions
1128
   */
1129
  public static NFCAccessControlPoint: typeof NFCAccessControlPoint;
1130
  /**
1131
   * @group Characteristic Definitions
1132
   */
1133
  public static NFCAccessSupportedConfiguration: typeof NFCAccessSupportedConfiguration;
1134
  /**
1135
   * @group Characteristic Definitions
1136
   */
1137
  public static NightVision: typeof NightVision;
1138
  /**
1139
   * @group Characteristic Definitions
1140
   */
1141
  public static NitrogenDioxideDensity: typeof NitrogenDioxideDensity;
1142
  /**
1143
   * @group Characteristic Definitions
1144
   */
1145
  public static ObstructionDetected: typeof ObstructionDetected;
1146
  /**
1147
   * @group Characteristic Definitions
1148
   */
1149
  public static OccupancyDetected: typeof OccupancyDetected;
1150
  /**
1151
   * @group Characteristic Definitions
1152
   */
1153
  public static On: typeof On;
1154
  /**
1155
   * @group Characteristic Definitions
1156
   */
1157
  public static OperatingStateResponse: typeof OperatingStateResponse;
1158
  /**
1159
   * @group Characteristic Definitions
1160
   */
1161
  public static OpticalZoom: typeof OpticalZoom;
1162
  /**
1163
   * @group Characteristic Definitions
1164
   */
1165
  public static OutletInUse: typeof OutletInUse;
1166
  /**
1167
   * @group Characteristic Definitions
1168
   */
1169
  public static OzoneDensity: typeof OzoneDensity;
1170
  /**
1171
   * @group Characteristic Definitions
1172
   */
1173
  public static PairingFeatures: typeof PairingFeatures;
1174
  /**
1175
   * @group Characteristic Definitions
1176
   */
1177
  public static PairSetup: typeof PairSetup;
1178
  /**
1179
   * @group Characteristic Definitions
1180
   */
1181
  public static PairVerify: typeof PairVerify;
1182
  /**
1183
   * @group Characteristic Definitions
1184
   */
1185
  public static PasswordSetting: typeof PasswordSetting;
1186
  /**
1187
   * @group Characteristic Definitions
1188
   */
1189
  public static PeriodicSnapshotsActive: typeof PeriodicSnapshotsActive;
1190
  /**
1191
   * @group Characteristic Definitions
1192
   */
1193
  public static PictureMode: typeof PictureMode;
1194
  /**
1195
   * @group Characteristic Definitions
1196
   */
1197
  public static Ping: typeof Ping;
1198
  /**
1199
   * @group Characteristic Definitions
1200
   */
1201
  public static PM10Density: typeof PM10Density;
1202
  /**
1203
   * @group Characteristic Definitions
1204
   */
1205
  public static PM2_5Density: typeof PM2_5Density;
1206
  /**
1207
   * @group Characteristic Definitions
1208
   */
1209
  public static PositionState: typeof PositionState;
1210
  /**
1211
   * @group Characteristic Definitions
1212
   */
1213
  public static PowerModeSelection: typeof PowerModeSelection;
1214
  /**
1215
   * @group Characteristic Definitions
1216
   */
1217
  public static PreferredWiFiList: typeof PreferredWiFiList;
1218
  /**
1219
   * @group Characteristic Definitions
1220
   */
1221
  public static ProductData: typeof ProductData;
1222
  /**
1223
   * @group Characteristic Definitions
1224
   */
1225
  public static ProgrammableSwitchEvent: typeof ProgrammableSwitchEvent;
1226
  /**
1227
   * @group Characteristic Definitions
1228
   */
1229
  public static ProgrammableSwitchOutputState: typeof ProgrammableSwitchOutputState;
1230
  /**
1231
   * @group Characteristic Definitions
1232
   */
1233
  public static ProgramMode: typeof ProgramMode;
1234
  /**
1235
   * @group Characteristic Definitions
1236
   */
1237
  public static ReceivedSignalStrengthIndication: typeof ReceivedSignalStrengthIndication;
1238
  /**
1239
   * @group Characteristic Definitions
1240
   */
1241
  public static ReceiverSensitivity: typeof ReceiverSensitivity;
1242
  /**
1243
   * @group Characteristic Definitions
1244
   */
1245
  public static RecordingAudioActive: typeof RecordingAudioActive;
1246
  /**
1247
   * @group Characteristic Definitions
1248
   */
1249
  public static RelativeHumidityDehumidifierThreshold: typeof RelativeHumidityDehumidifierThreshold;
1250
  /**
1251
   * @group Characteristic Definitions
1252
   */
1253
  public static RelativeHumidityHumidifierThreshold: typeof RelativeHumidityHumidifierThreshold;
1254
  /**
1255
   * @group Characteristic Definitions
1256
   * @deprecated Removed
1257
   */
1258
  public static RelayControlPoint: typeof RelayControlPoint;
1259
  /**
1260
   * @group Characteristic Definitions
1261
   * @deprecated Removed
1262
   */
1263
  public static RelayEnabled: typeof RelayEnabled;
1264
  /**
1265
   * @group Characteristic Definitions
1266
   * @deprecated Removed
1267
   */
1268
  public static RelayState: typeof RelayState;
1269
  /**
1270
   * @group Characteristic Definitions
1271
   */
1272
  public static RemainingDuration: typeof RemainingDuration;
1273
  /**
1274
   * @group Characteristic Definitions
1275
   */
1276
  public static RemoteKey: typeof RemoteKey;
1277
  /**
1278
   * @group Characteristic Definitions
1279
   */
1280
  public static ResetFilterIndication: typeof ResetFilterIndication;
1281
  /**
1282
   * @group Characteristic Definitions
1283
   */
1284
  public static RotationDirection: typeof RotationDirection;
1285
  /**
1286
   * @group Characteristic Definitions
1287
   */
1288
  public static RotationSpeed: typeof RotationSpeed;
1289
  /**
1290
   * @group Characteristic Definitions
1291
   */
1292
  public static RouterStatus: typeof RouterStatus;
1293
  /**
1294
   * @group Characteristic Definitions
1295
   */
1296
  public static Saturation: typeof Saturation;
1297
  /**
1298
   * @group Characteristic Definitions
1299
   */
1300
  public static SecuritySystemAlarmType: typeof SecuritySystemAlarmType;
1301
  /**
1302
   * @group Characteristic Definitions
1303
   */
1304
  public static SecuritySystemCurrentState: typeof SecuritySystemCurrentState;
1305
  /**
1306
   * @group Characteristic Definitions
1307
   */
1308
  public static SecuritySystemTargetState: typeof SecuritySystemTargetState;
1309
  /**
1310
   * @group Characteristic Definitions
1311
   */
1312
  public static SelectedAudioStreamConfiguration: typeof SelectedAudioStreamConfiguration;
1313
  /**
1314
   * @group Characteristic Definitions
1315
   */
1316
  public static SelectedCameraRecordingConfiguration: typeof SelectedCameraRecordingConfiguration;
1317
  /**
1318
   * @group Characteristic Definitions
1319
   */
1320
  public static SelectedDiagnosticsModes: typeof SelectedDiagnosticsModes;
1321
  /**
1322
   * @group Characteristic Definitions
1323
   */
1324
  public static SelectedRTPStreamConfiguration: typeof SelectedRTPStreamConfiguration;
1325
  /**
1326
   * @group Characteristic Definitions
1327
   */
1328
  public static SelectedSleepConfiguration: typeof SelectedSleepConfiguration;
1329
  /**
1330
   * @group Characteristic Definitions
1331
   */
1332
  public static SerialNumber: typeof SerialNumber;
1333
  /**
1334
   * @group Characteristic Definitions
1335
   */
1336
  public static ServiceLabelIndex: typeof ServiceLabelIndex;
1337
  /**
1338
   * @group Characteristic Definitions
1339
   */
1340
  public static ServiceLabelNamespace: typeof ServiceLabelNamespace;
1341
  /**
1342
   * @group Characteristic Definitions
1343
   */
1344
  public static SetDuration: typeof SetDuration;
1345
  /**
1346
   * @group Characteristic Definitions
1347
   */
1348
  public static SetupDataStreamTransport: typeof SetupDataStreamTransport;
1349
  /**
1350
   * @group Characteristic Definitions
1351
   */
1352
  public static SetupEndpoints: typeof SetupEndpoints;
1353
  /**
1354
   * @group Characteristic Definitions
1355
   */
1356
  public static SetupTransferTransport: typeof SetupTransferTransport;
1357
  /**
1358
   * @group Characteristic Definitions
1359
   */
1360
  public static SignalToNoiseRatio: typeof SignalToNoiseRatio;
1361
  /**
1362
   * @group Characteristic Definitions
1363
   */
1364
  public static SiriEnable: typeof SiriEnable;
1365
  /**
1366
   * @group Characteristic Definitions
1367
   */
1368
  public static SiriEndpointSessionStatus: typeof SiriEndpointSessionStatus;
1369
  /**
1370
   * @group Characteristic Definitions
1371
   */
1372
  public static SiriEngineVersion: typeof SiriEngineVersion;
1373
  /**
1374
   * @group Characteristic Definitions
1375
   */
1376
  public static SiriInputType: typeof SiriInputType;
1377
  /**
1378
   * @group Characteristic Definitions
1379
   */
1380
  public static SiriLightOnUse: typeof SiriLightOnUse;
1381
  /**
1382
   * @group Characteristic Definitions
1383
   */
1384
  public static SiriListening: typeof SiriListening;
1385
  /**
1386
   * @group Characteristic Definitions
1387
   */
1388
  public static SiriTouchToUse: typeof SiriTouchToUse;
1389
  /**
1390
   * @group Characteristic Definitions
1391
   */
1392
  public static SlatType: typeof SlatType;
1393
  /**
1394
   * @group Characteristic Definitions
1395
   */
1396
  public static SleepDiscoveryMode: typeof SleepDiscoveryMode;
1397
  /**
1398
   * @group Characteristic Definitions
1399
   */
1400
  public static SleepInterval: typeof SleepInterval;
1401
  /**
1402
   * @group Characteristic Definitions
1403
   */
1404
  public static SmokeDetected: typeof SmokeDetected;
1405
  /**
1406
   * @group Characteristic Definitions
1407
   */
1408
  public static SoftwareRevision: typeof SoftwareRevision;
1409
  /**
1410
   * @group Characteristic Definitions
1411
   */
1412
  public static StagedFirmwareVersion: typeof StagedFirmwareVersion;
1413
  /**
1414
   * @group Characteristic Definitions
1415
   */
1416
  public static StatusActive: typeof StatusActive;
1417
  /**
1418
   * @group Characteristic Definitions
1419
   */
1420
  public static StatusFault: typeof StatusFault;
1421
  /**
1422
   * @group Characteristic Definitions
1423
   */
1424
  public static StatusJammed: typeof StatusJammed;
1425
  /**
1426
   * @group Characteristic Definitions
1427
   */
1428
  public static StatusLowBattery: typeof StatusLowBattery;
1429
  /**
1430
   * @group Characteristic Definitions
1431
   */
1432
  public static StatusTampered: typeof StatusTampered;
1433
  /**
1434
   * @group Characteristic Definitions
1435
   */
1436
  public static StreamingStatus: typeof StreamingStatus;
1437
  /**
1438
   * @group Characteristic Definitions
1439
   */
1440
  public static SulphurDioxideDensity: typeof SulphurDioxideDensity;
1441
  /**
1442
   * @group Characteristic Definitions
1443
   */
1444
  public static SupportedAssetTypes: typeof SupportedAssetTypes;
1445
  /**
1446
   * @group Characteristic Definitions
1447
   */
1448
  public static SupportedAudioRecordingConfiguration: typeof SupportedAudioRecordingConfiguration;
1449
  /**
1450
   * @group Characteristic Definitions
1451
   */
1452
  public static SupportedAudioStreamConfiguration: typeof SupportedAudioStreamConfiguration;
1453
  /**
1454
   * @group Characteristic Definitions
1455
   */
1456
  public static SupportedCameraRecordingConfiguration: typeof SupportedCameraRecordingConfiguration;
1457
  /**
1458
   * @group Characteristic Definitions
1459
   */
1460
  public static SupportedCharacteristicValueTransitionConfiguration: typeof SupportedCharacteristicValueTransitionConfiguration;
1461
  /**
1462
   * @group Characteristic Definitions
1463
   */
1464
  public static SupportedDataStreamTransportConfiguration: typeof SupportedDataStreamTransportConfiguration;
1465
  /**
1466
   * @group Characteristic Definitions
1467
   */
1468
  public static SupportedDiagnosticsModes: typeof SupportedDiagnosticsModes;
1469
  /**
1470
   * @group Characteristic Definitions
1471
   */
1472
  public static SupportedDiagnosticsSnapshot: typeof SupportedDiagnosticsSnapshot;
1473
  /**
1474
   * @group Characteristic Definitions
1475
   */
1476
  public static SupportedFirmwareUpdateConfiguration: typeof SupportedFirmwareUpdateConfiguration;
1477
  /**
1478
   * @group Characteristic Definitions
1479
   */
1480
  public static SupportedMetrics: typeof SupportedMetrics;
1481
  /**
1482
   * @group Characteristic Definitions
1483
   */
1484
  public static SupportedRouterConfiguration: typeof SupportedRouterConfiguration;
1485
  /**
1486
   * @group Characteristic Definitions
1487
   */
1488
  public static SupportedRTPConfiguration: typeof SupportedRTPConfiguration;
1489
  /**
1490
   * @group Characteristic Definitions
1491
   */
1492
  public static SupportedSleepConfiguration: typeof SupportedSleepConfiguration;
1493
  /**
1494
   * @group Characteristic Definitions
1495
   */
1496
  public static SupportedTransferTransportConfiguration: typeof SupportedTransferTransportConfiguration;
1497
  /**
1498
   * @group Characteristic Definitions
1499
   */
1500
  public static SupportedVideoRecordingConfiguration: typeof SupportedVideoRecordingConfiguration;
1501
  /**
1502
   * @group Characteristic Definitions
1503
   */
1504
  public static SupportedVideoStreamConfiguration: typeof SupportedVideoStreamConfiguration;
1505
  /**
1506
   * @group Characteristic Definitions
1507
   */
1508
  public static SwingMode: typeof SwingMode;
1509
  /**
1510
   * @group Characteristic Definitions
1511
   * @deprecated Removed
1512
   */
1513
  public static TapType: typeof TapType;
1514
  /**
1515
   * @group Characteristic Definitions
1516
   */
1517
  public static TargetAirPurifierState: typeof TargetAirPurifierState;
1518
  /**
1519
   * @group Characteristic Definitions
1520
   */
1521
  public static TargetControlList: typeof TargetControlList;
1522
  /**
1523
   * @group Characteristic Definitions
1524
   */
1525
  public static TargetControlSupportedConfiguration: typeof TargetControlSupportedConfiguration;
1526
  /**
1527
   * @group Characteristic Definitions
1528
   */
1529
  public static TargetDoorState: typeof TargetDoorState;
1530
  /**
1531
   * @group Characteristic Definitions
1532
   */
1533
  public static TargetFanState: typeof TargetFanState;
1534
  /**
1535
   * @group Characteristic Definitions
1536
   */
1537
  public static TargetHeaterCoolerState: typeof TargetHeaterCoolerState;
1538
  /**
1539
   * @group Characteristic Definitions
1540
   */
1541
  public static TargetHeatingCoolingState: typeof TargetHeatingCoolingState;
1542
  /**
1543
   * @group Characteristic Definitions
1544
   */
1545
  public static TargetHorizontalTiltAngle: typeof TargetHorizontalTiltAngle;
1546
  /**
1547
   * @group Characteristic Definitions
1548
   */
1549
  public static TargetHumidifierDehumidifierState: typeof TargetHumidifierDehumidifierState;
1550
  /**
1551
   * @group Characteristic Definitions
1552
   */
1553
  public static TargetMediaState: typeof TargetMediaState;
1554
  /**
1555
   * @group Characteristic Definitions
1556
   */
1557
  public static TargetPosition: typeof TargetPosition;
1558
  /**
1559
   * @group Characteristic Definitions
1560
   */
1561
  public static TargetRelativeHumidity: typeof TargetRelativeHumidity;
1562
  /**
1563
   * @group Characteristic Definitions
1564
   */
1565
  public static TargetTemperature: typeof TargetTemperature;
1566
  /**
1567
   * @group Characteristic Definitions
1568
   */
1569
  public static TargetTiltAngle: typeof TargetTiltAngle;
1570
  /**
1571
   * @group Characteristic Definitions
1572
   */
1573
  public static TargetVerticalTiltAngle: typeof TargetVerticalTiltAngle;
1574
  /**
1575
   * @group Characteristic Definitions
1576
   */
1577
  public static TargetVisibilityState: typeof TargetVisibilityState;
1578
  /**
1579
   * @group Characteristic Definitions
1580
   */
1581
  public static TemperatureDisplayUnits: typeof TemperatureDisplayUnits;
1582
  /**
1583
   * @group Characteristic Definitions
1584
   */
1585
  public static ThirdPartyCameraActive: typeof ThirdPartyCameraActive;
1586
  /**
1587
   * @group Characteristic Definitions
1588
   */
1589
  public static ThreadControlPoint: typeof ThreadControlPoint;
1590
  /**
1591
   * @group Characteristic Definitions
1592
   */
1593
  public static ThreadNodeCapabilities: typeof ThreadNodeCapabilities;
1594
  /**
1595
   * @group Characteristic Definitions
1596
   */
1597
  public static ThreadOpenThreadVersion: typeof ThreadOpenThreadVersion;
1598
  /**
1599
   * @group Characteristic Definitions
1600
   */
1601
  public static ThreadStatus: typeof ThreadStatus;
1602
  /**
1603
   * @group Characteristic Definitions
1604
   * @deprecated Removed
1605
   */
1606
  public static Token: typeof Token;
1607
  /**
1608
   * @group Characteristic Definitions
1609
   */
1610
  public static TransmitPower: typeof TransmitPower;
1611
  /**
1612
   * @group Characteristic Definitions
1613
   * @deprecated Removed
1614
   */
1615
  public static TunnelConnectionTimeout: typeof TunnelConnectionTimeout;
1616
  /**
1617
   * @group Characteristic Definitions
1618
   * @deprecated Removed
1619
   */
1620
  public static TunneledAccessoryAdvertising: typeof TunneledAccessoryAdvertising;
1621
  /**
1622
   * @group Characteristic Definitions
1623
   * @deprecated Removed
1624
   */
1625
  public static TunneledAccessoryConnected: typeof TunneledAccessoryConnected;
1626
  /**
1627
   * @group Characteristic Definitions
1628
   * @deprecated Removed
1629
   */
1630
  public static TunneledAccessoryStateNumber: typeof TunneledAccessoryStateNumber;
1631
  /**
1632
   * @group Characteristic Definitions
1633
   */
1634
  public static ValveType: typeof ValveType;
1635
  /**
1636
   * @group Characteristic Definitions
1637
   */
1638
  public static Version: typeof Version;
1639
  /**
1640
   * @group Characteristic Definitions
1641
   */
1642
  public static VideoAnalysisActive: typeof VideoAnalysisActive;
1643
  /**
1644
   * @group Characteristic Definitions
1645
   */
1646
  public static VOCDensity: typeof VOCDensity;
1647
  /**
1648
   * @group Characteristic Definitions
1649
   */
1650
  public static Volume: typeof Volume;
1651
  /**
1652
   * @group Characteristic Definitions
1653
   */
1654
  public static VolumeControlType: typeof VolumeControlType;
1655
  /**
1656
   * @group Characteristic Definitions
1657
   */
1658
  public static VolumeSelector: typeof VolumeSelector;
1659
  /**
1660
   * @group Characteristic Definitions
1661
   */
1662
  public static WakeConfiguration: typeof WakeConfiguration;
1663
  /**
1664
   * @group Characteristic Definitions
1665
   */
1666
  public static WANConfigurationList: typeof WANConfigurationList;
1667
  /**
1668
   * @group Characteristic Definitions
1669
   */
1670
  public static WANStatusList: typeof WANStatusList;
1671
  /**
1672
   * @group Characteristic Definitions
1673
   */
1674
  public static WaterLevel: typeof WaterLevel;
1675
  /**
1676
   * @group Characteristic Definitions
1677
   */
1678
  public static WiFiCapabilities: typeof WiFiCapabilities;
1679
  /**
1680
   * @group Characteristic Definitions
1681
   */
1682
  public static WiFiConfigurationControl: typeof WiFiConfigurationControl;
1683
  /**
1684
   * @group Characteristic Definitions
1685
   */
1686
  public static WiFiSatelliteStatus: typeof WiFiSatelliteStatus;
1687
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=
1688

1689
  // NOTICE: when adding/changing properties, remember to possibly adjust the serialize/deserialize functions
1690
  public displayName: string;
1691
  public UUID: string;
1692
  iid: Nullable<number> = null;
23,448✔
1693
  value: Nullable<CharacteristicValue> = null;
23,448✔
1694
  /**
1695
   * @private
1696
   */
1697
  statusCode: HAPStatus = HAPStatus.SUCCESS;
23,448✔
1698
  props: CharacteristicProps;
1699

1700
  /**
1701
   * The {@link Characteristic.onGet} handler
1702
   */
1703
  private getHandler?: CharacteristicGetHandler;
1704

1705
  /**
1706
   * The {@link Characteristic.onSet} handler
1707
   */
1708
  private setHandler?: CharacteristicSetHandler;
1709

1710
  private subscriptions = 0;
23,448✔
1711
  /**
1712
   * @private
1713
   */
1714
  additionalAuthorizationHandler?: AdditionalAuthorizationHandler;
1715

1716
  public constructor(displayName: string, UUID: string, props: CharacteristicProps) {
1717
    super();
23,448✔
1718
    this.displayName = displayName;
23,448✔
1719
    this.UUID = UUID;
23,448✔
1720
    this.props = { // some weird defaults (with legacy constructor props was optional)
23,448✔
1721
      format: Formats.INT,
1722
      perms: [Perms.NOTIFY],
1723
    };
1724

1725
    this.setProps(props || {}); // ensure sanity checks are called
23,448!
1726
  }
1727

1728
  /**
1729
   * Accepts a function that will be called to retrieve the current value of a Characteristic.
1730
   * The function must return a valid Characteristic value for the Characteristic type.
1731
   * May optionally return a promise.
1732
   *
1733
   * @example
1734
   * ```ts
1735
   * Characteristic.onGet(async () => {
1736
   *   return true;
1737
   * });
1738
   * ```
1739
   * @param handler
1740
   */
1741
  public onGet(handler: CharacteristicGetHandler): Characteristic {
1742
    if (typeof handler !== "function") {
483!
1743
      this.characteristicWarning(".onGet handler must be a function");
×
1744
      return this;
×
1745
    }
1746
    this.getHandler = handler;
483✔
1747
    return this;
483✔
1748
  }
1749

1750
  /**
1751
   * Removes the {@link CharacteristicGetHandler} handler which was configured using {@link onGet}.
1752
   */
1753
  public removeOnGet(): Characteristic {
1754
    this.getHandler = undefined;
6✔
1755
    return this;
6✔
1756
  }
1757

1758
  /**
1759
   * Accepts a function that will be called when setting the value of a Characteristic.
1760
   * If the characteristic supports {@link Perms.WRITE_RESPONSE} and the request requests a write-response value,
1761
   * the returned value will be used.
1762
   * May optionally return a promise.
1763
   *
1764
   * @example
1765
   * ```ts
1766
   * Characteristic.onSet(async (value: CharacteristicValue) => {
1767
   *   console.log(value);
1768
   * });
1769
   * ```
1770
   * @param handler
1771
   */
1772
  public onSet(handler: CharacteristicSetHandler): Characteristic {
1773
    if (typeof handler !== "function") {
459!
1774
      this.characteristicWarning(".onSet handler must be a function");
×
1775
      return this;
×
1776
    }
1777
    this.setHandler = handler;
459✔
1778
    return this;
459✔
1779
  }
1780

1781
  /**
1782
   * Removes the {@link CharacteristicSetHandler} which was configured using {@link onSet}.
1783
   */
1784
  public removeOnSet(): Characteristic {
1785
    this.setHandler = undefined;
×
1786
    return this;
×
1787
  }
1788

1789
  /**
1790
   * Updates the properties of this characteristic.
1791
   * Properties passed via the parameter will be set. Any parameter set to null will be deleted.
1792
   * See {@link CharacteristicProps}.
1793
   *
1794
   * @param props - Partial properties object with the desired updates.
1795
   */
1796
  public setProps(props: PartialAllowingNull<CharacteristicProps>): Characteristic {
1797
    assert(props, "props cannot be undefined when setting props");
25,263✔
1798
    // TODO calling setProps after publish doesn't lead to a increment in the current configuration number
1799

1800
    let formatDidChange = false;
25,263✔
1801

1802
    // for every value "null" can be used to reset props, except for required props
1803
    if (props.format) {
25,263✔
1804
      formatDidChange = this.props.format !== props.format;
23,544✔
1805
      this.props.format = props.format;
23,544✔
1806
    }
1807
    if (props.perms) {
25,263✔
1808
      assert(props.perms.length > 0, "characteristic prop perms cannot be empty array");
23,541✔
1809
      this.props.perms = props.perms;
23,541✔
1810
    }
1811

1812
    if (props.unit !== undefined) {
25,263✔
1813
      this.props.unit = props.unit != null? props.unit: undefined;
1,890✔
1814
    }
1815
    if (props.description !== undefined) {
25,263!
1816
      this.props.description = props.description != null? props.description: undefined;
×
1817
    }
1818

1819
    // check minValue is valid for the format type
1820
    if (props.minValue !== undefined) {
25,263✔
1821
      if (props.minValue === null) {
7,269✔
1822
        props.minValue = undefined;
105✔
1823
      } else if (!isNumericFormat(this.props.format)) {
7,164✔
1824
        this.characteristicWarning(
3✔
1825
          "Characteristic Property 'minValue' can only be set for characteristics with numeric format, but not for " + this.props.format,
1826
          CharacteristicWarningType.ERROR_MESSAGE,
1827
        );
1828
        props.minValue = undefined;
3✔
1829
      } else if (typeof (props.minValue as unknown) !== "number" || !Number.isFinite(props.minValue)) {
7,161✔
1830
        this.characteristicWarning(
6✔
1831
          `Characteristic Property 'minValue' must be a finite number, received "${props.minValue}" (${typeof props.minValue})`,
1832
          CharacteristicWarningType.ERROR_MESSAGE,
1833
        );
1834
        props.minValue = undefined;
6✔
1835
      } else {
1836
        if (props.minValue < numericLowerBound(this.props.format)) {
7,155✔
1837
          this.characteristicWarning(
3✔
1838
            "Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " +
1839
            this.props.format + " minimum possible is " + numericLowerBound(this.props.format),
1840
            CharacteristicWarningType.ERROR_MESSAGE,
1841
          );
1842
          props.minValue = numericLowerBound(this.props.format);
3✔
1843
        } else if (props.minValue > numericUpperBound(this.props.format)) {
7,152✔
1844
          this.characteristicWarning(
3✔
1845
            "Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " +
1846
            this.props.format + " maximum possible is " + numericUpperBound(this.props.format),
1847
            CharacteristicWarningType.ERROR_MESSAGE,
1848
          );
1849
          props.minValue = numericLowerBound(this.props.format);
3✔
1850
        }
1851
      }
1852

1853
      this.props.minValue = props.minValue;
7,269✔
1854
    }
1855

1856
    // check maxValue is valid for the format type
1857
    if (props.maxValue !== undefined) {
25,263✔
1858
      if (props.maxValue === null) {
7,236✔
1859
        props.maxValue = undefined;
105✔
1860
      } else if (!isNumericFormat(this.props.format)) {
7,131✔
1861
        this.characteristicWarning(
3✔
1862
          "Characteristic Property 'maxValue' can only be set for characteristics with numeric format, but not for " + this.props.format,
1863
          CharacteristicWarningType.ERROR_MESSAGE,
1864
        );
1865
        props.maxValue = undefined;
3✔
1866
      } else if (typeof (props.maxValue as unknown) !== "number" || !Number.isFinite(props.maxValue)) {
7,128✔
1867
        this.characteristicWarning(
6✔
1868
          `Characteristic Property 'maxValue' must be a finite number, received "${props.maxValue}" (${typeof props.maxValue})`,
1869
          CharacteristicWarningType.ERROR_MESSAGE,
1870
        );
1871
        props.maxValue = undefined;
6✔
1872
      } else {
1873
        if (props.maxValue > numericUpperBound(this.props.format)) {
7,122✔
1874
          this.characteristicWarning(
9✔
1875
            "Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " +
1876
            this.props.format + " maximum possible is " + numericUpperBound(this.props.format),
1877
            CharacteristicWarningType.ERROR_MESSAGE,
1878
          );
1879
          props.maxValue = numericUpperBound(this.props.format);
9✔
1880
        } else if (props.maxValue < numericLowerBound(this.props.format)) {
7,113✔
1881
          this.characteristicWarning(
3✔
1882
            "Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " +
1883
            this.props.format + " minimum possible is " + numericLowerBound(this.props.format),
1884
            CharacteristicWarningType.ERROR_MESSAGE,
1885
          );
1886
          props.maxValue = numericUpperBound(this.props.format);
3✔
1887
        }
1888
      }
1889

1890
      this.props.maxValue = props.maxValue;
7,236✔
1891
    }
1892

1893
    if (props.minStep !== undefined) {
25,263✔
1894
      if (props.minStep === null) {
6,261✔
1895
        this.props.minStep = undefined;
87✔
1896
      } else if (!isNumericFormat(this.props.format)) {
6,174✔
1897
        this.characteristicWarning(
3✔
1898
          "Characteristic Property `minStep` can only be set for characteristics with numeric format, but not for " + this.props.format,
1899
          CharacteristicWarningType.ERROR_MESSAGE,
1900
        );
1901
      } else {
1902
        if (props.minStep < 1 && isIntegerNumericFormat(this.props.format)) {
6,171✔
1903
          this.characteristicWarning("Characteristic Property `minStep` was set to a value lower than 1, " +
3✔
1904
            "this will have no effect on format `" + this.props.format);
1905
        }
1906

1907
        this.props.minStep = props.minStep;
6,171✔
1908
      }
1909
    }
1910
    if (props.maxLen !== undefined) {
25,263✔
1911
      if (props.maxLen === null) {
3,507✔
1912
        this.props.maxLen = undefined;
3✔
1913
      } else if (this.props.format !== Formats.STRING) {
3,504!
1914
        this.characteristicWarning(
×
1915
          "Characteristic Property `maxLen` can only be set for characteristics with format `STRING`, but not for " + this.props.format,
1916
          CharacteristicWarningType.ERROR_MESSAGE,
1917
        );
1918
      } else {
1919
        if (props.maxLen > 256) {
3,504!
1920
          this.characteristicWarning("Characteristic Property string `maxLen` cannot be bigger than 256");
×
1921
          props.maxLen = 256;
×
1922
        }
1923
        this.props.maxLen = props.maxLen;
3,504✔
1924
      }
1925
    }
1926
    if (props.maxDataLen !== undefined) {
25,263✔
1927
      if (props.maxDataLen === null) {
6!
1928
        this.props.maxDataLen = undefined;
×
1929
      } else if (this.props.format !== Formats.DATA) {
6!
1930
        this.characteristicWarning(
×
1931
          "Characteristic Property `maxDataLen` can only be set for characteristics with format `DATA`, but not for " + this.props.format,
1932
          CharacteristicWarningType.ERROR_MESSAGE,
1933
        );
1934
      } else {
1935
        this.props.maxDataLen = props.maxDataLen;
6✔
1936
      }
1937
    }
1938
    if (props.validValues !== undefined) {
25,263✔
1939
      if (props.validValues === null) {
6,324!
1940
        this.props.validValues = undefined;
×
1941
      } else if (!isNumericFormat(this.props.format)) {
6,324✔
1942
        this.characteristicWarning("Characteristic Property `validValues` was supplied for non numeric format " + this.props.format);
471✔
1943
      } else {
1944
        assert(props.validValues.length, "characteristic prop validValues cannot be empty array");
5,853✔
1945
        this.props.validValues = props.validValues;
5,853✔
1946
      }
1947
    }
1948
    if (props.validValueRanges !== undefined) {
25,263✔
1949
      if (props.validValueRanges === null) {
63✔
1950
        this.props.validValueRanges = undefined;
18✔
1951
      } else if (!isNumericFormat(this.props.format)) {
45!
1952
        this.characteristicWarning("Characteristic Property `validValueRanges` was supplied for non numeric format " + this.props.format);
×
1953
      } else {
1954
        assert(props.validValueRanges.length === 2, "characteristic prop validValueRanges must have a length of 2");
45✔
1955
        this.props.validValueRanges = props.validValueRanges;
45✔
1956
      }
1957
    }
1958
    if (props.adminOnlyAccess !== undefined) {
25,263✔
1959
      this.props.adminOnlyAccess = props.adminOnlyAccess != null? props.adminOnlyAccess: undefined;
1,569!
1960
    }
1961

1962

1963
    if (this.props.minValue != null && this.props.maxValue != null) { // the eqeq instead of eqeqeq is important here
25,263✔
1964
      if (this.props.minValue > this.props.maxValue) { // see https://github.com/homebridge/HAP-NodeJS/issues/690
8,214✔
1965
        this.props.minValue = undefined;
15✔
1966
        this.props.maxValue = undefined;
15✔
1967
        throw new Error("Error setting CharacteristicsProps for '" + this.displayName + "': 'minValue' cannot be greater or equal the 'maxValue'!");
15✔
1968
      }
1969
    }
1970

1971
    if ((isNumericFormat(this.props.format) || this.props.format === Formats.STRING)
25,248✔
1972
      && this.value != null
1973
      && !formatDidChange
1974
      && this.statusCode === HAPStatus.SUCCESS
1975
      && this.UUID !== Characteristic.ProgrammableSwitchEvent.UUID) {
1976
      // explaining the if statement above:
1977
      // - We only do a check for numeric and string formats as they are the only ones affected by characteristic property restrictions.
1978
      // - There must be a value to begin with. Otherwise, it should just stay not having a value at all (anything else is guess work).
1979
      // - If the format is changed through `setProps` we rely on the user to supply a valid value after the `setProps` call!
1980
      // - If the characteristic is marked as erroneous the value is not considered valid anyway, and we must not remove the `statusCode`.
1981
      // - Special case for `ProgrammableSwitchEvent` where every change in value is considered an event which would result in ghost button presses
1982

1983
      // validateUserInput when called from setProps is intended to clamp value withing allowed range. It is why warnings should not be displayed.
1984
      const correctedValue = this.validateUserInput(this.value, CharacteristicWarningType.DEBUG_MESSAGE);
1,473✔
1985

1986
      if (correctedValue !== this.value) {
1,473✔
1987
        // we don't want to emit a CHANGE event if the value didn't change at all!
1988
        this.updateValue(correctedValue);
63✔
1989
      }
1990
    }
1991

1992
    return this;
25,248✔
1993
  }
1994

1995
  /**
1996
   * This method can be used to gain an Iterator to loop over all valid values defined for this characteristic.
1997
   *
1998
   * The range of valid values can be defined using three different ways via the {@link CharacteristicProps} object
1999
   * (set via the {@link setProps} method):
2000
   *  * First method is to specifically list every valid value inside {@link CharacteristicProps.validValues}
2001
   *  * Second you can specify a range via {@link CharacteristicProps.minValue} and {@link CharacteristicProps.maxValue} (with optionally defining
2002
   *    {@link CharacteristicProps.minStep})
2003
   *  * And lastly you can specify a range via {@link CharacteristicProps.validValueRanges}
2004
   *  * Implicitly a valid value range is predefined for characteristics with Format {@link Formats.UINT8}, {@link Formats.UINT16},
2005
   *    {@link Formats.UINT32} and {@link Formats.UINT64}: starting by zero to their respective maximum number
2006
   *
2007
   * The method will automatically detect which type of valid values definition is used and provide
2008
   * the correct Iterator for that case.
2009
   *
2010
   * Note: This method is (obviously) only valid for numeric characteristics.
2011
   *
2012
   * @example
2013
   * ```ts
2014
   * // use the iterator to loop over every valid value...
2015
   * for (const value of characteristic.validValuesIterator()) {
2016
   *   // Insert logic to run for every
2017
   * }
2018
   *
2019
   * // ... or collect them in an array for storage or manipulation
2020
   * const validValues = Array.from(characteristic.validValuesIterator());
2021
   * ```
2022
   */
2023
  public validValuesIterator(): Iterable<number> {
2024
    return new ValidValuesIterable(this.props);
15✔
2025
  }
2026

2027
  // noinspection JSUnusedGlobalSymbols
2028
  /**
2029
   * This method can be used to set up additional authorization for a characteristic.
2030
   * For one, it adds the {@link Perms.ADDITIONAL_AUTHORIZATION} permission to the characteristic
2031
   * (if it wasn't already) to signal support for additional authorization to HomeKit.
2032
   * Additionally, an {@link AdditionalAuthorizationHandler} is set up which is called
2033
   * before a write request is performed.
2034
   *
2035
   * Additional Authorization Data can be added to SET request via a custom iOS App.
2036
   * Before hap-nodejs executes a write request it will call the {@link AdditionalAuthorizationHandler}
2037
   * with 'authData' supplied in the write request. The 'authData' is a base64 encoded string
2038
   * (or undefined if no authData was supplied).
2039
   * The {@link AdditionalAuthorizationHandler} must then return true or false to indicate if the write request
2040
   * is authorized and should be accepted.
2041
   *
2042
   * @param handler - Handler called to check additional authorization data.
2043
   */
2044
  public setupAdditionalAuthorization(handler: AdditionalAuthorizationHandler): void {
2045
    if (!this.props.perms.includes(Perms.ADDITIONAL_AUTHORIZATION)) {
3!
2046
      this.props.perms.push(Perms.ADDITIONAL_AUTHORIZATION);
3✔
2047
    }
2048
    this.additionalAuthorizationHandler = handler;
3✔
2049
  }
2050

2051
  /**
2052
   * This updates the value by calling the {@link CharacteristicEventTypes.SET} event handler associated with the characteristic.
2053
   * This acts the same way as when a HomeKit controller sends a `/characteristics` request to update the characteristic.
2054
   * An event notification will be sent to all connected HomeKit controllers which are registered
2055
   * to receive event notifications for this characteristic.
2056
   *
2057
   * This method behaves like a {@link updateValue} call with the addition that the own {@link CharacteristicEventTypes.SET}
2058
   * event handler is called.
2059
   *
2060
   * @param value - The new value.
2061
   *
2062
   * Note: If you don't want the {@link CharacteristicEventTypes.SET} to be called, refer to {@link updateValue}.
2063
   */
2064
  setValue(value: CharacteristicValue): Characteristic
2065
  /**
2066
   * Sets the state of the characteristic to an errored state.
2067
   *
2068
   * If a {@link onGet} or {@link CharacteristicEventTypes.GET} handler is set up,
2069
   * the errored state will be ignored and the characteristic will always query the latest state by calling the provided handler.
2070
   *
2071
   * If a generic error object is supplied, the characteristic tries to extract a {@link HAPStatus} code
2072
   * from the error message string. If not possible a generic {@link HAPStatus.SERVICE_COMMUNICATION_FAILURE} will be used.
2073
   * If the supplied error object is an instance of {@link HapStatusError} the corresponding status will be used.
2074
   *
2075
   * This doesn't call any registered {@link onSet} or {@link CharacteristicEventTypes.SET} handlers.
2076
   *
2077
   * Have a look at the
2078
   * {@link https://github.com/homebridge/HAP-NodeJS/wiki/Presenting-Erroneous-Accessory-State-to-the-User Presenting Erroneous Accessory State to the User}
2079
   * guide for more information on how to present erroneous state to the user.
2080
   *
2081
   * @param error - The error object
2082
   *
2083
   * Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
2084
   *  state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
2085
   *  any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
2086
   */
2087
  setValue(error: HapStatusError | Error): Characteristic;
2088
  /**
2089
   * This updates the value by calling the {@link CharacteristicEventTypes.SET} event handler associated with the characteristic.
2090
   * This acts the same way as when a HomeKit controller sends a `/characteristics` request to update the characteristic.
2091
   * An event notification will be sent to all connected HomeKit controllers which are registered
2092
   * to receive event notifications for this characteristic.
2093
   *
2094
   * This method behaves like a {@link updateValue} call with the addition that the own {@link CharacteristicEventTypes.SET}
2095
   * event handler is called.
2096
   *
2097
   * @param value - The new value.
2098
   * @param context - Passed to the {@link CharacteristicEventTypes.SET} and {@link CharacteristicEventTypes.CHANGE} event handler.
2099
   *
2100
   * Note: If you don't want the {@link CharacteristicEventTypes.SET} to be called, refer to {@link updateValue}.
2101
   */
2102
  setValue(value: CharacteristicValue, context?: CharacteristicContext): Characteristic;
2103
  setValue(value: CharacteristicValue | Error, callback?: CharacteristicSetCallback, context?: CharacteristicContext): Characteristic {
2104
    if (value instanceof Error) {
6,666!
2105
      this.statusCode = value instanceof HapStatusError? value.hapStatus: extractHAPStatusFromError(value);
×
2106

2107
      if (callback) {
×
2108
        callback();
×
2109
      }
2110
      return this;
×
2111
    }
2112

2113
    if (callback && !context && typeof callback !== "function") {
6,666✔
2114
      context = callback;
3✔
2115
      callback = undefined;
3✔
2116
    }
2117

2118
    try {
6,666✔
2119
      value = this.validateUserInput(value)!;
6,666✔
2120
    } catch (error) {
2121
      this.characteristicWarning(error?.message || "Unknown error", CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
6!
2122
      if (callback) {
6!
2123
        callback(error);
×
2124
      }
2125
      return this;
6✔
2126
    }
2127

2128
    this.handleSetRequest(value, undefined, context).then(value => {
6,660✔
2129
      if (callback) {
6,660✔
2130
        if (value) { // possible write response
6!
2131
          callback(null, value);
×
2132
        } else {
2133
          callback(null);
6✔
2134
        }
2135
      }
2136
    }, reason => {
2137
      if (callback) {
×
2138
        callback(reason);
×
2139
      }
2140
    });
2141

2142
    return this;
6,660✔
2143
  }
2144

2145
  /**
2146
   * This updates the value of the characteristic. If the value changed, an event notification will be sent to all connected
2147
   * HomeKit controllers which are registered to receive event notifications for this characteristic.
2148
   *
2149
   * @param value - The new value or a `Error` or {@link HapStatusError}.
2150
   *
2151
   * Note: Refer to the respective overloads for {@link CharacteristicValue} or {@link HapStatusError} for respective documentation.
2152
   */
2153
  updateValue(value: Nullable<CharacteristicValue> | Error | HapStatusError): Characteristic;
2154
  /**
2155
   * This updates the value of the characteristic. If the value changed, an event notification will be sent to all connected
2156
   * HomeKit controllers which are registered to receive event notifications for this characteristic.
2157
   *
2158
   * @param value - The new value.
2159
   */
2160
  updateValue(value: Nullable<CharacteristicValue>): Characteristic;
2161
  /**
2162
   * Sets the state of the characteristic to an errored state.
2163
   * If a {@link onGet} or {@link CharacteristicEventTypes.GET} handler is set up,
2164
   * the errored state will be ignored and the characteristic will always query the latest state by calling the provided handler.
2165
   *
2166
   * If a generic error object is supplied, the characteristic tries to extract a {@link HAPStatus} code
2167
   * from the error message string. If not possible a generic {@link HAPStatus.SERVICE_COMMUNICATION_FAILURE} will be used.
2168
   * If the supplied error object is an instance of {@link HapStatusError} the corresponding status will be used.
2169
   *
2170
   * Have a look at the
2171
   * {@link https://github.com/homebridge/HAP-NodeJS/wiki/Presenting-Erroneous-Accessory-State-to-the-User Presenting Erroneous Accessory State to the User}
2172
   * guide for more information on how to present erroneous state to the user.
2173
   *
2174
   * @param error - The error object
2175
   *
2176
   * Note: Erroneous state is never **pushed** to the client side. Only, if the HomeKit client requests the current
2177
   *  state of the Characteristic, the corresponding {@link HapStatusError} is returned. As described above,
2178
   *  any {@link onGet} or {@link CharacteristicEventTypes.GET} handlers have precedence.
2179
   */
2180
  updateValue(error: Error | HapStatusError): Characteristic;
2181
  /**
2182
   * This updates the value of the characteristic. If the value changed, an event notification will be sent to all connected
2183
   * HomeKit controllers which are registered to receive event notifications for this characteristic.
2184
   *
2185
   * @param value - The new value.
2186
   * @param context - Passed to the {@link CharacteristicEventTypes.CHANGE} event handler.
2187
   */
2188
  updateValue(value: Nullable<CharacteristicValue>, context?: CharacteristicContext): Characteristic;
2189
  updateValue(value: Nullable<CharacteristicValue> | Error | HapStatusError, callback?: () => void, context?: CharacteristicContext): Characteristic {
2190
    if (value instanceof Error) {
3,159✔
2191
      this.statusCode = value instanceof HapStatusError? value.hapStatus: extractHAPStatusFromError(value);
3!
2192

2193
      if (callback) {
3!
2194
        callback();
×
2195
      }
2196
      return this;
3✔
2197
    }
2198

2199
    if (callback && !context && typeof callback !== "function") {
3,156✔
2200
      context = callback;
3✔
2201
      callback = undefined;
3✔
2202
    }
2203

2204
    try {
3,156✔
2205
      value = this.validateUserInput(value);
3,156✔
2206
    } catch (error) {
NEW
2207
      this.characteristicWarning(error?.message || "Unknown error", CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
×
2208
      if (callback) {
×
2209
        callback();
×
2210
      }
2211
      return this;
×
2212
    }
2213

2214
    this.statusCode = HAPStatus.SUCCESS;
3,156✔
2215

2216
    const oldValue = this.value;
3,156✔
2217
    this.value = value;
3,156✔
2218

2219
    if (callback) {
3,156✔
2220
      callback();
3✔
2221
    }
2222

2223
    this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, reason: ChangeReason.UPDATE, context: context });
3,156✔
2224

2225
    return this; // for chaining
3,156✔
2226
  }
2227

2228
  /**
2229
   * This method acts similarly to {@link updateValue} by setting the current value of the characteristic
2230
   * without calling any {@link CharacteristicEventTypes.SET} or {@link onSet} handlers.
2231
   * The difference is that this method forces an event notification sent (updateValue only sends one if the value changed).
2232
   * This is especially useful for characteristics like {@link Characteristic.ButtonEvent} or {@link Characteristic.ProgrammableSwitchEvent}.
2233
   *
2234
   * @param value - The new value.
2235
   * @param context - Passed to the {@link CharacteristicEventTypes.CHANGE} event handler.
2236
   */
2237
  public sendEventNotification(value: CharacteristicValue, context?: CharacteristicContext): Characteristic {
2238
    this.statusCode = HAPStatus.SUCCESS;
×
2239

2240
    value = this.validateUserInput(value)!;
×
2241
    const oldValue = this.value;
×
2242
    this.value = value;
×
2243

2244
    this.emit(CharacteristicEventTypes.CHANGE, { originator: undefined, oldValue: oldValue, newValue: value, reason: ChangeReason.EVENT, context: context });
×
2245

2246
    return this; // for chaining
×
2247
  }
2248

2249
  /**
2250
   * Called when a HAP requests wants to know the current value of the characteristic.
2251
   *
2252
   * @param connection - The HAP connection from which the request originated from.
2253
   * @param context - Deprecated parameter. There for backwards compatibility.
2254
   * @private Used by the Accessory to load the characteristic value
2255
   */
2256
  async handleGetRequest(connection?: HAPConnection, context?: CharacteristicContext): Promise<Nullable<CharacteristicValue>> {
2257
    if (!this.props.perms.includes(Perms.PAIRED_READ)) { // check if we are allowed to read from this characteristic
123!
2258
      throw HAPStatus.WRITE_ONLY_CHARACTERISTIC;
×
2259
    }
2260

2261
    if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
123✔
2262
      // special workaround for event only programmable switch event, which must always return null
2263
      return null;
3✔
2264
    }
2265

2266
    if (this.getHandler) {
120✔
2267
      if (this.listeners(CharacteristicEventTypes.GET).length > 0) {
18✔
2268
        this.characteristicWarning("Ignoring on('get') handler as onGet handler was defined instead");
3✔
2269
      }
2270

2271
      try {
18✔
2272
        let value = await this.getHandler(context, connection);
18✔
2273
        this.statusCode = HAPStatus.SUCCESS;
3✔
2274

2275
        try {
3✔
2276
          value = this.validateUserInput(value);
3✔
2277
        } catch (error) {
2278
          this.characteristicWarning(`An illegal value was supplied by the read handler for characteristic: ${error?.message}`,
×
2279
            CharacteristicWarningType.WARN_MESSAGE, error?.stack);
2280
          this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
×
2281
          return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
×
2282
        }
2283

2284
        const oldValue = this.value;
3✔
2285
        this.value = value;
3✔
2286

2287
        if (oldValue !== value) { // emit a change event if necessary
3!
2288
          this.emit(
3✔
2289
            CharacteristicEventTypes.CHANGE,
2290
            { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.READ, context: context },
2291
          );
2292
        }
2293

2294
        return value;
3✔
2295
      } catch (error) {
2296
        if (typeof error === "number") {
15✔
2297
          const hapStatusError = new HapStatusError(error);
6✔
2298
          this.statusCode = hapStatusError.hapStatus;
6✔
2299
        } else if (error instanceof HapStatusError) {
9✔
2300
          this.statusCode = error.hapStatus;
6✔
2301
        } else {
2302
          this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`,
3✔
2303
            CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
2304
          this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
3✔
2305
        }
2306
        throw this.statusCode;
15✔
2307
      }
2308
    }
2309

2310
    if (this.listeners(CharacteristicEventTypes.GET).length === 0) {
102✔
2311
      if (this.statusCode) {
81!
2312
        throw this.statusCode;
×
2313
      }
2314

2315
      try {
81✔
2316
        return this.validateUserInput(this.value);
81✔
2317
      } catch (error) {
2318
        this.characteristicWarning(`An illegal value was supplied by setting \`value\` for characteristic: ${error?.message}`,
×
2319
          CharacteristicWarningType.WARN_MESSAGE, error?.stack);
2320
        return Promise.reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
×
2321
      }
2322
    }
2323

2324
    return new Promise((resolve, reject) => {
21✔
2325
      try {
21✔
2326
        this.emit(CharacteristicEventTypes.GET, once((status, value) => {
21✔
2327
          if (status) {
18✔
2328
            if (typeof status === "number") {
12✔
2329
              const hapStatusError = new HapStatusError(status);
6✔
2330
              this.statusCode = hapStatusError.hapStatus;
6✔
2331
            } else if (status instanceof HapStatusError) {
6✔
2332
              this.statusCode = status.hapStatus;
3✔
2333
            } else {
2334
              debug("[%s] Received error from get handler %s", this.displayName, status.stack);
3✔
2335
              this.statusCode = extractHAPStatusFromError(status);
3✔
2336
            }
2337
            reject(this.statusCode);
12✔
2338
            return;
12✔
2339
          }
2340

2341
          this.statusCode = HAPStatus.SUCCESS;
6✔
2342

2343
          value = this.validateUserInput(value);
6✔
2344
          const oldValue = this.value;
6✔
2345
          this.value = value;
6✔
2346

2347
          resolve(value);
6✔
2348

2349
          if (oldValue !== value) { // emit a change event if necessary
6!
2350
            this.emit(
6✔
2351
              CharacteristicEventTypes.CHANGE,
2352
              { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.READ, context: context },
2353
            );
2354
          }
2355
        }), context, connection);
2356
      } catch (error) {
2357
        this.characteristicWarning(`Unhandled error thrown inside read handler for characteristic: ${error?.message}`,
×
2358
          CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
2359
        this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
×
2360
        reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
×
2361
      }
2362
    });
2363
  }
2364

2365
  /**
2366
   * Called when a HAP requests update the current value of the characteristic.
2367
   *
2368
   * @param value - The updated value
2369
   * @param connection - The connection from which the request originated from
2370
   * @param context - Deprecated parameter. There for backwards compatibility.
2371
   * @returns Promise resolve to void in normal operation. When characteristic supports write-response, HAP
2372
   *  requests a write-response and the set handler returns a write-response value, the respective
2373
   *  write response value is resolved.
2374
   * @private
2375
   */
2376
  async handleSetRequest(value: CharacteristicValue, connection?: HAPConnection, context?: CharacteristicContext): Promise<CharacteristicValue | void> {
2377
    this.statusCode = HAPStatus.SUCCESS;
7,029✔
2378

2379
    if (connection !== undefined) {
7,029✔
2380
      // if connection is undefined, the set "request" comes from the setValue method.
2381
      // for setValue a value of "null" is allowed and checked via validateUserInput.
2382
      try {
336✔
2383
        value = this.validateClientSuppliedValue(value);
336✔
2384
      } catch (e) {
2385
        debug(`[${this.displayName}]`, e.message);
177✔
2386
        return Promise.reject(HAPStatus.INVALID_VALUE_IN_REQUEST);
177✔
2387
      }
2388
    }
2389

2390
    const oldValue = this.value;
6,852✔
2391

2392
    if (this.setHandler) {
6,852✔
2393
      if (this.listeners(CharacteristicEventTypes.SET).length > 0) {
21✔
2394
        this.characteristicWarning("Ignoring on('set') handler as onSet handler was defined instead");
3✔
2395
      }
2396

2397
      try {
21✔
2398
        const writeResponse = await this.setHandler(value, context, connection);
21✔
2399
        this.statusCode = HAPStatus.SUCCESS;
6✔
2400

2401
        if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) {
6!
2402
          this.value = this.validateUserInput(writeResponse);
×
2403
          return this.value!;
×
2404
        } else {
2405
          if (writeResponse != null) {
6✔
2406
            this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response",
3✔
2407
              CharacteristicWarningType.DEBUG_MESSAGE);
2408
          }
2409
          this.value = value;
6✔
2410

2411
          this.emit(
6✔
2412
            CharacteristicEventTypes.CHANGE,
2413
            { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context },
2414
          );
2415
          return;
6✔
2416
        }
2417
      } catch (error) {
2418
        if (typeof error === "number") {
15✔
2419
          const hapStatusError = new HapStatusError(error);
6✔
2420
          this.statusCode = hapStatusError.hapStatus;
6✔
2421
        } else if (error instanceof HapStatusError) {
9✔
2422
          this.statusCode = error.hapStatus;
6✔
2423
        } else {
2424
          this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`,
3✔
2425
            CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
2426
          this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
3✔
2427
        }
2428
        throw this.statusCode;
15✔
2429
      }
2430
    }
2431

2432
    if (this.listeners(CharacteristicEventTypes.SET).length === 0) {
6,831✔
2433
      this.value = value;
6,798✔
2434
      this.emit(CharacteristicEventTypes.CHANGE, { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context });
6,798✔
2435
      return Promise.resolve();
6,798✔
2436
    } else {
2437
      return new Promise((resolve, reject) => {
33✔
2438
        try {
33✔
2439
          this.emit(CharacteristicEventTypes.SET, value, once((status, writeResponse) => {
33✔
2440
            if (status) {
30✔
2441
              if (typeof status === "number") {
12✔
2442
                const hapStatusError = new HapStatusError(status);
6✔
2443
                this.statusCode = hapStatusError.hapStatus;
6✔
2444
              } else if (status instanceof HapStatusError) {
6✔
2445
                this.statusCode = status.hapStatus;
3✔
2446
              } else {
2447
                debug("[%s] Received error from set handler %s", this.displayName, status.stack);
3✔
2448
                this.statusCode = extractHAPStatusFromError(status);
3✔
2449
              }
2450
              reject(this.statusCode);
12✔
2451
              return;
12✔
2452
            }
2453

2454
            this.statusCode = HAPStatus.SUCCESS;
18✔
2455

2456
            if (writeResponse != null && this.props.perms.includes(Perms.WRITE_RESPONSE)) {
18✔
2457
              // support write response simply by letting the implementor pass the response as second argument to the callback
2458
              this.value = this.validateUserInput(writeResponse);
6✔
2459
              resolve(this.value!);
6✔
2460
            } else {
2461
              if (writeResponse != null) {
12!
2462
                this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response",
×
2463
                  CharacteristicWarningType.DEBUG_MESSAGE);
2464
              }
2465
              this.value = value;
12✔
2466
              resolve();
12✔
2467

2468
              this.emit(
12✔
2469
                CharacteristicEventTypes.CHANGE,
2470
                { originator: connection, oldValue: oldValue, newValue: value, reason: ChangeReason.WRITE, context: context },
2471
              );
2472
            }
2473
          }), context, connection);
2474
        } catch (error) {
2475
          this.characteristicWarning(`Unhandled error thrown inside write handler for characteristic: ${error?.message}`,
×
2476
            CharacteristicWarningType.ERROR_MESSAGE, error?.stack);
2477
          this.statusCode = HAPStatus.SERVICE_COMMUNICATION_FAILURE;
×
2478
          reject(HAPStatus.SERVICE_COMMUNICATION_FAILURE);
×
2479
        }
2480
      });
2481
    }
2482
  }
2483

2484
  /**
2485
   * Called once a HomeKit controller subscribes to events of this characteristic.
2486
   * @private
2487
   */
2488
  subscribe(): void {
2489
    if (this.subscriptions === 0) {
39✔
2490
      this.emit(CharacteristicEventTypes.SUBSCRIBE);
27✔
2491
    }
2492
    this.subscriptions++;
39✔
2493
  }
2494

2495
  /**
2496
   * Called once a HomeKit controller unsubscribe to events of this characteristic or a HomeKit controller
2497
   * which was subscribed to this characteristic disconnects.
2498
   * @private
2499
   */
2500
  unsubscribe(): void {
2501
    const wasOne = this.subscriptions === 1;
24✔
2502
    this.subscriptions--;
24✔
2503
    this.subscriptions = Math.max(this.subscriptions, 0);
24✔
2504
    if (wasOne) {
24✔
2505
      this.emit(CharacteristicEventTypes.UNSUBSCRIBE);
15✔
2506
    }
2507
  }
2508

2509
  protected getDefaultValue(): Nullable<CharacteristicValue> {
2510
    // noinspection JSDeprecatedSymbols
2511
    switch (this.props.format) {
22,899!
2512
    case Formats.BOOL:
2513
      return false;
3,195✔
2514
    case Formats.STRING:
2515
      switch (this.UUID) {
4,947✔
2516
      case Characteristic.Manufacturer.UUID:
2517
        return "Default-Manufacturer";
351✔
2518
      case Characteristic.Model.UUID:
2519
        return "Default-Model";
351✔
2520
      case Characteristic.SerialNumber.UUID:
2521
        return "Default-SerialNumber";
351✔
2522
      case Characteristic.FirmwareRevision.UUID:
2523
        return "0.0.0";
351✔
2524
      default:
2525
        return "";
3,543✔
2526
      }
2527
    case Formats.DATA:
2528
      return ""; // who knows!
387✔
2529
    case Formats.TLV8:
2530
      return ""; // who knows!
5,361✔
2531
    case Formats.INT:
2532
    case Formats.FLOAT:
2533
    case Formats.UINT8:
2534
    case Formats.UINT16:
2535
    case Formats.UINT32:
2536
    case Formats.UINT64:
2537
      switch(this.UUID) {
9,009✔
2538
      case Characteristic.CurrentTemperature.UUID:
2539
        return 0; // some existing integrations expect this to be 0 by default
36✔
2540
      default: {
2541
        if (this.props.validValues?.length && typeof this.props.validValues[0] === "number") {
8,973✔
2542
          return this.props.validValues[0];
5,811✔
2543
        }
2544
        if (typeof this.props.minValue === "number" && Number.isFinite(this.props.minValue)) {
3,162✔
2545
          return this.props.minValue;
2,091✔
2546
        }
2547
        return 0;
1,071✔
2548
      }
2549
      }
2550
    default:
2551
      return 0;
×
2552
    }
2553
  }
2554

2555
  /**
2556
   * Checks if the value received from the HAP request is valid.
2557
   * If returned false the received value is not valid and {@link HAPStatus.INVALID_VALUE_IN_REQUEST}
2558
   * must be returned.
2559
   * @param value - Value supplied by the HomeKit controller
2560
   */
2561
  private validateClientSuppliedValue(value?: Nullable<CharacteristicValue>): CharacteristicValue {
2562
    if (value == null) {
336✔
2563
      throw new Error(`Client supplied invalid value for ${this.props.format}: ${value}`);
3✔
2564
    }
2565

2566
    switch (this.props.format) {
333✔
2567
    case Formats.BOOL: {
2568
      if (typeof value === "boolean") {
54✔
2569
        return value;
42✔
2570
      }
2571

2572
      if (typeof value === "number" && (value === 1 || value === 0)) {
12✔
2573
        return Boolean(value);
6✔
2574
      }
2575

2576
      throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
6✔
2577
    }
2578
    case Formats.INT:
2579
    case Formats.FLOAT:
2580
    case Formats.UINT8:
2581
    case Formats.UINT16:
2582
    case Formats.UINT32:
2583
    case Formats.UINT64: {
2584
      if (typeof value === "boolean") {
240✔
2585
        value = value ? 1 : 0;
36✔
2586
      }
2587

2588
      if (typeof value !== "number" || !Number.isFinite(value)) {
240✔
2589
        throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
54✔
2590
      }
2591

2592
      const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format));
186✔
2593
      const numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format));
186✔
2594

2595
      if (typeof numericMin === "number" && value < numericMin) {
186✔
2596
        throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${numericMin}`);
18✔
2597
      }
2598

2599
      if (typeof numericMax === "number" && value > numericMax) {
168✔
2600
        throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${numericMax}`);
18✔
2601
      }
2602

2603
      if (this.props.validValues && !this.props.validValues.includes(value)) {
150✔
2604
        throw new Error(`Client supplied value of ${value} is not in ${this.props.validValues.toString()}`);
18✔
2605
      }
2606

2607
      if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
132✔
2608
        if (value < this.props.validValueRanges[0]) {
54✔
2609
          throw new Error(`Client supplied value of ${value} is less than the minimum allowed value of ${this.props.validValueRanges[0]}`);
18✔
2610
        }
2611
        if (value > this.props.validValueRanges[1]) {
36✔
2612
          throw new Error(`Client supplied value of ${value} is greater than the maximum allowed value of ${this.props.validValueRanges[1]}`);
18✔
2613
        }
2614
      }
2615

2616
      return value;
96✔
2617
    }
2618
    case Formats.STRING: {
2619
      if (typeof value !== "string") {
15✔
2620
        throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
6✔
2621
      }
2622

2623
      const maxLength = this.props.maxLen != null? this.props.maxLen: 64; // default is 64; max is 256 which is set in setProps
9✔
2624
      if (value.length > maxLength) {
9✔
2625
        throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`);
3✔
2626
      }
2627

2628
      return value;
6✔
2629
    }
2630
    case Formats.DATA: {
2631
      if (typeof value !== "string") {
15✔
2632
        throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
6✔
2633
      }
2634

2635
      // we don't validate base64 here
2636

2637
      const maxLength = this.props.maxDataLen != null? this.props.maxDataLen: 0x200000; // default is 0x200000
9✔
2638
      if (value.length > maxLength) {
9✔
2639
        throw new Error(`Client supplied value length of ${value.length} exceeds maximum length allowed of ${maxLength}`);
3✔
2640
      }
2641

2642
      return value;
6✔
2643
    }
2644
    case Formats.TLV8:
2645
      if (typeof value !== "string") {
9✔
2646
        throw new Error(`Client supplied invalid type for ${this.props.format}: "${value}" (${typeof value})`);
6✔
2647
      }
2648

2649
      return value;
3✔
2650
    }
2651

2652
    return value;
×
2653
  }
2654

2655
  /**
2656
   * Checks if the value received from the API call is valid.
2657
   * It adjusts the value where it makes sense, prints a warning where values may be rejected with an error
2658
   * in the future and throws an error which can't be converted to a valid value.
2659
   *
2660
   * @param value - The value received from the API call
2661
   * @param warningType - Optionally defines the warning type to use when raising a {@link CharacteristicEventTypes.CHARACTERISTIC_WARNING}.
2662
   */
2663
  private validateUserInput(value?: Nullable<CharacteristicValue>, warningType = CharacteristicWarningType.WARN_MESSAGE): Nullable<CharacteristicValue> {
9,960✔
2664
    if (value === null) {
11,433✔
2665
      if (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID) { // mirrors the statement in case: Formats.STRING
24!
2666
        this.characteristicWarning("characteristic must have a non null value otherwise HomeKit will reject this accessory, ignoring new value",
×
2667
          CharacteristicWarningType.ERROR_MESSAGE);
2668
        return this.value; // don't change the value
×
2669
      }
2670

2671
      if (this.props.format === Formats.DATA || this.props.format === Formats.TLV8) {
24✔
2672
        return value; // TLV8 and DATA formats are allowed to have null as a value
6✔
2673
      }
2674

2675
      /**
2676
       * A short disclaimer here.
2677
       * null is actually a perfectly valid value for characteristics to have.
2678
       * The Home app will show "no response" for some characteristics for which it can't handle null
2679
       * but ultimately its valid and the developers decision what the return.
2680
       * BUT: out of history hap-nodejs did replace null with the last known value and thus
2681
       * homebridge devs started to adopting this method as a way of not changing the value in a GET handler.
2682
       * As an intermediate step we kept the behavior but added a warning printed to the console.
2683
       * In a future update we will do the breaking change of return null below!
2684
       */
2685

2686
      if (this.UUID.endsWith(BASE_UUID)) { // we have an apple defined characteristic (at least assuming nobody else uses the UUID namespace)
18✔
2687
        if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
6!
2688
          return value; // null is allowed as a value for ProgrammableSwitchEvent
×
2689
        }
2690

2691
        this.characteristicWarning("characteristic was supplied illegal value: null! Home App will reject null for Apple defined characteristics",
6✔
2692
          warningType);
2693

2694
        // if the value has been set previously, return it now, otherwise continue with validation to have a default value set.
2695
        if (this.value !== null) {
6✔
2696
          return this.value;
3✔
2697
        }
2698
      } else {
2699
        // we currently allow null for any non-custom defined characteristics
2700
        return value;
12✔
2701
      }
2702
    }
2703

2704
    switch (this.props.format) {
11,412✔
2705
    case Formats.BOOL: {
2706
      if (typeof value === "boolean") {
726✔
2707
        return value;
693✔
2708
      }
2709
      if (typeof value === "number") {
33✔
2710
        return value === 1;
6✔
2711
      }
2712
      if (typeof value === "string") {
27✔
2713
        return value === "1" || value === "true";
12✔
2714
      }
2715

2716
      this.characteristicWarning("characteristic value expected boolean and received " + typeof value, warningType);
15✔
2717
      return false;
15✔
2718
    }
2719
    case Formats.INT:
2720
    case Formats.FLOAT:
2721
    case Formats.UINT8:
2722
    case Formats.UINT16:
2723
    case Formats.UINT32:
2724
    case Formats.UINT64: {
2725
      if (typeof value === "boolean") {
4,452✔
2726
        value = value ? 1 : 0;
2,007✔
2727
      }
2728
      if (typeof value === "string") {
4,452✔
2729
        value = this.props.format === Formats.FLOAT ? parseFloat(value) : parseInt(value, 10);
39✔
2730
      }
2731
      if (typeof value !== "number" || !Number.isFinite(value)) {
4,452✔
2732
        this.characteristicWarning(`characteristic value expected valid finite number and received "${value}" (${typeof value})`, warningType);
102✔
2733
        value = typeof this.value === "number" ? this.value : this.props.minValue || 0;
102✔
2734
      }
2735

2736
      const numericMin = maxWithUndefined(this.props.minValue, numericLowerBound(this.props.format));
4,452✔
2737
      let numericMax = minWithUndefined(this.props.maxValue, numericUpperBound(this.props.format));
4,452✔
2738

2739
      let stepValue: number | undefined;
2740
      if (this.props.format === Formats.FLOAT) {
4,452✔
2741
        stepValue = this.props.minStep;
201✔
2742
      } else {
2743
        stepValue = maxWithUndefined(this.props.minStep, 1);
4,251✔
2744
      }
2745

2746
      if (stepValue != null && stepValue > 0) {
4,452✔
2747
        const minValue = this.props.minValue != null ? this.props.minValue : 0;
4,335✔
2748
        if (numericMax != null) {
4,335!
2749
          numericMax = stepValue * Math.floor((numericMax - minValue) / stepValue) + minValue;
4,335✔
2750
        }
2751
        value = stepValue * Math.round((value - minValue) / stepValue) + minValue;
4,335✔
2752
      }
2753

2754
      if (numericMin != null && value < numericMin) {
4,452✔
2755
        this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}`, warningType);
42✔
2756
        value = numericMin;
42✔
2757
      }
2758
      if (numericMax != null && value > numericMax) {
4,452✔
2759
        this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded maximum of ${numericMax}`, warningType);
42✔
2760
        value = numericMax;
42✔
2761
      }
2762

2763
      if (this.props.validValues && !this.props.validValues.includes(value)) {
4,452✔
2764
        this.characteristicWarning(`characteristic value ${value} is not contained in valid values array`, warningType);
18✔
2765
        return this.props.validValues.includes(this.value as number) ? this.value : (this.props.validValues[0] || 0);
18!
2766
      }
2767

2768
      if (this.props.validValueRanges && this.props.validValueRanges.length === 2) {
4,434✔
2769
        if (value < this.props.validValueRanges[0]) {
54✔
2770
          this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
18✔
2771
            + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType);
2772
          value = this.props.validValueRanges[0];
18✔
2773
        } else if (value > this.props.validValueRanges[1]) {
36!
2774
          this.characteristicWarning(`characteristic was supplied illegal value: number ${value} not contained in valid value range of `
×
2775
            + `${this.props.validValueRanges}, supplying illegal values will throw errors in the future`, warningType);
2776
          value = this.props.validValueRanges[1];
×
2777
        }
2778
      }
2779

2780
      return value;
4,434✔
2781
    }
2782
    case Formats.STRING: {
2783
      if (typeof value === "number") {
1,986✔
2784
        this.characteristicWarning("characteristic was supplied illegal value: number instead of string, " +
3✔
2785
          "supplying illegal values will throw errors in the future", warningType);
2786
        value = String(value);
3✔
2787
      }
2788
      if (typeof value !== "string") {
1,986✔
2789
        this.characteristicWarning("characteristic value expected string and received " + (typeof value), warningType);
9✔
2790
        value = typeof this.value === "string" ? this.value : value + "";
9✔
2791
      }
2792

2793
      // mirrors the case value = null at the beginning
2794
      if (value.length <= 1 && (this.UUID === Characteristic.Model.UUID || this.UUID === Characteristic.SerialNumber.UUID)) {
1,986!
2795
        this.characteristicWarning(`[${this.displayName}] characteristic must have a length of more than 1 character otherwise`
×
2796
          + ` HomeKit will reject this accessory, ignoring new value ${warningType}`);
2797
        return this.value; // just return the current value
×
2798
      }
2799

2800
      const maxLength = this.props.maxLen ?? 64; // default is 64 (max is 256 which is set in setProps)
1,986✔
2801
      if (value.length > maxLength) {
1,986✔
2802
        this.characteristicWarning(`characteristic was supplied illegal value: string '${value}' exceeded max length of ${maxLength}`, warningType);
6✔
2803
        value = value.substring(0, maxLength);
6✔
2804
      }
2805

2806
      if (value.length > 0 && this.UUID === Characteristic.ConfiguredName.UUID) {
1,986✔
2807
        checkName(this.displayName, "ConfiguredName", value);
3✔
2808
      }
2809

2810
      return value;
1,986✔
2811
    }
2812
    case Formats.DATA:
2813
      if (typeof value !== "string") {
21✔
2814
        throw new Error("characteristic with DATA format must have string value");
3✔
2815
      }
2816

2817
      if (this.props.maxDataLen != null && value.length > this.props.maxDataLen) {
18✔
2818
        // can't cut it as we would basically set binary rubbish afterwards
2819
        throw new Error("characteristic with DATA format exceeds specified maxDataLen");
3✔
2820
      }
2821
      return value;
15✔
2822
    case Formats.TLV8:
2823
      if (value === undefined) {
4,227!
2824
        this.characteristicWarning("characteristic was supplied illegal value: undefined", warningType);
×
2825
        return this.value;
×
2826
      }
2827
      return value; // we trust that this is valid tlv8
4,227✔
2828
    }
2829

2830
    // hopefully it shouldn't get to this point
2831
    if (value === undefined) {
×
2832
      this.characteristicWarning("characteristic was supplied illegal value: undefined", CharacteristicWarningType.ERROR_MESSAGE);
×
2833
      return this.value;
×
2834
    }
2835

2836
    return value;
×
2837
  }
2838

2839
  /**
2840
   * @private used to assign iid to characteristic
2841
   */
2842
  _assignID(identifierCache: IdentifierCache, accessoryName: string, serviceUUID: string, serviceSubtype?: string): void {
2843
    // generate our IID based on our UUID
2844
    this.iid = identifierCache.getIID(accessoryName, serviceUUID, serviceSubtype, this.UUID);
1,831✔
2845
  }
2846

2847
  private characteristicWarning(message: string, type = CharacteristicWarningType.WARN_MESSAGE, stack = new Error().stack): void {
1,014✔
2848
    this.emit(CharacteristicEventTypes.CHARACTERISTIC_WARNING, type, message, stack);
534✔
2849
  }
2850

2851
  /**
2852
   * @param event
2853
   * @private
2854
   */
2855
  removeAllListeners(event?: string | symbol): this {
2856
    if (!event) {
30!
2857
      this.removeOnGet();
×
2858
      this.removeOnSet();
×
2859
    }
2860
    return super.removeAllListeners(event);
30✔
2861
  }
2862

2863
  /**
2864
   * @param characteristic
2865
   * @private
2866
   */
2867
  replaceBy(characteristic: Characteristic): void {
2868
    this.props = characteristic.props;
×
2869
    this.updateValue(characteristic.value);
×
2870

2871
    const getListeners = characteristic.listeners(CharacteristicEventTypes.GET);
×
2872
    if (getListeners.length) {
×
2873
      // the callback can only be called once, so we remove all old listeners
2874
      this.removeAllListeners(CharacteristicEventTypes.GET);
×
2875
      getListeners.forEach(listener => this.addListener(CharacteristicEventTypes.GET, listener));
×
2876
    }
2877

2878
    this.removeOnGet();
×
2879
    if (characteristic.getHandler) {
×
2880
      this.onGet(characteristic.getHandler);
×
2881
    }
2882

2883
    const setListeners = characteristic.listeners(CharacteristicEventTypes.SET);
×
2884
    if (setListeners.length) {
×
2885
      // the callback can only be called once, so we remove all old listeners
2886
      this.removeAllListeners(CharacteristicEventTypes.SET);
×
2887
      setListeners.forEach(listener => this.addListener(CharacteristicEventTypes.SET, listener));
×
2888
    }
2889

2890
    this.removeOnSet();
×
2891
    if (characteristic.setHandler) {
×
2892
      this.onSet(characteristic.setHandler);
×
2893
    }
2894
  }
2895

2896
  /**
2897
   * Returns a JSON representation of this characteristic suitable for delivering to HAP clients.
2898
   * @private used to generate response to /accessories query
2899
   */
2900
  async toHAP(connection: HAPConnection, contactGetHandlers = true): Promise<CharacteristicJsonObject> {
27✔
2901
    const object = this.internalHAPRepresentation();
54✔
2902

2903
    if (!this.props.perms.includes(Perms.PAIRED_READ)) {
54✔
2904
      object.value = undefined;
6✔
2905
    } else if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) {
48!
2906
      // special workaround for event only programmable switch event, which must always return null
2907
      object.value = null;
×
2908
    } else { // query the current value
2909
      const value = contactGetHandlers
48!
2910
        ? await this.handleGetRequest(connection).catch(() => {
2911
          const value = this.getDefaultValue();
×
2912
          debug("[%s] Error getting value for characteristic on /accessories request. Returning default value instead: %s", this.displayName, `${value}`);
×
2913
          return value; // use default value
×
2914
        })
2915
        : this.value;
2916

2917
      object.value = formatOutgoingCharacteristicValue(value, this.props);
48✔
2918
    }
2919

2920
    return object;
54✔
2921
  }
2922

2923
  /**
2924
   * Returns a JSON representation of this characteristic without the value.
2925
   * @private used to generate the config hash
2926
   */
2927
  internalHAPRepresentation(): CharacteristicJsonObject {
2928
    assert(this.iid,"iid cannot be undefined for characteristic '" + this.displayName + "'");
1,480✔
2929
    // TODO include the value for characteristics of the AccessoryInformation service
2930
    return {
1,480✔
2931
      type: toShortForm(this.UUID),
2932
      iid: this.iid!,
2933
      value: null,
2934
      perms: this.props.perms,
2935
      description: this.props.description || this.displayName,
2,960✔
2936
      format: this.props.format,
2937
      unit: this.props.unit,
2938
      minValue: this.props.minValue,
2939
      maxValue: this.props.maxValue,
2940
      minStep: this.props.minStep,
2941
      maxLen: this.props.maxLen,
2942
      maxDataLen: this.props.maxDataLen,
2943
      "valid-values": this.props.validValues,
2944
      "valid-values-range": this.props.validValueRanges,
2945
    };
2946
  }
2947

2948
  /**
2949
   * Serialize characteristic into json string.
2950
   *
2951
   * @param characteristic - Characteristic object.
2952
   * @private used to store characteristic on disk
2953
   */
2954
  static serialize(characteristic: Characteristic): SerializedCharacteristic {
2955
    let constructorName: string | undefined;
2956
    if (characteristic.constructor.name !== "Characteristic") {
204✔
2957
      constructorName = characteristic.constructor.name;
201✔
2958
    }
2959

2960
    return {
204✔
2961
      displayName: characteristic.displayName,
2962
      UUID: characteristic.UUID,
2963
      eventOnlyCharacteristic: characteristic.UUID === Characteristic.ProgrammableSwitchEvent.UUID, // support downgrades for now
2964
      constructorName: constructorName,
2965
      value: characteristic.value,
2966
      props: clone({}, characteristic.props),
2967
    };
2968
  }
2969

2970
  /**
2971
   * Deserialize characteristic from json string.
2972
   *
2973
   * @param json - Json string representing a characteristic.
2974
   * @private used to recreate characteristic from disk
2975
   */
2976
  static deserialize(json: SerializedCharacteristic): Characteristic {
2977
    let characteristic: Characteristic;
2978

2979
    if (json.constructorName && json.constructorName.charAt(0).toUpperCase() === json.constructorName.charAt(0)
207✔
2980
      && Characteristic[json.constructorName as keyof (typeof Characteristic)]) { // MUST start with uppercase character and must exist on Characteristic object
2981
      const constructor = Characteristic[json.constructorName as keyof (typeof Characteristic)] as { new(): Characteristic };
90✔
2982
      characteristic = new constructor();
90✔
2983
      characteristic.displayName = json.displayName;
90✔
2984
      characteristic.setProps(json.props);
90✔
2985
    } else {
2986
      characteristic = new Characteristic(json.displayName, json.UUID, json.props);
117✔
2987
    }
2988

2989
    characteristic.value = json.value;
207✔
2990

2991
    return characteristic;
207✔
2992
  }
2993

2994
}
2995

2996
// We have a cyclic dependency problem. Within this file we have the definitions of "./definitions" as
2997
// type imports only (in order to define the static properties). Setting those properties is done outside
2998
// this file, within the definition files. Therefore, we import it at the end of this file. Seems weird, but is important.
2999
import "./definitions/CharacteristicDefinitions";
30✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc