Skip to content

Commit

Permalink
Workaround for #332 - iOS 13 Picture Notifications (#355)
Browse files Browse the repository at this point in the history
* Workaround for #332 - iOS 13 Picture Notifications

Workaround for iOS 13, and the requirement for motion sensors to be part of a camera in order for picture notifications.  Creates a dummy motion sensor and switch as part of the camera, and the switch turns the motion sensor on.  The motion sensor is automatically turned off after 5 seconds.

To enable picture notifications, create an Automation in the home app that turns on the dummy switch when the real motion sensor detects motion.

Also includes pull request #309

* Final version

Add option for dummy switch/motion button
Pull request #346
  • Loading branch information
NorthernMan54 authored and KhaosT committed Sep 27, 2019
1 parent 5bd4407 commit 282fca4
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 70 deletions.
77 changes: 34 additions & 43 deletions README.md
Expand Up @@ -31,6 +31,40 @@ ffmpeg plugin for [Homebridge](https://github.com/nfarina/homebridge)

#### Optional Parameters

* `uploader` enable uploading of snapshots to Google Drive, defaults to `false`. See wiki for more detailed instructions.
* `motion` enable a dummy switch and motion sensor to trigger picture notifications in iOS 13, defaults to `false`. See wiki for more detailed instructions.
* `manufacturer` set manufacturer name for display in the Home app
* `model` set model for display in the Home app
* `serialNumber` set serial number for display in the Home app
* `firmwareRevision` set firmware revision for display in the Home app

Example with manufacturer, model, serial number and firmware set:

```
{
"platform": "Camera-ffmpeg",
"cameras": [
{
"name": "Camera Name",
"manufacturer": "ACME, Inc.",
"model": "ABC-123",
"serialNumber": "1234567890",
"firmwareRevision": "1.0",
"videoConfig": {
"source": "-re -i rtsp://myfancy_rtsp_stream",
"stillImageSource": "-i http://faster_still_image_grab_url/this_is_optional.jpg",
"maxStreams": 2,
"maxWidth": 1280,
"maxHeight": 720,
"maxFPS": 30
}
}
]
}
```

#### Optional videoConfig Parameters

* `maxStreams` is the maximum number of streams that will be generated for this camera, default 2
* `maxWidth` is the maximum width reported to HomeKit, default `1280`
* `maxHeight` is the maximum height reported to HomeKit, default `720`
Expand Down Expand Up @@ -114,49 +148,6 @@ A somewhat complicated example:
}
```

## Uploading to Google Drive of Still Images ( Snapshots )

This is an optional feature that will automatically store every snapshot taken to your Google Drive account as a photo. This is very useful if you have motion sensor in the same room as the camera, as it will take a snapshot of whatever caused the motion sensor to trigger, and store the image on Google Drive and create a Picture Notification on your iOS device.

The snapshots are stored in a folder called "Camera Pictures", and are named with camera name, date and time of the image.

To enable this feature, please add a new config option "uploader", and follow the steps below.

* Add the option "uploader" to your config.json i.e.

```
{
"platform": "Camera-ffmpeg",
"cameras": [
{
"name": "Camera Name",
"uploader": true,
"videoConfig": {
"source": "-re -i rtsp://myfancy_rtsp_stream",
"stillImageSource": "-i http://faster_still_image_grab_url/this_is_optional.jpg",
"maxStreams": 2,
"maxWidth": 1280,
"maxHeight": 720,
"maxFPS": 30,
"vcodec": "h264_omx"
}
}
]
}
```

If the option is missing, it defaults to false, and does not enable the uploader.

* For the setup of Google Drive, please follow the Google Drive Quickstart for Node.js instructions from here except for these changes.

https://developers.google.com/drive/v3/web/quickstart/nodejs

* In Step 1, download the configuration file into your .homebridge directory, and name it `client_secret.json`
* Skip Step 2 and 3
* And in step 4, from the homebridge-camera-ffmpeg directory, run `node quickstart.js`

Then just follow steps a to c

## Tested configurations

We have started collecting tested configurations in the wiki, so please before raising an issue with your configuration, please check the [wiki](https://github.com/KhaosT/homebridge-camera-ffmpeg/wiki). Also if you have a working configuration that you would like to share, please add it to the [wiki](https://github.com/KhaosT/homebridge-camera-ffmpeg/wiki).
Expand Down
43 changes: 19 additions & 24 deletions drive.js
@@ -1,8 +1,7 @@
var debug = require('debug')('CameraDrive');
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
const {google} = require('googleapis');
var url = require('url');

module.exports = {
Expand Down Expand Up @@ -44,7 +43,6 @@ drive.prototype.storePicture = function(prefix, picture) {
debug("getFolder");
if (auth) {
getPictureFolder(function(err, folder) {
debug("upload");
uploadPicture(folder, prefix, picture);
})
}
Expand All @@ -63,8 +61,8 @@ function getPictureFolder(cb) {
if (err) {
cb(err);
} else {
if (res.files.length > 0) {
res.files.forEach(function(file) {
if (res.data.files.length > 0) {
res.data.files.forEach(function(file) {
debug('Found Folder: ', file.name, file.id);
cb(null, file.id);
});
Expand Down Expand Up @@ -105,7 +103,7 @@ function uploadPicture(folder, prefix, picture) {
};
var media = {
mimeType: 'image/jpeg',
body: picture
body: picture.toString()
};

drive.files.create({
Expand All @@ -118,7 +116,7 @@ function uploadPicture(folder, prefix, picture) {
// Handle error
console.log(err);
} else {
debug('File Id: ', file.id);
debug('File Id: ', file.data.id);
}
});

Expand All @@ -134,19 +132,16 @@ function uploadPicture(folder, prefix, picture) {
* @param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
const {client_secret, client_id, redirect_uris} = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);

// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function(err, token) {
if (err) {
getNewToken(oauth2Client, callback);
getNewToken(oAuth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
oAuth2Client.credentials = JSON.parse(token);
callback(oAuth2Client);
}
});
}
Expand All @@ -155,12 +150,12 @@ function authorize(credentials, callback) {
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
function getNewToken(oAuth2Client, callback) {
var authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
Expand All @@ -171,14 +166,14 @@ function getNewToken(oauth2Client, callback) {
});
rl.question('Enter the code from that page here: ', function(code) {
rl.close();
oauth2Client.getToken(code, function(err, token) {
oAuth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
oAuth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
callback(oAuth2Client);
});
});
}
Expand Down Expand Up @@ -245,7 +240,7 @@ function uploadFile(auth) {
if (err) {
callback(err);
} else {
res.files.forEach(function(file) {
res.data.files.forEach(function(file) {
debug('Found file: ', file.name, file.id);
});
if (res.nextPageToken) {
Expand Down Expand Up @@ -283,7 +278,7 @@ function uploadFile(auth) {
// Handle error
console.log(err);
} else {
debug('Folder Id: ', file.id);
debug('Folder Id: ', file.data.id);

var fileMetadata = {
'name': 'photo.jpg',
Expand All @@ -304,7 +299,7 @@ function uploadFile(auth) {
// Handle error
console.log(err);
} else {
debug('File Id: ', file.id);
debug('File Id: ', file.data.id);
}
});

Expand Down
46 changes: 45 additions & 1 deletion index.js
@@ -1,10 +1,12 @@
var Accessory, hap, UUIDGen;
var Accessory, Service, Characteristic, hap, UUIDGen;

var FFMPEG = require('./ffmpeg').FFMPEG;

module.exports = function(homebridge) {
Accessory = homebridge.platformAccessory;
hap = homebridge.hap;
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
UUIDGen = homebridge.hap.uuid;

homebridge.registerPlatform("homebridge-camera-ffmpeg", "Camera-ffmpeg", ffmpegPlatform, true);
Expand Down Expand Up @@ -51,11 +53,53 @@ ffmpegPlatform.prototype.didFinishLaunching = function() {

var uuid = UUIDGen.generate(cameraName);
var cameraAccessory = new Accessory(cameraName, uuid, hap.Accessory.Categories.CAMERA);
var cameraAccessoryInfo = cameraAccessory.getService(Service.AccessoryInformation);
if (cameraConfig.manufacturer) {
cameraAccessoryInfo.setCharacteristic(Characteristic.Manufacturer, cameraConfig.manufacturer);
}
if (cameraConfig.model) {
cameraAccessoryInfo.setCharacteristic(Characteristic.Model, cameraConfig.model);
}
if (cameraConfig.serialNumber) {
cameraAccessoryInfo.setCharacteristic(Characteristic.SerialNumber, cameraConfig.serialNumber);
}
if (cameraConfig.firmwareRevision) {
cameraAccessoryInfo.setCharacteristic(Characteristic.FirmwareRevision, cameraConfig.firmwareRevision);
}

cameraAccessory.context.log = self.log;
if (cameraConfig.motion) {
var button = new Service.Switch(cameraName);
cameraAccessory.addService(button);

var motion = new Service.MotionSensor(cameraName);
cameraAccessory.addService(motion);

button.getCharacteristic(Characteristic.On)
.on('set', _Motion.bind(cameraAccessory));
}

var cameraSource = new FFMPEG(hap, cameraConfig, self.log, videoProcessor, interfaceName);
cameraAccessory.configureCameraSource(cameraSource);
configuredAccessories.push(cameraAccessory);
});

self.api.publishCameraAccessories("Camera-ffmpeg", configuredAccessories);
}
};

function _Motion(on, callback) {
this.context.log("Setting %s Motion to %s", this.displayName, on);

this.getService(Service.MotionSensor).setCharacteristic(Characteristic.MotionDetected, (on ? 1 : 0));
if (on) {
setTimeout(_Reset.bind(this), 5000);
}
callback();
}

function _Reset() {
this.context.log("Setting %s Button to false", this.displayName);

this.getService(Service.Switch).setCharacteristic(Characteristic.On, false);
}
3 changes: 1 addition & 2 deletions package.json
Expand Up @@ -18,8 +18,7 @@
"homebridge": ">=0.4.5"
},
"dependencies": {
"google-auth-library": "^0.10.0",
"googleapis": "^18.0.0",
"googleapis": ">=39.1.0",
"ip": "^1.1.3",
"debug": "^2.2.0"
}
Expand Down

0 comments on commit 282fca4

Please sign in to comment.