Skip to content

Commit

Permalink
Merge branch 'master' into support-resolved
Browse files Browse the repository at this point in the history
  • Loading branch information
elyscape authored Sep 15, 2022
2 parents 09601ab + fdd4bc8 commit a2caa25
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 29 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hap-nodejs",
"version": "0.10.2",
"version": "0.10.3",
"description": "HAP-NodeJS is a Node.js implementation of HomeKit Accessory Server.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -51,7 +51,7 @@
"@types"
],
"dependencies": {
"@homebridge/ciao": "^1.1.4",
"@homebridge/ciao": "^1.1.5",
"@homebridge/dbus-native": "^0.4.1",
"bonjour-hap": "~3.6.3",
"debug": "^4.3.4",
Expand Down
6 changes: 4 additions & 2 deletions src/lib/Accessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,8 +1229,10 @@ export class Accessory extends EventEmitter {
(info.advertiser === MDNSAdvertiser.AVAHI && !await AvahiAdvertiser.isAvailable()) ||
(info.advertiser === MDNSAdvertiser.RESOLVED && !await ResolvedAdvertiser.isAvailable())
) {
console.error("[${this.displayName}] Selected \"" + info.advertiser + "\" advertiser though it isn't available on the platform. " +
"Reverting to \"" + MDNSAdvertiser.BONJOUR + "\"");
console.error(
`[${this.displayName}] The selected advertiser, "${info.advertiser}", isn't available on this platform. ` +
`Reverting to "${MDNSAdvertiser.BONJOUR}"`,
);
selectedAdvertiser = MDNSAdvertiser.BONJOUR;
}

Expand Down
42 changes: 42 additions & 0 deletions src/lib/Characteristic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ describe("Characteristic", () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
minStep: 0.001,
minValue: 0,
perms: [Perms.NOTIFY],
});
// @ts-expect-error: private access
Expand Down Expand Up @@ -1094,6 +1095,47 @@ describe("Characteristic", () => {
expect(mock).toBeCalledTimes(0);
});

it("should validate Formats.FLOAT with precision with minimum steps", () => {
const characteristic = createCharacteristic(Formats.FLOAT);
let minStep;

minStep = 100 / 6;
characteristic.setProps({
minValue: 0,
maxValue: 100,
minStep: minStep,
});
for(let i = 1; i <= 7; i++) {
const desiredValue = Math.min(Math.max(i * minStep, 0), 100);
characteristic.setValue(i * minStep);
expect(characteristic.value).toEqual(desiredValue);
}

minStep = 1;
characteristic.setProps({
minValue: 0.5,
maxValue: 2.5,
minStep: minStep,
});
for(let i = 1; i <= 4; i++) {
const desiredValue = Math.min(Math.max(i * minStep + 0.5, 0.5), 2.5);
characteristic.setValue(i * minStep + 0.5);
expect(characteristic.value).toEqual(desiredValue);
}

minStep = 100 / 3;
characteristic.setProps({
minValue: 0,
maxValue: 100,
minStep: minStep,
});
for(let i = 1; i <= 4; i++) {
const desiredValue = Math.min(Math.max(i * minStep, 0), 100);
characteristic.setValue(i * minStep);
expect(characteristic.value).toEqual(desiredValue);
}
});

