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
JavaScript
"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: