tchellomello / python-ring-doorbell

Python Ring Door Bell is a library written in Python 3 that exposes the Ring.com devices as Python objects.
GNU Lesser General Public License v3.0
548 stars 169 forks source link

Listen only picks up certain devices #390

Open JPAssoc opened 1 month ago

JPAssoc commented 1 month ago

I'm using the listen part of the API to receive notifications and my callback only seems to be triggered for one device in particular - a camera - whereas I actually want it to intercept my doorbell. Is there some kind of filtering going on, and if so how do I configure it differently?

Many thanks in advance,

Jon

JPAssoc commented 1 month ago

Quick update: this problem seems to be specific to Raspberry Pi (version 3.11.2), because it works fine on Mac (Python version 3.12.4).

JPAssoc commented 1 month ago

Should also add that the CLI works fine on Raspberry Pi, and I can't see any difference between what the CLI code is doing and what I'm doing!

sdb9696 commented 1 month ago

It should work with doorbells but I don’t think it works with intercoms. I can’t understand why the raspberry pi would make a difference, that seems like a red herring.

sdb9696 commented 1 month ago

Feel free to post your code and I’ll have a look tomorrow.

JPAssoc commented 1 month ago

Many thanks. Here it is (the commented-out GPIO references are all for the actual job that the program's supposed to be doing - controlling an old-school doorbell - and can be ignored):

!/usr/bin/env python

import sys import json import getpass import time import asyncio import struct import RPi.GPIO as GPIO import logging from pathlib import Path from pprint import pprint from ring_doorbell import Ring, Auth, AuthenticationError, Requires2FAError from ring_doorbell.exceptions import RingError from ring_doorbell.const import DINGS_ENDPOINT, CLI_TOKEN_FILE, GCM_TOKEN_FILE, USER_AGENT from ring_doorbell.listen import can_listen from ring_doorbell.listen import RingEventListener from threading import Thread

cache_file = Path(CLI_TOKEN_FILE) credentials_file = Path(GCM_TOKEN_FILE)

def token_updated(token): cache_file.write_text(json.dumps(token))

def otp_callback(): auth_code = input('2FA code: ') return str(auth_code)

def credentials_updated(credentials): credentials_file.write_text(json.dumps(credentials))

class ringCB(): def call(self, event): logging.info('Device: %s, kind %s, event %s' %(event.device_name, event.device_kind, event.kind))

def initialise():

GPIO.setwarnings(False)

GPIO.setmode(GPIO.BCM)

GPIO.setup(26, GPIO.OUT)

GPIO.output(26, GPIO.HIGH)

logging.basicConfig(format='%(asctime)s - %(message)s', filename='Ring2ByronServer.log', level=logging.INFO)

if cache_file.is_file():
    auth = Auth(USER_AGENT, json.loads(cache_file.read_text()), token_updated)
else:
    username = input('Username: ')
    password = getpass.getpass('Password: ')
    auth = Auth(USER_AGENT, None, token_updated)
    try:
        auth.fetch_token(username, password)
    except Requires2FAError:
        auth.fetch_token(username, password, otp_callback())

global ring
ring = Ring(auth)
ring.update_data()

global myCallback
myCallback = ringCB()

credentials = None

if (credentials_file.is_file()):
    credentials = json.loads(credentials_file.read_text())

global listener
listener = RingEventListener(ring, credentials, credentials_updated)
listener.start(callback=myCallback)

if listener.subscribed:
    logging.info('Subscribed')
else:
    logging.info('Not subscribed')

if listener.started:
    logging.info('Started')
else:
    logging.info('Not started')

if name == "main": initialise()

while True:
    time.sleep(1)

logging.info('Finished')
JPAssoc commented 1 month ago

(Just noticed that instead of being commented out, the hashes have turned those GPIO calls into some kind of header. Please ignore them!)

JPAssoc commented 1 month ago

Finally, a couple of other things that may or may not be relevant:

1) I have several stick-up cameras on my Ring network and the only one for which I get event notifications on Raspberry Pi is the most recent one that reports "Person detected" instead of "Motion".

2) Like several others have reported, I needed to tweak the call to strptime in eventlistener.py to avoid an exception, thus:

    created_at = ding["created_at"]
    create_seconds = (
        datetime.strptime(created_at, "%Y-%m-%dT%H:%M:%S%z")
    ).timestamp()

(removing the .%f after the %S)

JPAssoc commented 1 month ago

Feel free to post your code and I’ll have a look tomorrow.

Just wondering if you'd had any more thoughts on this? No worries if you haven't had time to look yet.

JPAssoc commented 1 month ago

Some more info. I've been doing some more experiments today, basically to see if I can pin down the difference between the code in the CLI and mine. And it now turns out on the Raspberry Pi that the CLI no longer delivers events apart from the one device that my code is picking up. I've even reverted to the official eventlistener.py release just in case I've accidentally broken something there, and all that happens if I do that is that events are ignored apart from that one device, which causes it to crash as mentioned above. So there's maybe some kind of intermittent thing going on - maybe something not initialised? Don't know. I'll carry on looking, but any help would be much appreciated.

JPAssoc commented 4 weeks ago

Another update. I've found that it works more reliably on my Mac, so I've split the functionality between the Mac and my Raspberry Pi, using a TCP socket to connect the two. However, even the Mac is not reliable. Sometimes it works and sometimes I have to reboot the Mac to clear down whatever it is that needs clearing down. There is definitely something odd going on.