Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor cleanups and some new example programs. #67

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
34 changes: 34 additions & 0 deletions examples/get_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import reolinkapi
import json

if __name__ == "__main__":
hostname = "watchdog1"

try:
cam = reolinkapi.Camera(hostname, username="userid", password="passwd")
except:
print(f"Failed to open camera {hostname}")
exit(1)

if not cam.is_logged_in():
print(f"Login failed for {hostname}")
exit(1)


print( json.dumps( cam.get_information(), indent=4 ) )
print( json.dumps( cam.get_network_general(), indent=4 ) )
print( json.dumps( cam.get_network_ddns(), indent=4 ) )
print( json.dumps( cam.get_network_ntp(), indent=4 ) )
print( json.dumps( cam.get_network_email(), indent=4 ) )
print( json.dumps( cam.get_network_ftp(), indent=4 ) )
print( json.dumps( cam.get_network_push(), indent=4 ) )
print( json.dumps( cam.get_network_status(), indent=4 ) )
print( json.dumps( cam.get_recording_encoding(), indent=4 ) )
print( json.dumps( cam.get_recording_advanced(), indent=4 ) )
print( json.dumps( cam.get_general_system(), indent=4 ) )
print( json.dumps( cam.get_performance(), indent=4 ) )
print( json.dumps( cam.get_dst(), indent=4 ) )
print( json.dumps( cam.get_online_user(), indent=4 ) )
print( json.dumps( cam.get_users(), indent=4 ) )


44 changes: 44 additions & 0 deletions examples/set_ftp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/opt/homebrew/bin/python3

import reolinkapi
import os
import json
from sys import argv

def ftp_cam(which):
hostname = 'watchdog%d' % which
cam_name = 'Watchdog%d' % which

try:
cam = reolinkapi.Camera(hostname, username="userid", password="passwd")
except:
print(f"Failed to open camera {cam_name}")
return 1

if not cam.is_logged_in():
print(f"Login failed for {cam_name}")
return 2

try:
print(f"Setting FTP params for {cam_name}")
response = cam.set_network_ftp("hocus", "pocus", "directory", "192.168.1.2", 0)
print( json.dumps( response, indent=4 ) )
print( json.dumps( cam.get_network_ftp(), indent=4 ) )
if response[0]["value"]["rspCode"] == 200:
print("Success!")
except Exception as e:
print(f"{argv[0]} for {cam_name} failed.")
print(e)
return 3

return 0


if __name__ == "__main__":
if len(argv) != 2:
print("Usage: %s <cam#>]" % argv[0])
exit(1)

exit( ftp_cam(int(argv[1])) )


41 changes: 41 additions & 0 deletions examples/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/opt/homebrew/bin/python3

import reolinkapi
import os
from sys import argv

def snap_cam(which, dirname):
hostname = 'watchdog%d' % which
cam_name = 'Watchdog%d' % which

try:
cam = reolinkapi.Camera(hostname, username="rtsp", password="darknet")
except:
print(f"Failed to open camera {cam_name}")
return 1

if not cam.is_logged_in():
print(f"Login failed for {cam_name}")
return 2

image_file = '%s/%s.jpg' % (dirname, hostname)
if os.path.exists(image_file):
os.unlink(image_file)

print("Snapping", cam_name)
image = cam.get_snap()
image.save(image_file)
return 0


if __name__ == "__main__":
dirname = '.'
if len(argv) == 3:
dirname = argv[2]
elif len(argv) != 2:
print("Usage: %s <cam#> [output-directory]" % argv[0])
exit(1)

exit( snap_cam(int(argv[1]), dirname) )


114 changes: 72 additions & 42 deletions examples/streaming_video.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,54 +1,65 @@
#!/opt/homebrew/bin/python3

import cv2
from queue import Queue
from sys import argv
from reolinkapi import Camera

_resize = 1024

def display_frame(cam, frame, count):
if frame is not None:
print("Frame %5d" % count, end='\r')
cv2.imshow(cam.ip, frame)

key = cv2.waitKey(1)
if key == ord('q') or key == ord('Q') or key == 27:
cam.stop_stream()
cv2.destroyAllWindows()
return False

return True

def non_blocking():
print("calling non-blocking")
def non_blocking(cam):
frames = Queue(maxsize=30)

def inner_callback(img):
cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
print("got the image non-blocking")
key = cv2.waitKey(1)
if key == ord('q'):
cv2.destroyAllWindows()
exit(1)
def inner_callback(frame):
if _resize > 10:
frames.put(maintain_aspect_ratio_resize(frame, width=_resize))
else:
frames.put(frame.copy())

c = Camera("192.168.1.112", "admin", "jUa2kUzi")
# t in this case is a thread
t = c.open_video_stream(callback=inner_callback)
t = cam.open_video_stream(callback=inner_callback)

print(t.is_alive())
while True:
if not t.is_alive():
print("continuing")
counter = 0

while t.is_alive():
frame = frames.get()
counter = counter+1

if not display_frame(cam, frame, counter):
break
# stop the stream
# client.stop_stream()

if __debug__ and frames.qsize() > 1:
# Can't consume frames fast enough??
print("\nQueue: %d" % frames.qsize())



def blocking():
c = Camera("192.168.1.112", "admin", "jUa2kUzi")
def blocking(cam):
# stream in this case is a generator returning an image (in mat format)
stream = c.open_video_stream()

# using next()
# while True:
# img = next(stream)
# cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
# print("got the image blocking")
# key = cv2.waitKey(1)
# if key == ord('q'):
# cv2.destroyAllWindows()
# exit(1)

# or using a for loop
for img in stream:
cv2.imshow("name", maintain_aspect_ratio_resize(img, width=600))
print("got the image blocking")
key = cv2.waitKey(1)
if key == ord('q'):
cv2.destroyAllWindows()
exit(1)
stream = cam.open_video_stream()
counter = 0

for frame in stream:
if _resize > 10:
frame = maintain_aspect_ratio_resize(frame, width=_resize)

counter = counter + 1

if not display_frame(cam, frame, counter):
break;


# Resizes a image and maintains aspect ratio
Expand Down Expand Up @@ -76,6 +87,25 @@ def maintain_aspect_ratio_resize(image, width=None, height=None, inter=cv2.INTER
return cv2.resize(image, dim, interpolation=inter)


# Call the methods. Either Blocking (using generator) or Non-Blocking using threads
# non_blocking()
blocking()

if __name__ == "__main__":
if len(argv) != 2:
print(f"Usage: {argv[0]} <Camera #>")
exit(1)

try:
host = f"watchdog{argv[1]}"
cam = Camera(host, username="rtsp", password="darknet")
except:
print(f"Failed to open camera: {host}")
exit(1)

if not cam.is_logged_in():
print(f"Login failed for {host}")
exit(1)

# Call the methods. Either Blocking (using generator) or Non-Blocking using threads
non_blocking(cam)
# blocking(cam)

print("\nDone.")
39 changes: 31 additions & 8 deletions reolinkapi/handlers/api_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import requests
from urllib3.exceptions import InsecureRequestWarning

from typing import Dict, List, Optional, Union
from reolinkapi.mixins.alarm import AlarmAPIMixin
from reolinkapi.mixins.device import DeviceAPIMixin
Expand Down Expand Up @@ -53,6 +55,8 @@ def __init__(self, ip: str, username: str, password: str, https: bool = False, *
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
self.ip = ip
self.token = None
self.ability = None
self.scheduleVersion = 0
self.username = username
self.password = password
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
Expand All @@ -64,28 +68,47 @@ def login(self) -> bool:
:return: bool
"""
try:
# Suppress only the single warning from urllib3 needed.
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make this optional?

or what about just leaving this to the client to set this as an env variable

PYTHONWARNINGS="ignore:Unverified HTTPS request"


body = [{"cmd": "Login", "action": 0,
"param": {"User": {"userName": self.username, "password": self.password}}}]
param = {"cmd": "Login", "token": "null"}
response = Request.post(self.url, data=body, params=param)
if response is not None:
# print("LOGIN GOT: ", response.text)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# print("LOGIN GOT: ", response.text)

data = response.json()[0]
code = data["code"]
if int(code) == 0:
self.token = data["value"]["Token"]["name"]
print("Login success")
return True
print(self.token)
return False
# print("Login success")
else:
# print(self.token)
Comment on lines +84 to +86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the comments or restore the print of "Login success?"

print("ERROR: LOGIN RESPONSE: ", response.text)
return False
else:
# TODO: Verify this change w/ owner. Delete old code if acceptable.
# A this point, response is NoneType. There won't be a status code property.
# print("Failed to login\nStatus Code:", response.status_code)
print("Failed to login\nResponse was null.")
return False
except Exception as e:
print("Error Login\n", e)
raise
print(f"ERROR Login Failed, exception: {e}")
return False

try:
ability = self.get_ability()
self.ability = ability[0]["value"]["Ability"]
self.scheduleVersion = self.ability["scheduleVersion"]["ver"]
print("API VERSION: ", self.scheduleVersion)
except Exception as e:
self.logout()
return False

return True

def is_logged_in(self) -> bool:
return self.token is not None

def logout(self) -> bool:
"""
Expand All @@ -98,7 +121,7 @@ def logout(self) -> bool:
# print(ret)
return True
except Exception as e:
print("Error Logout\n", e)
print(f"ERROR Logout Failed, exception: {e}")
return False

def _execute_command(self, command: str, data: List[Dict], multi: bool = False) -> \
Expand Down Expand Up @@ -137,5 +160,5 @@ def _execute_command(self, command: str, data: List[Dict], multi: bool = False)
response = Request.post(self.url, data=data, params=params)
return response.json()
except Exception as e:
print(f"Command {command} failed: {e}")
print(f"ERROR Command {command} failed, exception: {e}")
raise
15 changes: 13 additions & 2 deletions reolinkapi/mixins/alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@
class AlarmAPIMixin:
"""API calls for getting device alarm information."""

def get_alarm(self) -> Dict:
"""
Gets the device alarm motion
See examples/response/GetAlarmMotion.json for example response data.
:return: response json
"""
cmd = "GetAlarm"
body = [{"cmd": cmd, "action": 1, "param": {"Alarm": {"channel": 0, "type": "md"}}}]
return self._execute_command(cmd, body)

def get_alarm_motion(self) -> Dict:
"""
Gets the device alarm motion
See examples/response/GetAlarmMotion.json for example response data.
:return: response json
"""
body = [{"cmd": "GetAlarm", "action": 1, "param": {"Alarm": {"channel": 0, "type": "md"}}}]
return self._execute_command('GetAlarm', body)
cmd = "GetMdAlarm"
body = [{"cmd": cmd, "action": 1, "param": {"channel": 0}}]
return self._execute_command(cmd, body)
Loading