Ring 2 Doorbell

Moderator: leecollings

Korrel
Posts: 9
Joined: Friday 31 January 2014 11:02
Target OS: Linux
Domoticz version:
Contact:

Re: Ring 2 Doorbell

Post by Korrel »

robgeerts wrote: Thursday 16 June 2022 23:23 I installed this script today and it almost works, the problem is when i actived motion detection.
"last_event_type" is in my case always 'motion'...
Never 'ding'.

When i disabled motion detection, my 'last_event_type' is 'ding' and the script is working fine.
In the most ideal situation i want one domoticz switch for motion and one for ding.. how to solve if ding is never a 'last_event_type'?

Code: Select all

def check_device_has_active_alerts(device_name: str, doorbells):
    try:
        triggered_device = next((doorbell for doorbell in doorbells if doorbell.name == device_name), None)
        last_event_type = triggered_device.history(limit=1)[0]['kind']
        newest_event_id = find_newest_recording_id(triggered_device)

        if "ding" in last_event_type:
            print(f"{ts()}: {device_name} '{last_event_type}' event was triggered (id: {newest_event_id})")
            notify_domoticz(device_name)
    except:
        triggered_device, last_event_type, newest_event_id = None, None, None

    return triggered_device, last_event_type, newest_event_id

The script is getting events from history. A ding is mostly after a motion... Since a motion last for 40-60s, this would be the last item being stored in history when the ding appears...
I've recoded the script to not look at history but take the event at that time.
Ive also changed notify domoticz to trigger different ding and motion devices ;)

Code: Select all

import json, os
from pathlib import Path
from ring_doorbell import Ring, Auth
from oauthlib.oauth2 import MissingTokenError
import time
from datetime import datetime, timezone
import urllib.request as urllib
from threading import Thread

######### This Section Needs Edited By You - unique_api_name as well! ###########################
your_username = '***@*****.*l'
your_password = **************'
your_unique_api_name = 'PythonMonitorAPI/0.1.1'
cache_file = Path("/home/pi/domoticz/scripts/ring/cached_auth_token.json")
videopath = '/mnt/documents/ring/captures/'
poll_interval = 1.0  # interval in seconds that we poll the Ring API for new events
# Domoticz setup
domoticz_api = 'http://192.168.1.210:8080'
domoticz_idx_mapping_ding = {"Voordeur": 6711, "Back Door": 6711}
domoticz_idx_mapping_motion = {"Voordeur": 11231, "Back Door": 11231}
#################################################################################################

###### DO NOT EDIT THESE IF YOU DO NOT KNOW WHAT THEY ARE FOR ###################################
# .env overrides: 1) create your .env file with the exported VARS below (ie. export RING_USERNAME='MYUSERNAME')
#                 2) source them and start this script like:  $ source .env && python3 <script_name>.py
# Note: decided to handle this natively rather than use the python-dotenv package
username = os.getenv('RING_USERNAME', your_username)
password = os.getenv('RING_PASSWORD', your_password)
unique_api_name = os.getenv('RING_API_NAME', your_unique_api_name)

def authenticate():
    if cache_file.is_file():
        auth = Auth(unique_api_name, json.loads(cache_file.read_text()), token_updated)
    else:
        auth = Auth(unique_api_name, None, token_updated)
        try:
            auth.fetch_token(username, password)
        except MissingTokenError:
            auth.fetch_token(username, password, otp_callback())

    auth = Ring(auth)
    return auth

def authenticate_and_initialize(gadget_type: str = "doorbells"):
    myring = authenticate()
    myring.update_data()
    devices = myring.devices()

    if gadget_type is "doorbells":
        doorbells = devices['doorbots']
        enumerate_doorbells(doorbells)

        return myring, doorbells

def ts(filename_format: bool = False, dirname_format: bool = False):
    if dirname_format:
        return f"{datetime.now().strftime('%Y-%m-%d')}/"
    if filename_format:
        return datetime.now().strftime('%H-%M-%S')
    else:
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')


def construct_local_filename(triggered_device, device_name: str, last_event_type: str, newest_event_id: int):
    filename_timestamp = triggered_device.history(limit=1, enforce_limit=True)[0]['created_at'].replace(
        tzinfo=timezone.utc).astimezone(tz=None).strftime('%H-%M-%S')

    local_filename = f"{videopath}{daily_directory()}{device_name.replace(' ', '')}_{last_event_type}_" \
                     f"{filename_timestamp}_{str(newest_event_id)[-4:]}.mp4"

    return local_filename


def daily_directory():
    daily_directory_path = videopath + ts(dirname_format=True)

    if os.path.isdir(daily_directory_path):
        return ts(dirname_format=True)
    else:
        os.mkdir(daily_directory_path)
        print(f"{ts()}: Created new daily directory at {daily_directory_path}")
        return ts(dirname_format=True)


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


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


