Skip to content

Commit

Permalink
homekit: update hap
Browse files Browse the repository at this point in the history
  • Loading branch information
leedsalex committed Mar 8, 2023
1 parent f8c16ed commit 4520d1d
Show file tree
Hide file tree
Showing 8 changed files with 1,720 additions and 278 deletions.
1,825 changes: 1,646 additions & 179 deletions plugins/homekit/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions plugins/homekit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@scrypted/homekit",
"version": "1.2.13",
"version": "1.2.14",
"description": "HomeKit Plugin for Scrypted",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
Expand Down Expand Up @@ -36,7 +36,7 @@
"dependencies": {
"@koush/werift-src": "file:../../external/werift",
"check-disk-space": "^3.3.0",
"hap-nodejs": "file:../../external/HAP-NodeJS",
"hap-nodejs": "^0.11.0",
"lodash": "^4.17.21",
"mkdirp": "^1.0.4"
},
Expand Down
28 changes: 0 additions & 28 deletions plugins/homekit/src/hap-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,6 @@ import { Categories, EventedHTTPServer, HAPStorage } from './hap';
import { randomPinCode } from './pincode';
import './types';

class HAPLocalStorage {
initSync() {

}
getItem(key: string): any {
const data = localStorage.getItem(key);
if (!data)
return;
return JSON.parse(data);
}
setItemSync(key: string, value: any) {
localStorage.setItem(key, JSON.stringify(value));
}
removeItemSync(key: string) {
localStorage.removeItem(key);
}

persistSync() {

}
}

// HAP storage seems to be global?
export function initializeHapStorage() {
HAPStorage.setStorage(new HAPLocalStorage());
}


export function createHAPUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
Expand Down
28 changes: 14 additions & 14 deletions plugins/homekit/src/hap.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export * from 'hap-nodejs/src/lib/definitions'; // must be loaded before Characteristic and Service class
export * from 'hap-nodejs/src/lib/Accessory';
export * as uuid from 'hap-nodejs/src/lib/util/uuid';
export * from 'hap-nodejs/src/lib/Characteristic';
export * from 'hap-nodejs/src/lib/camera';
export * from 'hap-nodejs/src/lib/camera/RecordingManagement';
export * from 'hap-nodejs/src/lib/model/ControllerStorage';
export * from 'hap-nodejs/src/lib/util/eventedhttp';
export * from 'hap-nodejs/src/lib/controller/CameraController';
export * from 'hap-nodejs/src/lib/datastream/DataStreamServer';
export * from 'hap-nodejs/src/lib/Service';
export * from 'hap-nodejs/src/types';
export * from 'hap-nodejs/src/lib/model/HAPStorage';
export * from 'hap-nodejs/src/lib/Bridge';
export * from 'hap-nodejs/dist/lib/definitions'; // must be loaded before Characteristic and Service class
export * from 'hap-nodejs/dist/lib/Accessory';
export * as uuid from 'hap-nodejs/dist/lib/util/uuid';
export * from 'hap-nodejs/dist/lib/Characteristic';
export * from 'hap-nodejs/dist/lib/camera';
export * from 'hap-nodejs/dist/lib/camera/RecordingManagement';
export * from 'hap-nodejs/dist/lib/model/ControllerStorage';
export * from 'hap-nodejs/dist/lib/util/eventedhttp';
export * from 'hap-nodejs/dist/lib/controller/CameraController';
export * from 'hap-nodejs/dist/lib/datastream/DataStreamServer';
export * from 'hap-nodejs/dist/lib/Service';
export * from 'hap-nodejs/dist/types';
export * from 'hap-nodejs/dist/lib/model/HAPStorage';
export * from 'hap-nodejs/dist/lib/Bridge';
3 changes: 1 addition & 2 deletions plugins/homekit/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { maybeAddBatteryService } from './battery';
import { CameraMixin, canCameraMixin } from './camera-mixin';
import { SnapshotThrottle, supportedTypes } from './common';
import { Accessory, Bridge, Categories, Characteristic, ControllerStorage, MDNSAdvertiser, PublishInfo, Service } from './hap';
import { createHAPUsernameStorageSettingsDict, getHAPUUID, getRandomPort as createRandomPort, initializeHapStorage, logConnections, typeToCategory } from './hap-utils';
import { createHAPUsernameStorageSettingsDict, getHAPUUID, getRandomPort as createRandomPort, logConnections, typeToCategory } from './hap-utils';
import { HomekitMixin, HOMEKIT_MIXIN } from './homekit-mixin';
import { addAccessoryDeviceInfo } from './info';
import { randomPinCode } from './pincode';
Expand All @@ -17,7 +17,6 @@ import { VideoClipsMixinProvider } from './video-clips-provider';

const { systemManager, deviceManager } = sdk;

initializeHapStorage();
const includeToken = 4;

export class HomeKitPlugin extends ScryptedDeviceBase implements MixinProvider, Settings, DeviceProvider {
Expand Down
40 changes: 18 additions & 22 deletions plugins/homekit/src/types/camera.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sdk, { AudioSensor, Camera, Intercom, MotionSensor, ObjectsDetected, OnOff, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
import { defaultObjectDetectionContactSensorTimeout } from '../camera-mixin';
import { addSupportedType, bindCharacteristic, DummyDevice, } from '../common';
import { AudioRecordingCodec, AudioRecordingCodecType, AudioRecordingSamplerate, AudioStreamingCodec, AudioStreamingCodecType, AudioStreamingSamplerate, CameraController, CameraRecordingDelegate, CameraRecordingOptions, CameraStreamingOptions, Characteristic, CharacteristicEventTypes, DataStreamConnection, H264Level, H264Profile, OccupancySensor, RecordingManagement, Service, SRTPCryptoSuites, VideoCodecType, WithUUID } from '../hap';
import { AudioRecordingCodec, AudioRecordingCodecType, AudioRecordingSamplerate, AudioStreamingCodec, AudioStreamingCodecType, AudioStreamingSamplerate, CameraController, CameraRecordingDelegate, CameraRecordingOptions, CameraStreamingOptions, Characteristic, CharacteristicEventTypes, DataStreamConnection, H264Level, H264Profile, MediaContainerType, OccupancySensor, RecordingManagement, Service, SRTPCryptoSuites, VideoCodecType, WithUUID } from '../hap';
import { handleFragmentsRequests, iframeIntervalSeconds } from './camera/camera-recording';
import { createCameraStreamingDelegate } from './camera/camera-streaming';
import { FORCE_OPUS } from './camera/camera-utils';
Expand Down Expand Up @@ -62,7 +62,6 @@ addSupportedType({
const streamingOptions: CameraStreamingOptions = {
video: {
codec: {
type: VideoCodecType.H264,
levels: [H264Level.LEVEL3_1, H264Level.LEVEL3_2, H264Level.LEVEL4_0],
profiles: [H264Profile.MAIN],
},
Expand Down Expand Up @@ -146,23 +145,17 @@ addSupportedType({
// ensureHasWidthResolution(recordingResolutions, 1280, 720);
// ensureHasWidthResolution(recordingResolutions, 1920, 1080);

const h265Support = storage.getItem('h265Support') === 'true';
const codecType = h265Support ? VideoCodecType.H265 : VideoCodecType.H264

recordingOptions = {
motionService: true,
prebufferLength: numberPrebufferSegments * iframeIntervalSeconds * 1000,
eventTriggerOptions: 0x01,
mediaContainerConfigurations: [
mediaContainerConfiguration: [
{
type: 0,
type: MediaContainerType.FRAGMENTED_MP4,
fragmentLength: iframeIntervalSeconds * 1000,
}
],

video: {
codec: {
type: codecType,
type: VideoCodecType.H264,
parameters: {
levels: [H264Level.LEVEL3_1, H264Level.LEVEL3_2, H264Level.LEVEL4_0],
profiles: [H264Profile.BASELINE, H264Profile.MAIN, H264Profile.HIGH],
},
Expand All @@ -186,7 +179,10 @@ addSupportedType({
recording: {
options: recordingOptions,
delegate: recordingDelegate,
}
},
sensors: {
motion: true,
},
});

accessory.configureController(controller);
Expand Down Expand Up @@ -220,20 +216,20 @@ addSupportedType({
});
}

persistBooleanCharacteristic(recordingManagement.getService(), Characteristic.Active);
persistBooleanCharacteristic(recordingManagement.getService(), Characteristic.RecordingAudioActive);
persistBooleanCharacteristic(controller.cameraOperatingModeService, Characteristic.EventSnapshotsActive);
persistBooleanCharacteristic(controller.cameraOperatingModeService, Characteristic.HomeKitCameraActive);
persistBooleanCharacteristic(controller.cameraOperatingModeService, Characteristic.PeriodicSnapshotsActive);
persistBooleanCharacteristic(recordingManagement.recordingManagementService, Characteristic.Active);
persistBooleanCharacteristic(recordingManagement.recordingManagementService, Characteristic.RecordingAudioActive);
persistBooleanCharacteristic(recordingManagement.operatingModeService, Characteristic.EventSnapshotsActive);
persistBooleanCharacteristic(recordingManagement.operatingModeService, Characteristic.HomeKitCameraActive);
persistBooleanCharacteristic(recordingManagement.operatingModeService, Characteristic.PeriodicSnapshotsActive);

if (!device.interfaces.includes(ScryptedInterface.OnOff)) {
persistBooleanCharacteristic(controller.cameraOperatingModeService, Characteristic.CameraOperatingModeIndicator);
persistBooleanCharacteristic(recordingManagement.operatingModeService, Characteristic.CameraOperatingModeIndicator);
}
else {
const indicator = controller.cameraOperatingModeService.getCharacteristic(Characteristic.CameraOperatingModeIndicator);
const indicator = recordingManagement.operatingModeService.getCharacteristic(Characteristic.CameraOperatingModeIndicator);
const linkStatusIndicator = storage.getItem('statusIndicator') === 'true';
const property = `characteristic-v2-${Characteristic.CameraOperatingModeIndicator.UUID}`
bindCharacteristic(device, ScryptedInterface.OnOff, controller.cameraOperatingModeService, Characteristic.CameraOperatingModeIndicator, () => {
bindCharacteristic(device, ScryptedInterface.OnOff, recordingManagement.operatingModeService, Characteristic.CameraOperatingModeIndicator, () => {
if (!linkStatusIndicator)
return storage.getItem(property) === 'true' ? 1 : 0;

Expand All @@ -251,7 +247,7 @@ addSupportedType({
});
}

recordingManagement.getService().getCharacteristic(Characteristic.SelectedCameraRecordingConfiguration)
recordingManagement.recordingManagementService.getCharacteristic(Characteristic.SelectedCameraRecordingConfiguration)
.on(CharacteristicEventTypes.GET, callback => {
callback(null, storage.getItem(storageKeySelectedRecordingConfiguration) || '');
})
Expand Down
16 changes: 12 additions & 4 deletions plugins/homekit/src/types/camera/camera-recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import mkdirp from 'mkdirp';
import net from 'net';
import { Duplex, Readable, Writable } from 'stream';
import { } from '../../common';
import { AudioRecordingCodecType, AudioRecordingSamplerateValues, CameraRecordingConfiguration, DataStreamConnection } from '../../hap';
import { AudioRecordingCodecType, CameraRecordingConfiguration, DataStreamConnection } from '../../hap';
import type { HomeKitPlugin } from "../../main";
import { getCameraRecordingFiles, HksvVideoClip, VIDEO_CLIPS_NATIVE_ID } from './camera-recording-files';
import { checkCompatibleCodec, FORCE_OPUS, transcodingDebugModeWarning } from './camera-utils';
Expand All @@ -33,6 +33,14 @@ const allowedNaluTypes = [
NAL_TYPE_DELIMITER,
];

const AudioRecordingSamplerateValues = {
0: 8,
1: 16,
2: 24,
3: 32,
4: 44.1,
5: 48,
};

async function checkMp4StartsWithKeyFrame(console: Console, mp4: Buffer) {
const cp = child_process.spawn(await mediaManager.getFFmpegPath(), [
Expand Down Expand Up @@ -102,7 +110,7 @@ export async function* handleFragmentsRequests(connection: DataStreamConnection,
const saveRecordings = debugMode.recording;

// request more than needed, and determine what to do with the fragments after receiving them.
const prebuffer = configuration.mediaContainerConfiguration.prebufferLength * 2.5;
const prebuffer = configuration.prebufferLength * 2.5;

const media = await device.getVideoStream({
destination: 'remote-recorder',
Expand Down Expand Up @@ -153,7 +161,7 @@ export async function* handleFragmentsRequests(connection: DataStreamConnection,
width: configuration.videoCodec.resolution[0],
height: configuration.videoCodec.resolution[1],
fps: configuration.videoCodec.resolution[2],
max_bit_rate: configuration.videoCodec.bitrate,
max_bit_rate: configuration.videoCodec.parameters.bitRate,
}
}

Expand Down Expand Up @@ -210,7 +218,7 @@ export async function* handleFragmentsRequests(connection: DataStreamConnection,
const videoRecordingFilter = `scale=w='min(${configuration.videoCodec.resolution[0]},iw)':h=-2`;
addVideoFilterArguments(videoArgs, videoRecordingFilter);
videoArgs.push(
'-b:v', `${configuration.videoCodec.bitrate}k`,
'-b:v', `${configuration.videoCodec.parameters.bitRate}k`,
"-bufsize", (2 * request.video.max_bit_rate).toString() + "k",
"-maxrate", request.video.max_bit_rate.toString() + "k",
// used to use this but switched to group of picture (gop) instead.
Expand Down
54 changes: 27 additions & 27 deletions plugins/homekit/src/types/camera/camera-streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,33 +158,33 @@ export function createCameraStreamingDelegate(device: ScryptedDevice & VideoCame
// may not be reachable.
// Return the incoming address, assuming the sanity checks pass. Otherwise, fall through
// to the HAP-NodeJS implementation.
let check: string;
if (request.addressVersion === 'ipv4') {
const localAddress = request.connection.localAddress;
if (v4Regex.exec(localAddress)) {
check = localAddress;
}
else if (v4v6Regex.exec(localAddress)) {
// if this is a v4 over v6 address, parse it out.
check = localAddress.substring('::ffff:'.length);
}
}
else if (request.addressVersion === 'ipv6' && !v4Regex.exec(request.connection.localAddress)) {
check = request.connection.localAddress;
}

// ignore the IP if it is APIPA (Automatic Private IP Addressing)
if (check?.startsWith('169.')) {
check = undefined;
}

// sanity check this address.
if (check) {
const infos = os.networkInterfaces()[request.connection.networkInterface];
if (infos && infos.find(info => info.address === check)) {
response.addressOverride = check;
}
}
// let check: string;
// if (request.addressVersion === 'ipv4') {
// const localAddress = request.connection.localAddress;
// if (v4Regex.exec(localAddress)) {
// check = localAddress;
// }
// else if (v4v6Regex.exec(localAddress)) {
// // if this is a v4 over v6 address, parse it out.
// check = localAddress.substring('::ffff:'.length);
// }
// }
// else if (request.addressVersion === 'ipv6' && !v4Regex.exec(request.connection.localAddress)) {
// check = request.connection.localAddress;
// }

// // ignore the IP if it is APIPA (Automatic Private IP Addressing)
// if (check?.startsWith('169.')) {
// check = undefined;
// }

// // sanity check this address.
// if (check) {
// const infos = os.networkInterfaces()[request.connection.networkInterface];
// if (infos && infos.find(info => info.address === check)) {
// response.addressOverride = check;
// }
// }
}

console.log('source address', response.addressOverride, videoPort, audioPort);
Expand Down

0 comments on commit 4520d1d

Please sign in to comment.