UNPKG

homebridge-plugin-wrapper

Version:

Wrapper for Homebridge and NodeJS-HAP with reduced dependencies that allows to intercept plugin values and also send to them

988 lines (987 loc) 74.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Characteristic = exports.CharacteristicEventTypes = exports.ChangeReason = exports.Access = exports.Perms = exports.Units = exports.Formats = void 0; var tslib_1 = require("tslib"); var assert_1 = (0, tslib_1.__importDefault)(require("assert")); var debug_1 = (0, tslib_1.__importDefault)(require("debug")); var events_1 = require("events"); var definitions_1 = require("./definitions"); var HAPServer_1 = require("./HAPServer"); var clone_1 = require("./util/clone"); var hapStatusError_1 = require("./util/hapStatusError"); var once_1 = require("./util/once"); var request_util_1 = require("./util/request-util"); var uuid_1 = require("./util/uuid"); var debug = (0, debug_1.default)("HAP-NodeJS:Characteristic"); var Formats; (function (Formats) { Formats["BOOL"] = "bool"; /** * Signed 32-bit integer */ Formats["INT"] = "int"; /** * Signed 64-bit floating point */ Formats["FLOAT"] = "float"; /** * String encoded in utf8 */ Formats["STRING"] = "string"; /** * Unsigned 8-bit integer. */ Formats["UINT8"] = "uint8"; /** * Unsigned 16-bit integer. */ Formats["UINT16"] = "uint16"; /** * Unsigned 32-bit integer. */ Formats["UINT32"] = "uint32"; /** * Unsigned 64-bit integer. */ Formats["UINT64"] = "uint64"; /** * Data is base64 encoded string. */ Formats["DATA"] = "data"; /** * Base64 encoded tlv8 string. */ Formats["TLV8"] = "tlv8"; /** * @deprecated Not contained in the HAP spec */ Formats["ARRAY"] = "array"; /** * @deprecated Not contained in the HAP spec */ Formats["DICTIONARY"] = "dict"; })(Formats = exports.Formats || (exports.Formats = {})); var Units; (function (Units) { /** * Celsius is the only temperature unit in the HomeKit Accessory Protocol. * Unit conversion is always done on the client side e.g. on the iPhone in the Home App depending on * the configured unit on the device itself. */ Units["CELSIUS"] = "celsius"; Units["PERCENTAGE"] = "percentage"; Units["ARC_DEGREE"] = "arcdegrees"; Units["LUX"] = "lux"; Units["SECONDS"] = "seconds"; })(Units = exports.Units || (exports.Units = {})); var Perms; (function (Perms) { // noinspection JSUnusedGlobalSymbols /** * @deprecated replaced by {@link PAIRED_READ}. Kept for backwards compatibility. */ Perms["READ"] = "pr"; /** * @deprecated replaced by {@link PAIRED_WRITE}. Kept for backwards compatibility. */ Perms["WRITE"] = "pw"; Perms["PAIRED_READ"] = "pr"; Perms["PAIRED_WRITE"] = "pw"; Perms["NOTIFY"] = "ev"; Perms["EVENTS"] = "ev"; Perms["ADDITIONAL_AUTHORIZATION"] = "aa"; Perms["TIMED_WRITE"] = "tw"; Perms["HIDDEN"] = "hd"; Perms["WRITE_RESPONSE"] = "wr"; })(Perms = exports.Perms || (exports.Perms = {})); var Access; (function (Access) { Access[Access["READ"] = 0] = "READ"; Access[Access["WRITE"] = 1] = "WRITE"; Access[Access["NOTIFY"] = 2] = "NOTIFY"; })(Access = exports.Access || (exports.Access = {})); var ChangeReason; (function (ChangeReason) { /** * Reason used when HomeKit writes a value or the API user calls {@link Characteristic.setValue}. */ ChangeReason["WRITE"] = "write"; /** * Reason used when the API user calls the method {@link Characteristic.updateValue}. */ ChangeReason["UPDATE"] = "update"; /** * Used when when HomeKit reads a value or the API user calls the deprecated method {@link Characteristic.getValue}. */ ChangeReason["READ"] = "read"; /** * Used when call to {@link Characteristic.sendEventNotification} was made. */ ChangeReason["EVENT"] = "event"; })(ChangeReason = exports.ChangeReason || (exports.ChangeReason = {})); var CharacteristicEventTypes; (function (CharacteristicEventTypes) { /** * This event is thrown when a HomeKit controller wants to read the current value of the characteristic. * The event handler should call the supplied callback as fast as possible. * * HAP-NodeJS will complain about slow running get handlers after 3 seconds and terminate the request after 10 seconds. */ CharacteristicEventTypes["GET"] = "get"; /** * This event is thrown when a HomeKit controller wants to write a new value to the characteristic. * The event handler should call the supplied callback as fast as possible. * * HAP-NodeJS will complain about slow running set handlers after 3 seconds and terminate the request after 10 seconds. */ CharacteristicEventTypes["SET"] = "set"; /** * Emitted after a new value is set for the characteristic. * The new value can be set via a request by a HomeKit controller or via an API call. */ CharacteristicEventTypes["CHANGE"] = "change"; /** * @private */ CharacteristicEventTypes["SUBSCRIBE"] = "subscribe"; /** * @private */ CharacteristicEventTypes["UNSUBSCRIBE"] = "unsubscribe"; /** * @private */ CharacteristicEventTypes["CHARACTERISTIC_WARNING"] = "characteristic-warning"; })(CharacteristicEventTypes = exports.CharacteristicEventTypes || (exports.CharacteristicEventTypes = {})); var ValidValuesIterable = /** @class */ (function () { function ValidValuesIterable(props) { (0, assert_1.default)((0, request_util_1.isNumericFormat)(props.format), "Cannot instantiate valid values iterable when format is not numeric. Found " + props.format); this.props = props; } ValidValuesIterable.prototype[Symbol.iterator] = function () { var _a, _b, value, e_1_1, min, max, stepValue, i; var e_1, _c; return (0, tslib_1.__generator)(this, function (_d) { switch (_d.label) { case 0: if (!this.props.validValues) return [3 /*break*/, 9]; _d.label = 1; case 1: _d.trys.push([1, 6, 7, 8]); _a = (0, tslib_1.__values)(this.props.validValues), _b = _a.next(); _d.label = 2; case 2: if (!!_b.done) return [3 /*break*/, 5]; value = _b.value; return [4 /*yield*/, value]; case 3: _d.sent(); _d.label = 4; case 4: _b = _a.next(); return [3 /*break*/, 2]; case 5: return [3 /*break*/, 8]; case 6: e_1_1 = _d.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 8]; case 7: try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_1) throw e_1.error; } return [7 /*endfinally*/]; case 8: return [3 /*break*/, 13]; case 9: min = 0; max = void 0; stepValue = 1; if (this.props.validValueRanges) { min = this.props.validValueRanges[0]; max = this.props.validValueRanges[1]; } else if (this.props.minValue != null && this.props.maxValue != null) { min = this.props.minValue; max = this.props.maxValue; if (this.props.minStep != null) { stepValue = this.props.minStep; } } else if ((0, request_util_1.isUnsignedNumericFormat)(this.props.format)) { max = (0, request_util_1.numericUpperBound)(this.props.format); } else { throw new Error("Could not find valid iterator strategy for props: " + JSON.stringify(this.props)); } i = min; _d.label = 10; case 10: if (!(i <= max)) return [3 /*break*/, 13]; return [4 /*yield*/, i]; case 11: _d.sent(); _d.label = 12; case 12: i += stepValue; return [3 /*break*/, 10]; case 13: return [2 /*return*/]; } }); }; return ValidValuesIterable; }()); var numberPattern = /^-?\d+$/; function extractHAPStatusFromError(error) { var errorValue = -70402 /* SERVICE_COMMUNICATION_FAILURE */; if (numberPattern.test(error.message)) { var value = parseInt(error.message, 10); if ((0, HAPServer_1.IsKnownHAPStatusError)(value)) { errorValue = value; } } return errorValue; } function maxWithUndefined(a, b) { if (a === undefined) { return b; } else if (b === undefined) { return a; } else { return Math.max(a, b); } } function minWithUndefined(a, b) { if (a === undefined) { return b; } else if (b === undefined) { return a; } else { return Math.min(a, b); } } /** * Characteristic represents a particular typed variable that can be assigned to a Service. For instance, a * "Hue" Characteristic might store a 'float' value of type 'arcdegrees'. You could add the Hue Characteristic * to a {@link Service} in order to store that value. A particular Characteristic is distinguished from others by its * UUID. HomeKit provides a set of known Characteristic UUIDs defined in HomeKit.ts along with a * corresponding concrete subclass. * * You can also define custom Characteristics by providing your own UUID. Custom Characteristics can be added * to any native or custom Services, but Siri will likely not be able to work with these. */ var Characteristic = /** @class */ (function (_super) { (0, tslib_1.__extends)(Characteristic, _super); function Characteristic(displayName, UUID, props) { var _this = _super.call(this) || this; _this.iid = null; _this.value = null; /** * @deprecated replaced by {@link statusCode} * @private */ _this.status = null; /** * @private */ _this.statusCode = 0 /* SUCCESS */; _this.subscriptions = 0; _this.displayName = displayName; _this.UUID = UUID; _this.props = { format: "int" /* INT */, perms: ["ev" /* NOTIFY */], }; _this.setProps(props || {}); // ensure sanity checks are called return _this; } /** * Accepts a function that will be called to retrieve the current value of a Characteristic. * The function must return a valid Characteristic value for the Characteristic type. * May optionally return a promise. * * @example * ```ts * Characteristic.onGet(async () => { * return true; * }); * ``` * @param handler */ Characteristic.prototype.onGet = function (handler) { if (typeof handler !== "function") { this.characteristicWarning(".onGet handler must be a function"); return this; } this.getHandler = handler; return this; }; /** * Removes the {@link CharacteristicGetHandler} handler which was configured using {@link onGet}. */ Characteristic.prototype.removeOnGet = function () { this.getHandler = undefined; return this; }; /** * Accepts a function that will be called when setting the value of a Characteristic. * If the characteristic supports {@link Perms.WRITE_RESPONSE} and the request requests a write response value, * the returned value will be used. * May optionally return a promise. * * @example * ```ts * Characteristic.onSet(async (value: CharacteristicValue) => { * console.log(value); * }); * ``` * @param handler */ Characteristic.prototype.onSet = function (handler) { if (typeof handler !== "function") { this.characteristicWarning(".onSet handler must be a function"); return this; } this.setHandler = handler; return this; }; /** * Removes the {@link CharacteristicSetHandler} which was configured using {@link onSet}. */ Characteristic.prototype.removeOnSet = function () { this.setHandler = undefined; return this; }; /** * Updates the properties of this characteristic. * Properties passed via the parameter will be set. Any parameter set to null will be deleted. * See {@link CharacteristicProps}. * * @param props - Partial properties object with the desired updates. */ Characteristic.prototype.setProps = function (props) { (0, assert_1.default)(props, "props cannot be undefined when setting props"); // TODO calling setProps after publish doesn't lead to a increment in the current configuration number // for every value "null" can be used to reset props, except for required props if (props.format) { this.props.format = props.format; } if (props.perms) { (0, assert_1.default)(props.perms.length > 0, "characteristic prop perms cannot be empty array"); this.props.perms = props.perms; } if (props.unit !== undefined) { this.props.unit = props.unit != null ? props.unit : undefined; } if (props.description !== undefined) { this.props.description = props.description != null ? props.description : undefined; } // check minValue is valid for the format type if (props.minValue !== undefined) { if (props.minValue === null) { props.minValue = undefined; } else if (!(0, request_util_1.isNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property 'minValue' can only be set for characteristics with numeric format, but not for " + this.props.format, "error-message" /* ERROR_MESSAGE */); props.minValue = undefined; } else if (typeof props.minValue !== "number" || !Number.isFinite(props.minValue)) { this.characteristicWarning("Characteristic Property 'minValue' must be a finite number, received \"".concat(props.minValue, "\" (").concat(typeof props.minValue, ")"), "error-message" /* ERROR_MESSAGE */); props.minValue = undefined; } else { if (props.minValue < (0, request_util_1.numericLowerBound)(this.props.format)) { this.characteristicWarning("Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " + this.props.format + " minimum possible is " + (0, request_util_1.numericLowerBound)(this.props.format), "error-message" /* ERROR_MESSAGE */); props.minValue = (0, request_util_1.numericLowerBound)(this.props.format); } else if (props.minValue > (0, request_util_1.numericUpperBound)(this.props.format)) { this.characteristicWarning("Characteristic Property 'minValue' was set to " + props.minValue + ", but for numeric format " + this.props.format + " maximum possible is " + (0, request_util_1.numericUpperBound)(this.props.format), "error-message" /* ERROR_MESSAGE */); props.minValue = (0, request_util_1.numericLowerBound)(this.props.format); } } this.props.minValue = props.minValue; } // check maxValue is valid for the format type if (props.maxValue !== undefined) { if (props.maxValue === null) { props.maxValue = undefined; } else if (!(0, request_util_1.isNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property 'maxValue' can only be set for characteristics with numeric format, but not for " + this.props.format, "error-message" /* ERROR_MESSAGE */); props.maxValue = undefined; } else if (typeof props.maxValue !== "number" || !Number.isFinite(props.maxValue)) { this.characteristicWarning("Characteristic Property 'maxValue' must be a finite number, received \"".concat(props.maxValue, "\" (").concat(typeof props.maxValue, ")"), "error-message" /* ERROR_MESSAGE */); props.maxValue = undefined; } else { if (props.maxValue > (0, request_util_1.numericUpperBound)(this.props.format)) { this.characteristicWarning("Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " + this.props.format + " maximum possible is " + (0, request_util_1.numericUpperBound)(this.props.format), "error-message" /* ERROR_MESSAGE */); props.maxValue = (0, request_util_1.numericUpperBound)(this.props.format); } else if (props.maxValue < (0, request_util_1.numericLowerBound)(this.props.format)) { this.characteristicWarning("Characteristic Property 'maxValue' was set to " + props.maxValue + ", but for numeric format " + this.props.format + " minimum possible is " + (0, request_util_1.numericLowerBound)(this.props.format), "error-message" /* ERROR_MESSAGE */); props.maxValue = (0, request_util_1.numericUpperBound)(this.props.format); } } this.props.maxValue = props.maxValue; } if (props.minStep !== undefined) { if (props.minStep === null) { this.props.minStep = undefined; } else if (!(0, request_util_1.isNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property `minStep` can only be set for characteristics with numeric format, but not for " + this.props.format, "error-message" /* ERROR_MESSAGE */); } else { if (props.minStep < 1 && (0, request_util_1.isIntegerNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property `minStep` was set to a value lower than 1, " + "this will have no effect on format `" + this.props.format); } this.props.minStep = props.minStep; } } if (props.maxLen !== undefined) { if (props.maxLen === null) { this.props.maxLen = undefined; } else if (this.props.format !== "string" /* STRING */) { this.characteristicWarning("Characteristic Property `maxLen` can only be set for characteristics with format `STRING`, but not for " + this.props.format, "error-message" /* ERROR_MESSAGE */); } else { if (props.maxLen > 256) { this.characteristicWarning("Characteristic Property string `maxLen` cannot be bigger than 256"); props.maxLen = 256; } this.props.maxLen = props.maxLen; } } if (props.maxDataLen !== undefined) { if (props.maxDataLen === null) { this.props.maxDataLen = undefined; } else if (this.props.format !== "data" /* DATA */) { this.characteristicWarning("Characteristic Property `maxDataLen` can only be set for characteristics with format `DATA`, but not for " + this.props.format, "error-message" /* ERROR_MESSAGE */); } else { this.props.maxDataLen = props.maxDataLen; } } if (props.validValues !== undefined) { if (props.validValues === null) { this.props.validValues = undefined; } else if (!(0, request_util_1.isNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property `validValues` was supplied for non numeric format " + this.props.format); } else { (0, assert_1.default)(props.validValues.length, "characteristic prop validValues cannot be empty array"); this.props.validValues = props.validValues; } } if (props.validValueRanges !== undefined) { if (props.validValueRanges === null) { this.props.validValueRanges = undefined; } else if (!(0, request_util_1.isNumericFormat)(this.props.format)) { this.characteristicWarning("Characteristic Property `validValueRanges` was supplied for non numeric format " + this.props.format); } else { (0, assert_1.default)(props.validValueRanges.length === 2, "characteristic prop validValueRanges must have a length of 2"); this.props.validValueRanges = props.validValueRanges; } } if (props.adminOnlyAccess !== undefined) { this.props.adminOnlyAccess = props.adminOnlyAccess != null ? props.adminOnlyAccess : undefined; } if (this.props.minValue != null && this.props.maxValue != null) { // the eqeq instead of eqeqeq is important here if (this.props.minValue > this.props.maxValue) { // see https://github.com/homebridge/HAP-NodeJS/issues/690 this.props.minValue = undefined; this.props.maxValue = undefined; throw new Error("Error setting CharacteristicsProps for '" + this.displayName + "': 'minValue' cannot be greater or equal the 'maxValue'!"); } } return this; }; /** * This method can be used to gain a Iterator to loop over all valid values defined for this characteristic. * * The range of valid values can be defined using three different ways via the {@link CharacteristicProps} object * (set via the {@link setProps} method): * * First method is to specifically list every valid value inside {@link CharacteristicProps.validValues} * * Second you can specify a range via {@link CharacteristicProps.minValue} and {@link CharacteristicProps.maxValue} (with optionally defining * {@link CharacteristicProps.minStep}) * * And lastly you can specify a range via {@link CharacteristicProps.validValueRanges} * * Implicitly a valid value range is predefined for characteristics with Format {@link Formats.UINT8}, {@link Formats.UINT16}, * {@link Formats.UINT32} and {@link Formats.UINT64}: starting by zero to their respective maximum number * * The method will automatically detect which type of valid values definition is used and provide * the correct Iterator for that case. * * Note: This method is (obviously) only valid for numeric characteristics. * * @example * ```ts * // use the iterator to loop over every valid value... * for (const value of characteristic.validValuesIterator()) { * // Insert logic to run for every * } * * // ... or collect them in an array for storage or manipulation * const validValues = Array.from(characteristic.validValuesIterator()); * ``` */ Characteristic.prototype.validValuesIterator = function () { return new ValidValuesIterable(this.props); }; // noinspection JSUnusedGlobalSymbols /** * This method can be used to setup additional authorization for a characteristic. * For one it adds the {@link Perms.ADDITIONAL_AUTHORIZATION} permission to the characteristic * (if it wasn't already) to signal support for additional authorization to HomeKit. * Additionally an {@link AdditionalAuthorizationHandler} is setup up which is called * before a write request is performed. * * Additional Authorization Data can be added to SET request via a custom iOS App. * Before hap-nodejs executes a write request it will call the {@link AdditionalAuthorizationHandler} * with 'authData' supplied in the write request. The 'authData' is a base64 encoded string * (or undefined if no authData was supplied). * The {@link AdditionalAuthorizationHandler} must then return true or false to indicate if the write request * is authorized and should be accepted. * * @param handler - Handler called to check additional authorization data. */ Characteristic.prototype.setupAdditionalAuthorization = function (handler) { if (!this.props.perms.includes("aa" /* ADDITIONAL_AUTHORIZATION */)) { this.props.perms.push("aa" /* ADDITIONAL_AUTHORIZATION */); } this.additionalAuthorizationHandler = handler; }; /** * Updates the current value of the characteristic. * * @param callback * @param context * @private use to return the current value on HAP requests * * @deprecated */ Characteristic.prototype.getValue = function (callback, context) { this.handleGetRequest(undefined, context).then(function (value) { if (callback) { callback(null, value); } }, function (reason) { if (callback) { callback(reason); } }); }; Characteristic.prototype.setValue = function (value, callback, context) { if (value instanceof Error) { this.statusCode = value instanceof hapStatusError_1.HapStatusError ? value.hapStatus : extractHAPStatusFromError(value); // noinspection JSDeprecatedSymbols this.status = value; if (callback) { callback(); } return this; } if (callback && !context && typeof callback !== "function") { context = callback; callback = undefined; } try { value = this.validateUserInput(value); } catch (error) { this.characteristicWarning((error === null || error === void 0 ? void 0 : error.message) + "", "error-message" /* ERROR_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); if (callback) { callback(error); } return this; } this.handleSetRequest(value, undefined, context).then(function (value) { if (callback) { if (value) { // possible write response callback(null, value); } else { callback(null); } } }, function (reason) { if (callback) { callback(reason); } }); return this; }; Characteristic.prototype.updateValue = function (value, callback, context) { if (value instanceof Error) { this.statusCode = value instanceof hapStatusError_1.HapStatusError ? value.hapStatus : extractHAPStatusFromError(value); // noinspection JSDeprecatedSymbols this.status = value; if (callback) { callback(); } return this; } if (callback && !context && typeof callback !== "function") { context = callback; callback = undefined; } try { value = this.validateUserInput(value); } catch (error) { this.characteristicWarning((error === null || error === void 0 ? void 0 : error.message) + "", "error-message" /* ERROR_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); if (callback) { callback(); } return this; } this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols this.status = null; var oldValue = this.value; this.value = value; if (callback) { callback(); } this.emit("change" /* CHANGE */, { originator: undefined, oldValue: oldValue, newValue: value, reason: "update" /* UPDATE */, context: context }); return this; // for chaining }; /** * This method acts similarly to {@link updateValue} by setting the current value of the characteristic * without calling any {@link CharacteristicEventTypes.SET} or {@link onSet} handlers. * The difference is that this method forces an event notification sent (updateValue only sends one if the value changed). * This is especially useful for characteristics like {@link Characteristic.ButtonEvent} or {@link Characteristic.ProgrammableSwitchEvent}. * * @param value - The new value. * @param context - Passed to the {@link CharacteristicEventTypes.CHANGE} event handler. */ Characteristic.prototype.sendEventNotification = function (value, context) { this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols this.status = null; value = this.validateUserInput(value); var oldValue = this.value; this.value = value; this.emit("change" /* CHANGE */, { originator: undefined, oldValue: oldValue, newValue: value, reason: "event" /* EVENT */, context: context }); return this; // for chaining }; /** * Called when a HAP requests wants to know the current value of the characteristic. * * @param connection - The HAP connection from which the request originated from. * @param context - Deprecated parameter. There for backwards compatibility. * @private Used by the Accessory to load the characteristic value */ Characteristic.prototype.handleGetRequest = function (connection, context) { return (0, tslib_1.__awaiter)(this, void 0, void 0, function () { var value, oldValue, error_1, hapStatusError; var _this = this; return (0, tslib_1.__generator)(this, function (_a) { switch (_a.label) { case 0: if (!this.props.perms.includes("pr" /* PAIRED_READ */)) { // check if we are allowed to read from this characteristic throw -70405 /* WRITE_ONLY_CHARACTERISTIC */; } if (this.UUID === Characteristic.ProgrammableSwitchEvent.UUID) { // special workaround for event only programmable switch event, which must always return null return [2 /*return*/, null]; } if (!this.getHandler) return [3 /*break*/, 4]; if (this.listeners("get" /* GET */).length > 0) { this.characteristicWarning("Ignoring on('get') handler as onGet handler was defined instead"); } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.getHandler(context, connection)]; case 2: value = _a.sent(); this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols this.status = null; try { value = this.validateUserInput(value); } catch (error) { this.characteristicWarning("An illegal value was supplied by the read handler for characteristic: ".concat(error === null || error === void 0 ? void 0 : error.message), "warn-message" /* WARN_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); this.statusCode = -70402 /* SERVICE_COMMUNICATION_FAILURE */; // noinspection JSDeprecatedSymbols this.status = error; return [2 /*return*/, Promise.reject(-70402 /* SERVICE_COMMUNICATION_FAILURE */)]; } oldValue = this.value; this.value = value; if (oldValue !== value) { // emit a change event if necessary this.emit("change" /* CHANGE */, { originator: connection, oldValue: oldValue, newValue: value, reason: "read" /* READ */, context: context }); } return [2 /*return*/, value]; case 3: error_1 = _a.sent(); if (typeof error_1 === "number") { hapStatusError = new hapStatusError_1.HapStatusError(error_1); this.statusCode = hapStatusError.hapStatus; // noinspection JSDeprecatedSymbols this.status = hapStatusError; } else if (error_1 instanceof hapStatusError_1.HapStatusError) { this.statusCode = error_1.hapStatus; // noinspection JSDeprecatedSymbols this.status = error_1; } else { this.characteristicWarning("Unhandled error thrown inside read handler for characteristic: ".concat(error_1 === null || error_1 === void 0 ? void 0 : error_1.message), "error-message" /* ERROR_MESSAGE */, error_1 === null || error_1 === void 0 ? void 0 : error_1.stack); this.statusCode = -70402 /* SERVICE_COMMUNICATION_FAILURE */; // noinspection JSDeprecatedSymbols this.status = error_1; } throw this.statusCode; case 4: if (this.listeners("get" /* GET */).length === 0) { if (this.statusCode) { throw this.statusCode; } try { return [2 /*return*/, this.validateUserInput(this.value)]; } catch (error) { this.characteristicWarning("An illegal value was supplied by setting `value` for characteristic: ".concat(error === null || error === void 0 ? void 0 : error.message), "warn-message" /* WARN_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); return [2 /*return*/, Promise.reject(-70402 /* SERVICE_COMMUNICATION_FAILURE */)]; } } return [2 /*return*/, new Promise(function (resolve, reject) { try { _this.emit("get" /* GET */, (0, once_1.once)(function (status, value) { if (status) { if (typeof status === "number") { var hapStatusError = new hapStatusError_1.HapStatusError(status); _this.statusCode = hapStatusError.hapStatus; // noinspection JSDeprecatedSymbols _this.status = hapStatusError; } else if (status instanceof hapStatusError_1.HapStatusError) { _this.statusCode = status.hapStatus; // noinspection JSDeprecatedSymbols _this.status = status; } else { debug("[%s] Received error from get handler %s", _this.displayName, status.stack); _this.statusCode = extractHAPStatusFromError(status); // noinspection JSDeprecatedSymbols _this.status = status; } reject(_this.statusCode); return; } _this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols _this.status = null; value = _this.validateUserInput(value); var oldValue = _this.value; _this.value = value; resolve(value); if (oldValue !== value) { // emit a change event if necessary _this.emit("change" /* CHANGE */, { originator: connection, oldValue: oldValue, newValue: value, reason: "read" /* READ */, context: context }); } }), context, connection); } catch (error) { _this.characteristicWarning("Unhandled error thrown inside read handler for characteristic: ".concat(error === null || error === void 0 ? void 0 : error.message), "error-message" /* ERROR_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); _this.statusCode = -70402 /* SERVICE_COMMUNICATION_FAILURE */; // noinspection JSDeprecatedSymbols _this.status = error; reject(-70402 /* SERVICE_COMMUNICATION_FAILURE */); } })]; } }); }); }; /** * Called when a HAP requests update the current value of the characteristic. * * @param value - The updated value * @param connection - The connection from which the request originated from * @param context - Deprecated parameter. There for backwards compatibility. * @returns Promise resolve to void in normal operation. When characteristic supports write response, the * HAP request requests write response and the set handler returns a write response value, the respective * write response value is resolved. * @private */ Characteristic.prototype.handleSetRequest = function (value, connection, context) { return (0, tslib_1.__awaiter)(this, void 0, void 0, function () { var oldValue, writeResponse, error_2, hapStatusError; var _this = this; return (0, tslib_1.__generator)(this, function (_a) { switch (_a.label) { case 0: this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols this.status = null; if (connection !== undefined) { // if connection is undefined, the set "request" comes from the setValue method. // for setValue a value of "null" is allowed and checked via validateUserInput. try { value = this.validateClientSuppliedValue(value); } catch (e) { debug("[".concat(this.displayName, "]"), e.message); return [2 /*return*/, Promise.reject(-70410 /* INVALID_VALUE_IN_REQUEST */)]; } } oldValue = this.value; if (!this.setHandler) return [3 /*break*/, 4]; if (this.listeners("set" /* SET */).length > 0) { this.characteristicWarning("Ignoring on('set') handler as onSet handler was defined instead"); } _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.setHandler(value, context, connection)]; case 2: writeResponse = _a.sent(); this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols this.status = null; if (writeResponse != null && this.props.perms.includes("wr" /* WRITE_RESPONSE */)) { this.value = this.validateUserInput(writeResponse); return [2 /*return*/, this.value]; } else { if (writeResponse != null) { this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response", "debug-message" /* DEBUG_MESSAGE */); } this.value = value; this.emit("change" /* CHANGE */, { originator: connection, oldValue: oldValue, newValue: value, reason: "write" /* WRITE */, context: context }); return [2 /*return*/]; } return [3 /*break*/, 4]; case 3: error_2 = _a.sent(); if (typeof error_2 === "number") { hapStatusError = new hapStatusError_1.HapStatusError(error_2); this.statusCode = hapStatusError.hapStatus; // noinspection JSDeprecatedSymbols this.status = hapStatusError; } else if (error_2 instanceof hapStatusError_1.HapStatusError) { this.statusCode = error_2.hapStatus; // noinspection JSDeprecatedSymbols this.status = error_2; } else { this.characteristicWarning("Unhandled error thrown inside write handler for characteristic: ".concat(error_2 === null || error_2 === void 0 ? void 0 : error_2.message), "error-message" /* ERROR_MESSAGE */, error_2 === null || error_2 === void 0 ? void 0 : error_2.stack); this.statusCode = -70402 /* SERVICE_COMMUNICATION_FAILURE */; // noinspection JSDeprecatedSymbols this.status = error_2; } throw this.statusCode; case 4: if (this.listeners("set" /* SET */).length === 0) { this.value = value; this.emit("change" /* CHANGE */, { originator: connection, oldValue: oldValue, newValue: value, reason: "write" /* WRITE */, context: context }); return [2 /*return*/, Promise.resolve()]; } else { return [2 /*return*/, new Promise(function (resolve, reject) { try { _this.emit("set" /* SET */, value, (0, once_1.once)(function (status, writeResponse) { if (status) { if (typeof status === "number") { var hapStatusError = new hapStatusError_1.HapStatusError(status); _this.statusCode = hapStatusError.hapStatus; // noinspection JSDeprecatedSymbols _this.status = hapStatusError; } else if (status instanceof hapStatusError_1.HapStatusError) { _this.statusCode = status.hapStatus; // noinspection JSDeprecatedSymbols _this.status = status; } else { debug("[%s] Received error from set handler %s", _this.displayName, status.stack); _this.statusCode = extractHAPStatusFromError(status); // noinspection JSDeprecatedSymbols _this.status = status; } reject(_this.statusCode); return; } _this.statusCode = 0 /* SUCCESS */; // noinspection JSDeprecatedSymbols _this.status = null; if (writeResponse != null && _this.props.perms.includes("wr" /* WRITE_RESPONSE */)) { // support write response simply by letting the implementor pass the response as second argument to the callback _this.value = _this.validateUserInput(writeResponse); resolve(_this.value); } else { if (writeResponse != null) { _this.characteristicWarning("SET handler returned write response value, though the characteristic doesn't support write response", "debug-message" /* DEBUG_MESSAGE */); } _this.value = value; resolve(); _this.emit("change" /* CHANGE */, { originator: connection, oldValue: oldValue, newValue: value, reason: "write" /* WRITE */, context: context }); } }), context, connection); } catch (error) { _this.characteristicWarning("Unhandled error thrown inside write handler for characteristic: ".concat(error === null || error === void 0 ? void 0 : error.message), "error-message" /* ERROR_MESSAGE */, error === null || error === void 0 ? void 0 : error.stack); _this.statusCode = -70402 /* SERVICE_COMMUNICATION_FAILURE */; // noinspection JSDeprecatedSymbols _this.status = error; reject(-70402 /* SERVICE_COMMUNICATION_FAILURE */); } })]; } return [2 /*return*/]; } }); }); }; /** * Called once a HomeKit controller subscribes to events of this characteristics. * @private */ Characteristic.prototype.subscribe = function () { if (this.subscriptions === 0) { this.emit("subscribe" /* SUBSCRIBE */); } this.subscriptions++; }; /** * Called once a HomeKit controller unsubscribe to events of this characteristics or a HomeKit controller * which was subscribed to this characteristic disconnects. * @private */ Characteristic.prototype.unsubscribe = function () { var wasOne = this.subscriptions === 1; this.subscriptions--; this.subscriptions = Math.max(this.subscriptions, 0); if (wasOne) { this.emit("unsubscribe" /* UNSUBSCRIBE */); } }; Characteristic.prototype.getDefaultValue = function () { var _a; // noinspection JSDeprecatedSymbols switch (this.props.format) { case "bool" /* BOOL */: return false; case "string" /* STRING */: switch (this.UUID) { case Characteristic.Manufacturer.UUID: return "Default-Manufacturer"; case Characteristic.Model.UUID: