homekit: update hap

This commit is contained in:
Alex Leeds
2023-03-08 13:07:59 -05:00
parent f8c16edaae
commit 4520d1d29f
8 changed files with 1602 additions and 160 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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",
@@ -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"
},

View File

@@ -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);

View File

@@ -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';

View File

@@ -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';
@@ -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 {

View File

@@ -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';
@@ -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],
},
@@ -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],
},
@@ -186,7 +179,10 @@ addSupportedType({
recording: {
options: recordingOptions,
delegate: recordingDelegate,
}
},
sensors: {
motion: true,
},
});
accessory.configureController(controller);
@@ -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;
@@ -251,7 +247,7 @@ addSupportedType({
});
}
recordingManagement.getService().getCharacteristic(Characteristic.SelectedCameraRecordingConfiguration)
recordingManagement.recordingManagementService.getCharacteristic(Characteristic.SelectedCameraRecordingConfiguration)
.on(CharacteristicEventTypes.GET, callback => {
callback(null, storage.getItem(storageKeySelectedRecordingConfiguration) || '');
})

View File

@@ -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';
@@ -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(), [
@@ -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',
@@ -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,
}
}
@@ -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.

View File

@@ -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;
}
// 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;
}
// // 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;
}
}
// // 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);