List of commits:
Subject Hash Author Date (UTC)
Initial commit 9a953422e18006f3a5ceb554e2699434cde705d4 brettck85 2018-07-06 10:52:31
Commit 9a953422e18006f3a5ceb554e2699434cde705d4 - Initial commit
Author: brettck85
Author date (UTC): 2018-07-06 10:52
Committer name: brettck85
Committer date (UTC): 2018-07-06 10:52
Parent(s):
Signing key:
Tree: 034cbe89156e2142b8ae6ac0d8bfe51546e2c69a
File Lines added Lines deleted
.gitignore 37 0
LICENSE 21 0
README.md 59 0
index.js 336 0
package.json 27 0
File .gitignore added (mode: 100644) (index 0000000..5148e52)
1 # Logs
2 logs
3 *.log
4 npm-debug.log*
5
6 # Runtime data
7 pids
8 *.pid
9 *.seed
10
11 # Directory for instrumented libs generated by jscoverage/JSCover
12 lib-cov
13
14 # Coverage directory used by tools like istanbul
15 coverage
16
17 # nyc test coverage
18 .nyc_output
19
20 # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 .grunt
22
23 # node-waf configuration
24 .lock-wscript
25
26 # Compiled binary addons (http://nodejs.org/api/addons.html)
27 build/Release
28
29 # Dependency directories
30 node_modules
31 jspm_packages
32
33 # Optional npm cache directory
34 .npm
35
36 # Optional REPL history
37 .node_repl_history
File LICENSE added (mode: 100644) (index 0000000..77bca7a)
1 MIT License
2
3 Copyright (c) 2016 Homespun
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in all
13 copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 SOFTWARE.
File README.md added (mode: 100644) (index 0000000..c621547)
1 # homebridge-platform-ring-video-doorbell
2 A [Ring Video Doorbell](https://ring.com/) platform plugin for [Homebridge](https://github.com/nfarina/homebridge).
3
4 # Installation
5 Run these commands:
6
7 % sudo npm install -g homebridge
8 % sudo npm install -g homebridge-platform-ring-video-doorbell
9
10 On Linux, you might see this output for the second command:
11
12 npm ERR! pcap2@3.0.4 install: node-gyp rebuild
13 npm ERR! Exit status 1
14 npm ERR!
15
16 If so, please try
17
18 % apt-get install libpcap-dev
19
20 and try
21
22 % sudo npm install -g homebridge-platform-ring-video-doorbell
23
24 again!
25
26 NB: If you install homebridge like this:
27
28 sudo npm install -g --unsafe-perm homebridge
29
30 Then all subsequent installations must be like this:
31
32 sudo npm install -g --unsafe-perm homebridge-platform-ring-video-doorbell
33
34 # Configuration
35 Edit `~/.homebridge/config`, inside `"platforms": [ ... ]` add:
36
37 { "platform" : "ring-video-doorbell"
38 , "name" : "Doorbell"
39 , "username" : "user@example.com"
40 , "password" : "secret"
41
42 // optional, here are the defaults
43 , "options" : { "retries": 15, "ttl": 5, "verboseP" : false }
44 }
45
46 # Camera Integration
47 The current version of this plugin doesn't handle the camera available in the Ring Video doorbell;
48 however,
49 as noted by [@barkerja](https://github.com/barkerja),
50 you can use the [camera plugin](https://github.com/KhaosT/homebridge-camera-ffmpeg),
51 and place both accessories in the same "room".
52 HomeKit manage the two accessories as one "seamless" device.
53
54 # Many Thanks
55 Many thanks to [jeroenmoors](https://github.com/jeroenmoors) author of
56 [php-ring-api](https://github.com/jeroenmoors/php-ring-api).
57
58 Many thanks (also) to [davglass](https://github.com/davglass) author of
59 [doorbot](https://github.com/davglass/doorbot).
File index.js added (mode: 100644) (index 0000000..7332306)
1 /* jshint asi: true, node: true, laxbreak: true, laxcomma: true, undef: true, unused: true */
2
3 // there is no known webhook/websocket to use for events, this results in very frequent polling under the push-sensor model...
4
5 var homespun = require('homespun-discovery')
6 , pushsensor = homespun.utilities.pushsensor
7 , PushSensor = pushsensor.Sensor
8 , RingAPI = require('doorbot')
9 , sensorTypes = homespun.utilities.sensortypes
10 , underscore = require('underscore')
11 , util = require('util')
12
13
14 var Accessory
15 , Service
16 , Characteristic
17 , CommunityTypes
18 , UUIDGen
19
20 module.exports = function (homebridge) {
21 Accessory = homebridge.platformAccessory
22 Service = homebridge.hap.Service
23 Characteristic = homebridge.hap.Characteristic
24 CommunityTypes = require('hap-nodejs-community-types')(homebridge)
25 UUIDGen = homebridge.hap.uuid
26
27 pushsensor.init(homebridge)
28 homebridge.registerPlatform('homebridge-platform-ring-video-doorbell', 'ring-video-doorbell', Ring, true)
29 }
30
31
32 var Ring = function (log, config, api) {
33 if (!(this instanceof Ring)) return new Ring(log, config, api)
34
35 if (!config) return
36
37 this.log = log
38 this.config = config
39 this.api = api
40
41 this.options = underscore.defaults(this.config.options || {}, { retries: 5, ttl: 5, verboseP: false })
42
43 this.discoveries = {}
44 this.doorbots = {}
45 this.stickup_cams = {}
46
47 if (api) this.api.on('didFinishLaunching', this._didFinishLaunching.bind(this))
48 else this._didFinishLaunching()
49 }
50
51 Ring.prototype._didFinishLaunching = function () {
52 var self = this
53
54 var refresh = function () {
55 var ring = RingAPI({ email : self.config.username
56 , password : self.config.password
57 , retries : self.options.retries
58 , userAgent : self.options.userAgent
59 })
60
61 self.doorbot = ring
62 self._refresh1(function (err) {
63 if (err) {
64 self.log.error('refresh1', underscore.extend({ username: self.config.username }, err))
65 return setTimeout(refresh, 30 * 1000)
66 }
67
68 self._refresh2(function (err) {
69 if (err) {
70 self.log.error('refresh2', underscore.extend({ username: self.config.username }, err))
71 return setTimeout(refresh, 30 * 1000)
72 }
73
74 return setTimeout(refresh, self.options.ttl * 1000)
75 })
76 })
77 }
78
79 refresh()
80
81 self.log('didFinishLaunching')
82 }
83
84 Ring.prototype._addAccessory = function (device) {
85 var self = this
86
87 var accessory = new Accessory(device.name, device.uuid)
88
89 accessory.on('identify', function (paired, callback) {
90 self.log(accessory.displayName, ': identify request')
91 callback()
92 })
93
94 if (device.attachAccessory.bind(device)(accessory)) self.api.updatePlatformAccessories([ accessory ])
95
96 if (!self.discoveries[accessory.UUID]) {
97 self.api.registerPlatformAccessories('homebridge-platform-ring-video-doorbell', 'ring-video-doorbell', [ accessory ])
98 self.log('addAccessory', underscore.pick(device, [ 'uuid', 'name', 'manufacturer', 'model', 'serialNumber' ]))
99 }
100 }
101
102 Ring.prototype.configurationRequestHandler = function (context, request, callback) {/* jshint unused: false */
103 this.log('configuration request', { context: context, request: request })
104 }
105
106 Ring.prototype.configureAccessory = function (accessory) {
107 var self = this
108
109 accessory.on('identify', function (paired, callback) {
110 self.log(accessory.displayName, ': identify request')
111 callback()
112 })
113
114 self.discoveries[accessory.UUID] = accessory
115 self.log('configureAccessory', underscore.pick(accessory, [ 'UUID', 'displayName' ]))
116 }
117
118 /*
119 { "doorbots" :
120 { "id" : ...
121 , "description" : "Front Gate"
122 , "device_id" : "..."
123 , "time_zone" : "America\/Chicago"
124 , "subscribed" : true
125 , "subscribed_motions" : true
126 , "battery_life" : 20
127 , "external_connection" : false
128 , "firmware_version" : "1.8.73"
129 , "kind" : "doorbell"
130 , "latitude" : 39.8333333
131 , "longitude" : -98.585522
132 , "address" : ".... .... .., Lebanon, KS 66952 USA"
133 , "settings" : { ... }
134 , "features" :
135 { "motions_enabled" : true
136 , "show_recordings" : true
137 , "show_vod_settings" : true
138 }
139 , "owned" : true
140 , "alerts" :
141 { "connection" : "offline"
142 , "battery" : "low"
143 }
144 , "owner" :
145 { "id" : ...
146 , "first_name" : null
147 , "last_name" : null
148 , "email" : "user@example.com"
149 }
150 }
151 , "authorized_doorbots" : [ ... ]
152 , "chimes" : [ ... ]
153 , "stickup_cams" : [ ... ]
154 }
155 */
156
157 Ring.prototype._refresh1 = function (callback) {
158 var self = this
159
160 self.doorbot.devices(function (err, result) {
161 var serialNumbers = []
162
163 if (err) return callback(err)
164
165 var handle_device = function(proto, devices, service) {
166 var capabilities, properties
167 , deviceId = service.id
168 , device = devices[deviceId]
169
170 if (!device) {
171 console.log('type: ' + ((devices === self.doorbots) ? 'ringing' : 'floodlight') + ' ... ' + JSON.stringify(service, null, 2))
172 capabilities = underscore.pick(sensorTypes,
173 [ 'battery_level', 'battery_low', 'motion_detected', 'reachability' ])
174 underscore.extend(capabilities, underscore.pick(sensorTypes,
175 [ (devices === self.doorbots) ? 'ringing' : 'floodlight' ]))
176 properties = { name : service.description
177 , manufacturer : 'Bot Home Automation, Inc.'
178 , model : service.kind
179 , serialNumber : service.id.toString()
180 , firmwareRevision : service.firmware_version
181 , hardwareRevision : ''
182 }
183
184 device = new proto(self, service.id.toString(), { capabilities: capabilities, properties: properties })
185 devices[deviceId] = device
186 }
187
188 device.readings = { battery_level : service.battery_life
189 , battery_low : (service.alerts) && (service.alerts.battery == 'low')
190 ? Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
191 : Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL
192 , reachability : (service.alerts) && (service.alerts.connection !== 'offline')
193 , floodlight : !service.led_status ? undefined : service.led_status !== 'off'
194 }
195 device._update.bind(device)(device.readings)
196
197 serialNumbers.push(service.id.toString())
198 }
199 var check_devices = function (devices) {
200 underscore.keys(devices).forEach(function (deviceId) {
201 var device = devices[deviceId]
202 var accessory = device.accessory
203
204 if (serialNumbers.indexOf(device.serialNumber) !== -1) return
205
206 if (accessory) {
207 self.api.registerPlatformAccessories('homebridge-platform-ring-video-doorbell', 'ring-video-doorbell', [ accessory ])
208 self.log('removeAccessory', underscore.pick(device, [ 'uuid', 'name', 'manufacturer', 'model', 'serialNumber' ]))
209 }
210
211 delete devices[deviceId]
212 })
213 }
214
215 if (!result) result = {}
216
217 if (!result.doorbots) result.doorbots = []
218 result.doorbots.forEach(function (service) { handle_device(Doorbot, self.doorbots, service) })
219 check_devices(self.doorbots)
220
221 if (!result.stickup_cams) result.stickup_cams = []
222 result.stickup_cams.forEach(function (service) { handle_device(StickupCam, self.stickup_cams, service) })
223 check_devices(self.stickup_cams)
224
225 callback()
226 })
227 }
228
229 /*
230 [
231 {
232 "id" : ...,
233 "id_str" : "...",
234 "state" : "ringing",
235 "protocol" : "sip",
236 "doorbot_id" : ...,
237 "doorbot_description" : "Front Gate",
238 "device_kind" : "doorbell",
239 "motion" : false,
240 "snapshot_url" : "",
241 "kind" : "ding",
242 "sip_server_ip" : "a.b.c.d"
243 "sip_server_port" : "15063",
244 "sip_server_tls" : "false",
245 "sip_session_id" : "...",
246 "sip_from" : "sip:...@ring.com",
247 "sip_to" : "sip:...@a.b.c.d:15063;transport=tcp",
248 "audio_jitter_buffer_ms" : 0,
249 "video_jitter_buffer_ms" : 0,
250 "sip_endpoints" : null,
251 "expires_in" : 171,
252 "now" : 1483114179.70994,
253 "optimization_level" : 3,
254 "sip_token" : "..."
255 "sip_ding_id" : "..."
256 }
257 ]
258 */
259
260 Ring.prototype._refresh2 = function (callback) {
261 var self = this
262
263 self.doorbot.dings(function (err, result) {
264 if (err) return callback(err)
265
266 if (!util.isArray(result)) return callback(new Error('not an Array: ' + typeof result))
267
268 underscore.keys(self.doorbots).forEach(function (deviceId) {
269 underscore.extend(self.doorbots[deviceId].readings, { motion_detected: false, ringing: false })
270 })
271
272 result.forEach(function (event) {
273 var device
274
275 if (event.state !== 'ringing') return
276
277 device = self.doorbots[event.doorbot_id] || self.stickup_cams[event.doorbot_id]
278 if (!device) return self.log.error('dings/active: no device', event)
279
280 underscore.extend(device.readings, { motion_detected : (event.kind === 'motion') || (event.motion)
281 , ringing : event.kind === 'ding' })
282 })
283 underscore.keys(self.doorbots).forEach(function (deviceId) {
284 var device = self.doorbots[deviceId]
285
286 device._update.bind(device)(device.readings)
287 })
288 underscore.keys(self.stickup_cams).forEach(function (deviceId) {
289 var device = self.stickup_cams[deviceId]
290
291 device._update.bind(device)(device.readings)
292 })
293
294 callback()
295 })
296 }
297
298
299 var Doorbot = function (platform, deviceId, service) {
300 if (!(this instanceof Doorbot)) return new Doorbot(platform, deviceId, service)
301
302 PushSensor.call(this, platform, deviceId, service)
303 }
304 util.inherits(Doorbot, PushSensor)
305
306 var StickupCam = function (platform, deviceId, service) {
307 var self = this
308
309 if (!(this instanceof StickupCam)) return new StickupCam(platform, deviceId, service)
310
311 var floodlight
312
313 PushSensor.call(this, platform, deviceId, service)
314
315 floodlight = self.getAccessoryService(Service.Lightbulb)
316 if (!floodlight) return self.log.warn('StickupCam', { err: 'could not find Service.Lightbulb' })
317
318 console.log('!!! setting callback for on/off')
319 floodlight.getCharacteristic(Characteristic.On).on('set', function (value, callback) {
320 console.log('!!! set value to ' + JSON.stringify(value))
321 platform.doorbot[value ? 'lightOn' : 'lightOff']({ id: deviceId },
322 function (err, response, result) {/* jshint unused: false */
323 console.log('!!! result from doorbot.' + (value ? 'lightOn' : 'lightOff') + ': errP=' + (!!err))
324 if (err) {
325 self.log.error('setValue', underscore.extend({ deviceId: deviceId }, err))
326 } else {
327 self._update.bind(self)({ floodlight: value })
328 }
329
330 callback()
331 })
332 console.log('!!! setting value to ' + JSON.stringify(value))
333 })
334 console.log('!!! callback for on/off is now set')
335 }
336 util.inherits(StickupCam, PushSensor)
File package.json added (mode: 100644) (index 0000000..097b64d)
1 { "name" : "homebridge-platform-ring-video-doorbell"
2 , "version" : "0.6.5"
3 , "description" : "A Ring Video Doorbell platform plugin for Homebridge: https://github.com/nfarina/homebridge"
4 , "author" :
5 { "name" : "Marshall Rose"
6 , "email" : "mrose17@homespun.io"
7 }
8 , "main" : "index.js"
9 , "scripts" : { "lint" : "jshint package.json *.js" }
10 , "license" : "MIT"
11 , "keywords" : [ "homebridge-plugin", "ring-video-doorbell" ]
12 , "repository" :
13 { "type" : "git"
14 , "url" : "git://github.com/homespun/homebridge-platform-ring-video-doorbell.git"
15 }
16 , "bugs" : { "url": "https://github.com/homespun/homebridge-platform-ring-video-doorbell/issues" }
17 , "engines" :
18 { "node" : ">=0.12.0"
19 , "homebridge" : ">=0.4.16"
20 }
21 , "dependencies" :
22 { "doorbot" : "^3.0.1"
23 , "hap-nodejs-community-types" : "https://github.com/homespun/hap-nodejs-community-types.git"
24 , "homespun-discovery" : "0.1.26"
25 , "underscore" : "1.8.3"
26 }
27 }
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/brettck85/homebridge-platform-ring-video-doorbell

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/brettck85/homebridge-platform-ring-video-doorbell

Clone this repository using git:
git clone git://git.rocketgit.com/user/brettck85/homebridge-platform-ring-video-doorbell

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main