it("should allow negative floats in range for Formats.FLOAT", () => {
const characteristic = createCharacteristicWithProps({
format: Formats.FLOAT,
Expand Down
14 changes: 5 additions & 9 deletions src/lib/Characteristic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,11 @@ export class Characteristic extends EventEmitter {
stepValue = maxWithUndefined(this.props.minStep, 1);
}

if (stepValue != null && stepValue > 0) {
const minValue = numericMin != null ? numericMin : 0;
value = stepValue * Math.round((value - minValue) / stepValue) + minValue;
}

if (numericMin != null && value < numericMin) {
this.characteristicWarning(`characteristic was supplied illegal value: number ${value} exceeded minimum of ${numericMin}`);
value = numericMin;
Expand All @@ -2067,15 +2072,6 @@ export class Characteristic extends EventEmitter {
}
}

if (stepValue != null) {
if (stepValue === 1) {
value = Math.round(value);
} else if (stepValue > 1) {
value = Math.round(value);
value = value - (value % stepValue);
} // for stepValue < 1 rounding is done only when formatting the response. We can't store the "perfect" .step anyways
}

return value;
}
case Formats.STRING: {
Expand Down
16 changes: 9 additions & 7 deletions src/lib/camera/RTPStreamManagement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ export class RTPStreamManagement {
*/
connectionID?: SessionIdentifier;
private activeConnection?: HAPConnection;
private activeConnectionClosedListener?: () => void;
private readonly activeConnectionClosedListener: (callback?: CharacteristicSetCallback) => void;
sessionIdentifier?: StreamSessionIdentifier = undefined;
streamStatus: StreamingStatus = StreamingStatus.AVAILABLE; // use _updateStreamStatus to update this property
private ipVersion?: "ipv4" | "ipv6"; // ip version for the current session
Expand Down Expand Up @@ -558,6 +558,8 @@ export class RTPStreamManagement {
this.supportedVideoStreamConfiguration = RTPStreamManagement._supportedVideoStreamConfiguration(options.video);
this.supportedAudioStreamConfiguration = this._supportedAudioStreamConfiguration(options.audio);

this.activeConnectionClosedListener = this._handleStopStream.bind(this);

this.service = service || this.constructService(id);
this.setupServiceHandlers();

Expand Down Expand Up @@ -667,14 +669,13 @@ export class RTPStreamManagement {
this.resetSelectedStreamConfiguration();
this.resetSetupEndpointsResponse();

if (this.activeConnectionClosedListener && this.activeConnection) {
if (this.activeConnection) {
this.activeConnection.removeListener(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener);
this.activeConnectionClosedListener = undefined;
this.activeConnection = undefined;
}

this._updateStreamStatus(StreamingStatus.AVAILABLE);
this.sessionIdentifier = undefined;
this.activeConnection = undefined;
// noinspection JSDeprecatedSymbols
this.connectionID = undefined;
this.ipVersion = undefined;
Expand All @@ -691,13 +692,11 @@ export class RTPStreamManagement {

private streamingIsDisabled(callback?: CharacteristicSetCallback): boolean {
if (!this.service.getCharacteristic(Characteristic.Active).value) {
console.log("STREAMING DISABLED ACTIVE");
callback && callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE));
return true;
}

if (this.disabledThroughOperatingMode?.()) {
console.log("STREAMING DISABLED OPERATION MODE!");
callback && callback(new HapStatusError(HAPStatus.NOT_ALLOWED_IN_CURRENT_STATE));
return true;
}
Expand Down Expand Up @@ -982,8 +981,11 @@ export class RTPStreamManagement {
return;
}

assert(this.activeConnection == null,
"Found non-nil `activeConnection` when trying to setup streaming endpoints, even though streamStatus is reported to be AVAILABLE!");

this.activeConnection = connection;
this.activeConnection.on(HAPConnectionEvent.CLOSED, (this.activeConnectionClosedListener = this._handleStopStream.bind(this)));
this.activeConnection.on(HAPConnectionEvent.CLOSED, this.activeConnectionClosedListener);

// noinspection JSDeprecatedSymbols
this.connectionID = connection.sessionID;
Expand Down
46 changes: 46 additions & 0 deletions src/lib/util/eventedhttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as uuid from "./uuid";

const debug = createDebug("HAP-NodeJS:EventedHTTPServer");
const debugCon = createDebug("HAP-NodeJS:EventedHTTPServer:Connection");
const debugEvents = createDebug("HAP-NodeJS:EventEmitter");

export type HAPUsername = string;
export type EventName = string; // "<aid>.<iid>"
Expand Down Expand Up @@ -361,6 +362,47 @@ export class HAPConnection extends EventEmitter {
this.internalHttpServer.listen(0, this.internalHttpServerAddress = getOSLoopbackAddressIfAvailable());
}

private debugListenerRegistration(event: string | symbol, registration = true, beforeCount = -1): void {
const stackTrace = new Error().stack!.split("\n")[3];
const eventCount = this.listeners(event).length;

const tabs1 = event === HAPConnectionEvent.AUTHENTICATED ? "\t" : "\t\t";
const tabs2 = !registration ? "\t" : "\t\t";

// eslint-disable-next-line max-len
debugEvents(`[${this.remoteAddress}] ${registration ? "Registered" : "Unregistered"} event '${String(event).toUpperCase()}' ${tabs1}(total: ${eventCount}${!registration ? " Before: " + beforeCount : ""}) ${tabs2}${stackTrace}`);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event: string | symbol, listener: (...args: any[]) => void): this {
const result = super.on(event, listener);
this.debugListenerRegistration(event);
return result;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
addListener(event: string | symbol, listener: (...args: any[]) => void): this {
const result = super.addListener(event, listener);
this.debugListenerRegistration(event);
return result;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
removeListener(event: string | symbol, listener: (...args: any[]) => void): this {
const beforeCount = this.listeners(event).length;
const result = super.removeListener(event, listener);
this.debugListenerRegistration(event, false, beforeCount);
return result;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
off(event: string | symbol, listener: (...args: any[]) => void): this {
const result = super.off(event, listener);
const beforeCount = this.listeners(event).length;
this.debugListenerRegistration(event, false, beforeCount);
return result;
}

/**
* This method is called once the connection has gone through pair-verify.
* As any HomeKit controller will initiate a pair-verify after the pair-setup procedure, this method gets
Expand Down Expand Up @@ -501,6 +543,10 @@ export class HAPConnection extends EventEmitter {
private writeEventNotification(notification: EventNotification): void {
debugCon("[%s] Sending HAP event notifications %o", this.remoteAddress, notification.characteristics);

// Apple backend processes events in reverse order, so we need to reverse the array
// so that events are processed in chronological order.
notification.characteristics.reverse();

const dataBuffer = Buffer.from(JSON.stringify(notification), "utf8");
const header = Buffer.from(
"EVENT/1.0 200 OK\r\n" +
Expand Down

0 comments on commit a2caa25

Please sign in to comment.