From c9443f8e699bc0d2a84c06632aa3072bb61892e9 Mon Sep 17 00:00:00 2001 From: dgreif Date: Sun, 22 Dec 2024 07:35:42 -0500 Subject: [PATCH] wip: remove bridged cameras --- .changeset/afraid-waves-rhyme.md | 5 + packages/homebridge-ring/README.md | 45 ++++---- packages/homebridge-ring/camera-source.ts | 117 ++++++-------------- packages/homebridge-ring/camera.ts | 5 +- packages/homebridge-ring/config.schema.json | 6 - packages/homebridge-ring/config.ts | 1 - packages/homebridge-ring/ring-platform.ts | 28 +---- 7 files changed, 64 insertions(+), 143 deletions(-) create mode 100644 .changeset/afraid-waves-rhyme.md diff --git a/.changeset/afraid-waves-rhyme.md b/.changeset/afraid-waves-rhyme.md new file mode 100644 index 00000000..f7f95c31 --- /dev/null +++ b/.changeset/afraid-waves-rhyme.md @@ -0,0 +1,5 @@ +--- +'homebridge-ring': major +--- + +Removed bridged cameras. If you already had `unbridgeCameras: true` in your config, this change will not affect you. For those who were still using bridged cameras, you will need to manually add each camera to HomeKit after upgrading. This change allows us to stop requiring special builds of ffmpeg and should make video streaming more reliable. Unbridge cameras are also avoid blocking the whole bridge while waiting for requests (e.g. Snapshot), which leads to a better overall experience. diff --git a/packages/homebridge-ring/README.md b/packages/homebridge-ring/README.md index 29f3481f..17fe2747 100644 --- a/packages/homebridge-ring/README.md +++ b/packages/homebridge-ring/README.md @@ -80,29 +80,28 @@ Only include an optional parameter if you actually need it. Default behavior wit } ``` -| Option | Default | Explanation | -| ---------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `unbridgeCameras` | `false` | if `true`, all Ring Cameras we be treated as external accessories, which generally leads to better performance. This means they each need to be individually added to HomeKit. This option will be enabled by default in the next major release. WARNING: If your cameras are already bridged, they will be deleted from HomeKit when you enable this option, and you will need to reconfigure any associated automations or HomeKit settings | -| `alarmOnEntryDelay` | `false` | if `true`, HomeKit will register a delayed entry event as a triggered alarm. There are some households where this is a nice feature as a heads up if you have other people who enter your house and you want an alert so that you can disable the alarm for them before it actually goes off. This works well if you automatically arm/disarm on leave/arrive (see setup instructions below) | -| `beamDurationSeconds` | `60` for light groups, previous from Ring app for individual lights | Ring smart lighting has a default behavior of only staying on for 60 seconds when you turn on a light via the Ring app. To force a duration when the light is turned on from HomeKit, set this option to a specific number of seconds. If this option is not set, the lights will use the duration from the previous time the light was turned on in the Ring app. For light groups, this will default to 60 seconds. The maximum value is `32767`, which is ~9.1 hours. | -| `hideDeviceIds` | `[]` | Allows you to hide specific devices by an array of ids. The id for each device is logged when homebridge starts. | -| `hideLightGroups` | `false` | Ring smart lighting allows you to create lighting groups within the Ring app. These groups are convenient for detecting motion in an area of your yard and turning on/off all lights in the group. However, you may wish to group the lights differently in HomeKit and ignore the groups you have configured in Ring. If this option is `true`, your Ring groups (and their associated motion sensor) will be ignored and will not show up in HomeKit. | -| `hideDoorbellSwitch` | `false` | If you have a Ring video doorbell, you will see a Programmable Switch associated with it. This switch can be used to perform actions on when the doorbell is pressed using "Single Press" actions. If you do not care to perform actions when the doorbell is pressed, you can hide the Programmable Switch by setting this option to `true`. You will still be able to receive _notifications_ from the doorbell even if the Programmable Switch is hidden (notifications can be configured in the settings for the doorbell camera in the Home app) | -| `hideCameraLight` | `false` | If `true`, hides the light for Ring cameras in HomeKit. | -| `hideCameraMotionSensor` | `false` | If `true`, hides the motion sensor for Ring cameras in HomeKit. | -| `hideCameraSirenSwitch` | `false` | If `true`, hides the siren switch for Ring cameras in HomeKit. | -| `hideInHomeDoorbellSwitch` | `false` | If `true`, hides the switch for in-home doorbells in HomeKit. | -| `hideAlarmSirenSwitch` | `false` | If you have a Ring Alarm, you will see both the alarm and a "Siren" switch in HomeKit. The siren switch can sometimes get triggered by Siri commands by accident, which is loud and annoying. Set this option to `true` to hide the siren switch. | -| `showPanicButtons` | `false` | Creates a new `Panic Buttons` device in HomeKit with `Burglar Alarm` and `Fire Alarm` switches. **Use these at your own risk. I do not guarantee functionality in case of emergency, nor do I take responsibility for any false alarms**. These function just like the SOS sliders in the Ring app. | -| `nightModeBypassFor` | no Night Mode | Allows you to use Night mode to "Bypass and Arm" a Ring Alarm. Can be set to `all` for Away or `some` for Home. If set and Night mode is activated from HomeKit, any open contact sensors will automatically be bypassed. | -| `avoidSnapshotBatteryDrain` | false | Causes snapshots for battery cameras to be fetched at a minimum 10 minute interval to avoid draining the battery. | -| `cameraStatusPollingSeconds` | `20` | How frequently to poll for updates to your cameras and chimes (in seconds). Information like light/siren/volume/snooze status do not update in real time and need to be requested periodically. | -| `locationModePollingSeconds` | `20` | How frequently to poll for location mode updates (in seconds). This is only useful if you are using location modes to control camera settings and want to keep an up-to-date reference of the current mode for each location. Polling is automatically disabled for locations equipped with a Ring Alarm. Will hide Location Mode switch if set to `0` | -| `locationIds` | All Locations | Use this option if you only want a subset of your locations to appear in HomeKit. If this option is not included, all of your locations will be added to HomeKit (which is what most users will want to do). | -| `onlyDeviceTypes` | All Device Types | A subset of device types to be shown. If set, all other device types will be hidden in HomeKit. | -| `ffmpegPath` | Uses `ffmpeg-for-homebridge` | A custom path to the `ffmpeg` executable. By default, the static binaries built in [ffmpeg-for-homebridge](https://github.com/oznu/ffmpeg-for-homebridge) will be used. If you prefer to use your own version of ffmpeg, you can pass a complete path, or simply `"ffmpeg"` to use ffmpeg from your `PATH`. | -| `debug` | false | Turns on additional logging. In particular, ffmpeg logging. | -| `disableLogs` | false | Turns off all logging | +| Option | Default | Explanation | +| ---------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | +| `alarmOnEntryDelay` | `false` | if `true`, HomeKit will register a delayed entry event as a triggered alarm. There are some households where this is a nice feature as a heads up if you have other people who enter your house and you want an alert so that you can disable the alarm for them before it actually goes off. This works well if you automatically arm/disarm on leave/arrive (see setup instructions below) | +| `beamDurationSeconds` | `60` for light groups, previous from Ring app for individual lights | Ring smart lighting has a default behavior of only staying on for 60 seconds when you turn on a light via the Ring app. To force a duration when the light is turned on from HomeKit, set this option to a specific number of seconds. If this option is not set, the lights will use the duration from the previous time the light was turned on in the Ring app. For light groups, this will default to 60 seconds. The maximum value is `32767`, which is ~9.1 hours. | +| `hideDeviceIds` | `[]` | Allows you to hide specific devices by an array of ids. The id for each device is logged when homebridge starts. | +| `hideLightGroups` | `false` | Ring smart lighting allows you to create lighting groups within the Ring app. These groups are convenient for detecting motion in an area of your yard and turning on/off all lights in the group. However, you may wish to group the lights differently in HomeKit and ignore the groups you have configured in Ring. If this option is `true`, your Ring groups (and their associated motion sensor) will be ignored and will not show up in HomeKit. | +| `hideDoorbellSwitch` | `false` | If you have a Ring video doorbell, you will see a Programmable Switch associated with it. This switch can be used to perform actions on when the doorbell is pressed using "Single Press" actions. If you do not care to perform actions when the doorbell is pressed, you can hide the Programmable Switch by setting this option to `true`. You will still be able to receive _notifications_ from the doorbell even if the Programmable Switch is hidden (notifications can be configured in the settings for the doorbell camera in the Home app) | +| `hideCameraLight` | `false` | If `true`, hides the light for Ring cameras in HomeKit. | +| `hideCameraMotionSensor` | `false` | If `true`, hides the motion sensor for Ring cameras in HomeKit. | +| `hideCameraSirenSwitch` | `false` | If `true`, hides the siren switch for Ring cameras in HomeKit. | +| `hideInHomeDoorbellSwitch` | `false` | If `true`, hides the switch for in-home doorbells in HomeKit. | +| `hideAlarmSirenSwitch` | `false` | If you have a Ring Alarm, you will see both the alarm and a "Siren" switch in HomeKit. The siren switch can sometimes get triggered by Siri commands by accident, which is loud and annoying. Set this option to `true` to hide the siren switch. | +| `showPanicButtons` | `false` | Creates a new `Panic Buttons` device in HomeKit with `Burglar Alarm` and `Fire Alarm` switches. **Use these at your own risk. I do not guarantee functionality in case of emergency, nor do I take responsibility for any false alarms**. These function just like the SOS sliders in the Ring app. | +| `nightModeBypassFor` | no Night Mode | Allows you to use Night mode to "Bypass and Arm" a Ring Alarm. Can be set to `all` for Away or `some` for Home. If set and Night mode is activated from HomeKit, any open contact sensors will automatically be bypassed. | +| `avoidSnapshotBatteryDrain` | false | Causes snapshots for battery cameras to be fetched at a minimum 10 minute interval to avoid draining the battery. | +| `cameraStatusPollingSeconds` | `20` | How frequently to poll for updates to your cameras and chimes (in seconds). Information like light/siren/volume/snooze status do not update in real time and need to be requested periodically. | +| `locationModePollingSeconds` | `20` | How frequently to poll for location mode updates (in seconds). This is only useful if you are using location modes to control camera settings and want to keep an up-to-date reference of the current mode for each location. Polling is automatically disabled for locations equipped with a Ring Alarm. Will hide Location Mode switch if set to `0` | +| `locationIds` | All Locations | Use this option if you only want a subset of your locations to appear in HomeKit. If this option is not included, all of your locations will be added to HomeKit (which is what most users will want to do). | +| `onlyDeviceTypes` | All Device Types | A subset of device types to be shown. If set, all other device types will be hidden in HomeKit. | +| `ffmpegPath` | Uses `ffmpeg-for-homebridge` | A custom path to the `ffmpeg` executable. By default, the static binaries built in [ffmpeg-for-homebridge](https://github.com/oznu/ffmpeg-for-homebridge) will be used. If you prefer to use your own version of ffmpeg, you can pass a complete path, or simply `"ffmpeg"` to use ffmpeg from your `PATH`. | +| `debug` | false | Turns on additional logging. In particular, ffmpeg logging. | +| `disableLogs` | false | Turns off all logging | ### Cameras diff --git a/packages/homebridge-ring/camera-source.ts b/packages/homebridge-ring/camera-source.ts index afbc7cce..66a0ec80 100644 --- a/packages/homebridge-ring/camera-source.ts +++ b/packages/homebridge-ring/camera-source.ts @@ -72,25 +72,6 @@ class StreamingSessionWrapper { videoSplitter = new RtpSplitter() repacketizeAudioSplitter = new RtpSplitter() - libfdkAacInstalledPromise = doesFfmpegSupportCodec( - 'libfdk_aac', - getFfmpegPath(), - ) - .then((supported) => { - if (!supported) { - logError( - 'Streaming video only - found ffmpeg, but libfdk_aac is not installed. See https://github.com/dgreif/ring/wiki/FFmpeg for details.', - ) - } - return supported - }) - .catch(() => { - logError( - 'Streaming video only - ffmpeg was not found. See https://github.com/dgreif/ring/wiki/FFmpeg for details.', - ) - return false - }) - constructor( public streamingSession: StreamingSession, public prepareStreamRequest: PrepareStreamRequest, @@ -262,38 +243,19 @@ class StreamingSessionWrapper { }), ) - const shouldTranscodeAudio = await this.libfdkAacInstalledPromise - if (!shouldTranscodeAudio) { - return this.streamingSession.requestKeyFrame() - } - const transcodingPromise = this.streamingSession.startTranscoding({ input: ['-vn'], audio: [ '-map', '0:a', - ...(request.audio.codec === AudioStreamingCodecType.OPUS - ? [ - // OPUS specific - it works, but audio is very choppy - '-acodec', - 'libopus', - '-frame_duration', - request.audio.packet_time, - '-application', - 'lowdelay', - ] - : [ - // AAC-eld specific - '-acodec', - 'libfdk_aac', - '-profile:a', - 'aac_eld', - '-eld_sbr:a', - '1', - '-eld_v2', - '1', - ]), + // OPUS specific - it works, but audio is very choppy + '-acodec', + 'libopus', + '-frame_duration', + request.audio.packet_time, + '-application', + 'lowdelay', // Shared options '-flags', @@ -337,7 +299,6 @@ class StreamingSessionWrapper { return null }), - isRingUsingOpus = await this.streamingSession.isUsingOpus, returnAudioTranscoder = new ReturnAudioTranscoder({ prepareStreamRequest: this.prepareStreamRequest, startStreamRequest: request, @@ -347,19 +308,15 @@ class StreamingSessionWrapper { }, outputArgs: [ '-acodec', - ...(isRingUsingOpus - ? [ - 'libopus', - '-ac', - '1', - '-ar', - '24k', - '-b:a', - '24k', - '-application', - 'lowdelay', - ] - : ['pcm_mulaw', '-ac', 1, '-ar', '8k']), + 'libopus', + '-ac', + '1', + '-ar', + '24k', + '-b:a', + '24k', + '-application', + 'lowdelay', '-flags', '+global_header', '-f', @@ -398,10 +355,7 @@ export class CameraSource implements CameraStreamingDelegate { private sessions: { [sessionKey: string]: StreamingSessionWrapper } = {} private cachedSnapshot?: Buffer - constructor( - private ringCamera: RingCamera, - private useOpus = false, - ) { + constructor(private ringCamera: RingCamera) { this.controller = new hap.CameraController({ cameraStreamCount: 10, delegate: this, @@ -425,28 +379,21 @@ export class CameraSource implements CameraStreamingDelegate { }, }, audio: { - codecs: this.useOpus - ? [ - { - type: AudioStreamingCodecType.OPUS, - // required by watch - samplerate: AudioStreamingSamplerate.KHZ_8, - }, - { - type: AudioStreamingCodecType.OPUS, - samplerate: AudioStreamingSamplerate.KHZ_16, - }, - { - type: AudioStreamingCodecType.OPUS, - samplerate: AudioStreamingSamplerate.KHZ_24, - }, - ] - : [ - { - type: AudioStreamingCodecType.AAC_ELD, - samplerate: AudioStreamingSamplerate.KHZ_16, - }, - ], + codecs: [ + { + type: AudioStreamingCodecType.OPUS, + // required by watch + samplerate: AudioStreamingSamplerate.KHZ_8, + }, + { + type: AudioStreamingCodecType.OPUS, + samplerate: AudioStreamingSamplerate.KHZ_16, + }, + { + type: AudioStreamingCodecType.OPUS, + samplerate: AudioStreamingSamplerate.KHZ_24, + }, + ], }, }, }) diff --git a/packages/homebridge-ring/camera.ts b/packages/homebridge-ring/camera.ts index f7dc4625..570a5dcf 100644 --- a/packages/homebridge-ring/camera.ts +++ b/packages/homebridge-ring/camera.ts @@ -20,10 +20,7 @@ export class Camera extends BaseDataAccessory { ) { super() - this.cameraSource = new CameraSource( - this.device, - this.config.unbridgeCameras, - ) + this.cameraSource = new CameraSource(this.device) if (!hap.CameraController) { const error = diff --git a/packages/homebridge-ring/config.schema.json b/packages/homebridge-ring/config.schema.json index d0f2fbbd..b697724e 100644 --- a/packages/homebridge-ring/config.schema.json +++ b/packages/homebridge-ring/config.schema.json @@ -16,11 +16,6 @@ "description": "Click \"Generate New Refresh Token\" above to change accounts or if your refresh token has expired", "placeholder": "Refresh Token" }, - "unbridgeCameras": { - "title": "Unbridge Cameras", - "type": "boolean", - "description": "If enabled, all Ring Cameras we be treated as external accessories, which generally leads to better performance. This means they each need to be individually added to HomeKit. This option will be enabled by default in the next major release. WARNING: If your cameras are already bridged, they will be deleted from HomeKit when you enable this option, and you will need to reconfigure any associated automations or HomeKit settings" - }, "alarmOnEntryDelay": { "title": "Alarm on Entry Delay", "type": "boolean", @@ -157,7 +152,6 @@ "title": "Optional Configuration", "expandable": true, "items": [ - "unbridgeCameras", "alarmOnEntryDelay", "hideLightGroups", "hideDoorbellSwitch", diff --git a/packages/homebridge-ring/config.ts b/packages/homebridge-ring/config.ts index b10525f0..4109f42f 100644 --- a/packages/homebridge-ring/config.ts +++ b/packages/homebridge-ring/config.ts @@ -22,7 +22,6 @@ export interface RingPlatformConfig extends RingApiOptions { nightModeBypassFor: AlarmMode onlyDeviceTypes?: string[] showPanicButtons?: boolean - unbridgeCameras?: boolean disableLogs?: boolean } diff --git a/packages/homebridge-ring/ring-platform.ts b/packages/homebridge-ring/ring-platform.ts index 7e9f6648..0b9c64e5 100644 --- a/packages/homebridge-ring/ring-platform.ts +++ b/packages/homebridge-ring/ring-platform.ts @@ -211,7 +211,6 @@ export class RingPlatform implements DynamicPlatformPlugin { platformAccessories: PlatformAccessory[] = [], externalAccessories: PlatformAccessory[] = [], activeAccessoryIds: string[] = [] - let hasBridgedCameras = false logInfo('Found the following locations:') @@ -286,8 +285,7 @@ export class RingPlatform implements DynamicPlatformPlugin { hapDevices.forEach( ({ deviceType, device, isCamera, id, name, AccessoryClass }) => { const uuid = hap.uuid.generate(debugPrefix + id), - displayName = debugPrefix + name, - isExternalCamera = isCamera && this.config.unbridgeCameras + displayName = debugPrefix + name if ( !AccessoryClass || @@ -302,7 +300,8 @@ export class RingPlatform implements DynamicPlatformPlugin { return } - if (isExternalCamera && this.homebridgeAccessories[uuid]) { + // TODO: I think we can remove this, uuid is different with the camera differentiator + if (isCamera && this.homebridgeAccessories[uuid]) { // Camera was previously bridged. Remove it from the platform so that it can be added as an external accessory this.log.warn( `Camera ${displayName} was previously bridged. You will need to manually pair it as a new accessory.`, @@ -322,7 +321,7 @@ export class RingPlatform implements DynamicPlatformPlugin { : hap.Categories.SECURITY_SYSTEM, ) - if (isExternalCamera) { + if (isCamera) { logInfo( `Configured camera ${uuid} ${deviceType} ${displayName}`, ) @@ -334,17 +333,6 @@ export class RingPlatform implements DynamicPlatformPlugin { platformAccessories.push(accessory) } - if ( - isCamera && - !isExternalCamera && - typeof hap.Accessory.cleanupAccessoryData === 'function' - ) { - // This is a one-time cleanup that will remove persist files for old external accessories from unbridged cameras - hap.Accessory.cleanupAccessoryData( - generateMacAddress(accessory.UUID), - ) - } - return accessory }, homebridgeAccessory = @@ -358,8 +346,6 @@ export class RingPlatform implements DynamicPlatformPlugin { this.homebridgeAccessories[uuid] = homebridgeAccessory activeAccessoryIds.push(uuid) - - hasBridgedCameras ||= isCamera && !isExternalCamera }, ) }), @@ -406,11 +392,5 @@ export class RingPlatform implements DynamicPlatformPlugin { }) }, ) - - if (hasBridgedCameras) { - logError( - 'Bridged camera support will be removed in the next major release of homebridge-ring. Please enable the unbridgeCameras option in your configuration and add the individual cameras to HomeKit to prepare for this change.', - ) - } } }