def enumerate_doorbells(doorbells):
    for doorbell in doorbells:
        print(f"{ts()}: Authentication and discovery successful for {doorbell}.")


def notify_domoticz(device_name: str, this_event_type: str):
    try:
        if this_event_type == "ding":
          idx = domoticz_idx_mapping_ding.get(device_name)
        if this_event_type == "motion":
          idx = domoticz_idx_mapping_motion.get(device_name)
        urllib.urlopen(f'{domoticz_api}/json.htm?type=command&param=switchlight&idx={idx}&switchcmd=On')
        print(f"{ts()}: Domoticz (idx:{idx}) notified of event at {device_name}")
    except:
        print(f"{ts()}: Error switching to domoticz : '{domoticz_api}/json.htm?type=command&param=switchlight&idx={idx}&switchcmd=On'.")

def main():
    myring, doorbells = authenticate_and_initialize(gadget_type="doorbells")

    if myring and doorbells:
        print(f"{ts()}: Entering observation loop - polling interval set to {poll_interval}s\n")
    while True:

        myring.update_dings()
        alerts = myring.active_alerts()

        if alerts:
            for alert in alerts:
                try:
                    this_event_id = alert['id']
                    this_device_name = alert['doorbot_description']
                    this_event_type = alert['kind']
                    print(f"{ts()}: {this_device_name} '{this_event_type}' event was triggered (id: {this_event_id})")
                    if this_event_type in "dingmotion":
                      notify_domoticz(this_device_name, this_event_type)

                except:
                    time.sleep(poll_interval)
                    continue

                time.sleep(poll_interval)

if __name__ == "__main__":
    main()

robgeerts
Posts: 1273
Joined: Saturday 24 January 2015 22:12
Target OS: NAS (Synology & others)
Domoticz version: 3.7067
Location: NL
Contact:

Re: Ring 2 Doorbell

Post by robgeerts »

Nice, thanks Korrel, will test asap!
pvdgulik
Posts: 21
Joined: Friday 28 December 2018 20:23
Target OS: Raspberry Pi / ODroid
Domoticz version: 2023.1
Contact:

Re: Ring 2 Doorbell

Post by pvdgulik »

Hello, interested to get this working. Have to questions:

First, how to get my_own_unique_API_name: Followed this but still don't have a clue:
" 7) If you are getting missing_token errors, you must use your own unique API name and version (see this post: https://github.com/tchellomello/python- ... -874319220 ). Ring seems to be flagging the default value and blocking it."

Second, is it possible to run this script in Domoticz-scripts?

Thanks!
User avatar
Egregius
Posts: 2589
Joined: Thursday 09 April 2015 12:19
Target OS: Linux
Domoticz version: v2024.7
Location: Beitem, BE
Contact:

Re: Ring 2 Doorbell

Post by Egregius »

First one is easy. "ThisCouldBeYourApiName1.0" or anything you can imagen.
Second one, no, it's not a domoticz script. It runs by it's own.
Jerby
Posts: 2
Joined: Wednesday 27 September 2017 16:05
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Ring 2 Doorbell

Post by Jerby »

Does anyone has the last script working? I still get only Motion events in Domoticz and no downloaded video's in the rPi directory.
What code is required to get a snapshot when Motion or Ding is detected?
Korrel
Posts: 9
Joined: Friday 31 January 2014 11:02
Target OS: Linux
Domoticz version:
Contact:

Re: Ring 2 Doorbell

Post by Korrel »

Jerby wrote: Saturday 22 July 2023 22:06 Does anyone has the last script working? I still get only Motion events in Domoticz and no downloaded video's in the rPi directory.
What code is required to get a snapshot when Motion or Ding is detected?
Several things have changed in the authorisation(-token) ....

See my code for a working ding implententation (please upgrade api to latest)

Code: Select all

import json, os
from pathlib import Path
from ring_doorbell import Ring, Auth
from oauthlib.oauth2 import MissingTokenError
import time
from datetime import datetime, timezone
import urllib.request as urllib
from threading import Thread

######### This Section Needs Edited By You - unique_api_name as well! ###########################
your_unique_api_name = 'PythonMonitorAPI/0.1.1'
user_agent = "Myproject_ringdoor_1.0"  # Change this
cache_file = Path("/home/pi/domoticz/scripts/ring/" + user_agent + ".token.cache")
videopath = '/mnt/documents/ring/captures/'
poll_interval = 1.0  # interval in seconds that we poll the Ring API for new events
# Domoticz setup
domoticz_api = 'http://192.168.x.x:8080'
domoticz_idx_mapping_ding = {"Voordeur": 6711, "Back Door": 6711, "Recording": 11492}
domoticz_idx_mapping_motion = {"Voordeur": 11231, "Back Door": 11231, "Recording": 11492}
#################################################################################################

def authenticate():
    if cache_file.is_file():
        auth = Auth(user_agent, json.loads(cache_file.read_text()), token_updated)
        myring = Ring(auth)
        try:
            myring.create_session()  # auth token still valid
        except AuthenticationError:  # auth token has expired
            auth = do_auth()
    else:
        auth = do_auth()  # Get new auth token
    auth = Ring(auth)
    return auth

def authenticate_and_initialize(gadget_type: str = "doorbells"):
    myring = authenticate()
    myring.update_data()
    devices = myring.devices()

    if gadget_type == "doorbells":
        doorbells = devices['doorbots']
        enumerate_doorbells(doorbells)

        return myring, doorbells

def ts(filename_format: bool = False, dirname_format: bool = False):
    if dirname_format:
        return f"{datetime.now().strftime('%Y-%m-%d')}/"
    if filename_format:
        return datetime.now().strftime('%H-%M-%S')
    else:
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')


def construct_local_filename(triggered_device, device_name: str, last_event_type: str, newest_event_id: int):
    filename_timestamp = triggered_device.history(limit=1, enforce_limit=True)[0]['created_at'].replace(
        tzinfo=timezone.utc).astimezone(tz=None).strftime('%H-%M-%S')

    local_filename = f"{videopath}{daily_directory()}{device_name.replace(' ', '')}_{last_event_type}_" \
                     f"{filename_timestamp}_{str(newest_event_id)[-4:]}.mp4"

    return local_filename


def daily_directory():
    daily_directory_path = videopath + ts(dirname_format=True)

    if os.path.isdir(daily_directory_path):
        return ts(dirname_format=True)
    else:
        os.mkdir(daily_directory_path)
        print(f"{ts()}: Created new daily directory at {daily_directory_path}")
        return ts(dirname_format=True)


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


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


def enumerate_doorbells(doorbells):
    for doorbell in doorbells:
        print(f"{ts()}: Authentication and discovery successful for {doorbell}.")


def notify_domoticz(device_name: str, this_event_type: str):
    try:
        if this_event_type == "ding":
          idx = domoticz_idx_mapping_ding.get(device_name)
        if this_event_type == "motion":
          idx = domoticz_idx_mapping_motion.get(device_name)
        urllib.urlopen(f'{domoticz_api}/json.htm?type=command&param=switchlight&idx={idx}&switchcmd=On')
        print(f"{ts()}: Domoticz (idx:{idx}) notified of event at {device_name}")
    except:
        print(f"{ts()}: Error switching to domoticz : '{domoticz_api}/json.htm?type=command&param=switchlight&idx={idx}&switchcmd=On'.")


def get_device_by_id(this_device_id: str, doorbells):
    try:
        this_device = next((doorbell for doorbell in doorbells if doorbell.id == this_device_id), None)
        return this_device
    except:
        return 0


def main():
    myring, doorbells = authenticate_and_initialize(gadget_type="doorbells")

    if myring and doorbells:
        print(f"{ts()}: Entering observation loop - polling interval set to {poll_interval}s\n")

    last_event_id = 0

    while True:

        myring.update_dings()
        alerts = myring.active_alerts()
        if alerts:
            for alert in alerts:
                try:
                    if alert['kind'] == 'ding':
                    	this_event_id = alert['id']
#                   	 print(f"{ts()}: EVENT (last-id: {last_event_id}) -  (this-id: {this_event_id})")
                    	if this_event_id != last_event_id:
                        	last_event_id = this_event_id
                        	this_device = get_device_by_id(alert['doorbot_id'], doorbells)
	                        this_device_name = this_device.name #alert['doorbot_description'] werkt niet meer
                        	this_event_type = alert['kind']
                        	print(f"{ts()}: {this_device_name} '{this_event_type}' event was triggered (id: {this_event_id})")
                        	notify_domoticz(this_device_name, this_event_type)
                except:
                    time.sleep(poll_interval)
                    continue

            time.sleep(poll_interval)


if __name__ == "__main__":
    main()
    
I've split alert kind to different python scripts to run as independant services... At some point you will experience timeout... Implementing pythons scripts as service with restart option will keep everything running ....
bldewit
Posts: 59
Joined: Tuesday 09 May 2017 23:31
Target OS: Raspberry Pi / ODroid
Domoticz version: Stable
Location: NL
Contact:

Re: Ring 2 Doorbell

Post by bldewit »

I keep getting the error

Python[4648]: /User/bin/Python: can't find '__main__' module in '/home/pi/domoticz/scripts/python'

What could be the solution?
-‐----------------------------------------------------------
Running on rPi 3b (main) and rPi 2b (somfy), both RFLink
IthoRemote, Xiaomi Gateway, KAKU
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests