Skip to content

Commit 5dfe2a6

Browse files
author
Max Ziermann
committed
Merge branch 'Benehiko-master'
2 parents a2c44f8 + 2124725 commit 5dfe2a6

22 files changed

+1133
-924
lines changed

APIHandler.py

Lines changed: 0 additions & 917 deletions
This file was deleted.

Camera.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from APIHandler import APIHandler
1+
from api import APIHandler
22

33

44
class Camera(APIHandler):
@@ -11,8 +11,9 @@ def __init__(self, ip, username="admin", password="", https=False):
1111
:param username:
1212
:param password:
1313
"""
14-
# For when you need to connect to a camera behind a proxy
15-
APIHandler.__init__(self, ip, username, password, proxy={"http": "socks5://127.0.0.1:8000"}, https=https)
14+
# For when you need to connect to a camera behind a proxy, pass
15+
# a proxy argument: proxy={"http": "socks5://127.0.0.1:8000"}
16+
APIHandler.__init__(self, ip, username, password, https=https)
1617

1718
# Normal call without proxy:
1819
# APIHandler.__init__(self, ip, username, password)

Pipfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[[source]]
2+
name = "pypi"
3+
url = "https://pypi.org/simple"
4+
verify_ssl = true
5+
6+
[dev-packages]
7+
8+
[packages]
9+
pillow = "*"
10+
pyyaml = "*"
11+
requests = "*"
12+
13+
[requires]

Pipfile.lock

Lines changed: 102 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/APIHandler.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from api.recording import RecordingAPIMixin
2+
from .device import DeviceAPIMixin
3+
from .display import DisplayAPIMixin
4+
from .network import NetworkAPIMixin
5+
from .system import SystemAPIMixin
6+
from .user import UserAPIMixin
7+
from resthandle import Request
8+
9+
10+
class APIHandler(SystemAPIMixin,
11+
NetworkAPIMixin,
12+
UserAPIMixin,
13+
DeviceAPIMixin,
14+
DisplayAPIMixin,
15+
RecordingAPIMixin):
16+
"""
17+
The APIHandler class is the backend part of the API, the actual API calls
18+
are implemented in Mixins.
19+
This handles communication directly with the camera.
20+
Current camera's tested: RLC-411WS
21+
22+
All Code will try to follow the PEP 8 standard as described here: https://www.python.org/dev/peps/pep-0008/
23+
"""
24+
25+
def __init__(self, ip: str, username: str, password: str, https=False, **kwargs):
26+
"""
27+
Initialise the Camera API Handler (maps api calls into python)
28+
:param ip:
29+
:param username:
30+
:param password:
31+
:param proxy: Add a proxy dict for requests to consume.
32+
eg: {"http":"socks5://[username]:[password]@[host]:[port], "https": ...}
33+
More information on proxies in requests: https://stackoverflow.com/a/15661226/9313679
34+
"""
35+
scheme = 'https' if https else 'http'
36+
self.url = f"{scheme}://{ip}/cgi-bin/api.cgi"
37+
self.ip = ip
38+
self.token = None
39+
self.username = username
40+
self.password = password
41+
Request.proxies = kwargs.get("proxy") # Defaults to None if key isn't found
42+
43+
def login(self) -> bool:
44+
"""
45+
Get login token
46+
Must be called first, before any other operation can be performed
47+
:return: bool
48+
"""
49+
try:
50+
body = [{"cmd": "Login", "action": 0,
51+
"param": {"User": {"userName": self.username, "password": self.password}}}]
52+
param = {"cmd": "Login", "token": "null"}
53+
response = Request.post(self.url, data=body, params=param)
54+
if response is not None:
55+
data = response.json()[0]
56+
code = data["code"]
57+
if int(code) == 0:
58+
self.token = data["value"]["Token"]["name"]
59+
print("Login success")
60+
return True
61+
print(self.token)
62+
return False
63+
else:
64+
print("Failed to login\nStatus Code:", response.status_code)
65+
return False
66+
except Exception as e:
67+
print("Error Login\n", e)
68+
raise
69+
70+
def _execute_command(self, command, data, multi=False):
71+
"""
72+
Send a POST request to the IP camera with given data.
73+
:param command: name of the command to send
74+
:param data: object to send to the camera (send as json)
75+
:param multi: whether the given command name should be added to the
76+
url parameters of the request. Defaults to False. (Some multi-step
77+
commands seem to not have a single command name)
78+
:return: response JSON as python object
79+
"""
80+
params = {"token": self.token, 'cmd': command}
81+
if multi:
82+
del params['cmd']
83+
try:
84+
if self.token is None:
85+
raise ValueError("Login first")
86+
response = Request.post(self.url, data=data, params=params)
87+
return response.json()
88+
except Exception as e:
89+
print(f"Command {command} failed: {e}")
90+
raise

api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .APIHandler import APIHandler

api/device.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class DeviceAPIMixin:
2+
"""API calls for getting device information."""
3+
def get_hdd_info(self) -> object:
4+
"""
5+
Gets all HDD and SD card information from Camera
6+
See examples/response/GetHddInfo.json for example response data.
7+
:return: response json
8+
"""
9+
body = [{"cmd": "GetHddInfo", "action": 0, "param": {}}]
10+
return self._execute_command('GetHddInfo', body)
11+
12+
def format_hdd(self, hdd_id: [int] = [0]) -> bool:
13+
"""
14+
Format specified HDD/SD cards with their id's
15+
:param hdd_id: List of id's specified by the camera with get_hdd_info api. Default is 0 (SD card)
16+
:return: bool
17+
"""
18+
body = [{"cmd": "Format", "action": 0, "param": {"HddInfo": {"id": hdd_id}}}]
19+
r_data = self._execute_command('Format', body)[0]
20+
if r_data["value"]["rspCode"] == 200:
21+
return True
22+
print("Could not format HDD/SD. Camera responded with:", r_data["value"])
23+
return False

api/display.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
class DisplayAPIMixin:
2+
"""API calls related to the current image (osd, on screen display)."""
3+
4+
def get_osd(self) -> object:
5+
"""
6+
Get OSD information.
7+
See examples/response/GetOsd.json for example response data.
8+
:return: response json
9+
"""
10+
body = [{"cmd": "GetOsd", "action": 1, "param": {"channel": 0}}]
11+
return self._execute_command('GetOsd', body)
12+
13+
def get_mask(self) -> object:
14+
"""
15+
Get the camera mask information.
16+
See examples/response/GetMask.json for example response data.
17+
:return: response json
18+
"""
19+
body = [{"cmd": "GetMask", "action": 1, "param": {"channel": 0}}]
20+
return self._execute_command('GetMask', body)
21+
22+
def set_osd(self, bg_color: bool = 0, channel: int = 0, osd_channel_enabled: bool = 0, osd_channel_name: str = "",
23+
osd_channel_pos: str = "Lower Right", osd_time_enabled: bool = 0,
24+
osd_time_pos: str = "Lower Right") -> bool:
25+
"""
26+
Set OSD
27+
:param bg_color: bool
28+
:param channel: int channel id
29+
:param osd_channel_enabled: bool
30+
:param osd_channel_name: string channel name
31+
:param osd_channel_pos: string channel position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
32+
:param osd_time_enabled: bool
33+
:param osd_time_pos: string time position ["Upper Left","Top Center","Upper Right","Lower Left","Bottom Center","Lower Right"]
34+
:return: whether the action was successful
35+
"""
36+
body = [{"cmd": "SetOsd", "action": 1, "param": {
37+
"Osd": {"bgcolor": bg_color, "channel": channel,
38+
"osdChannel": {"enable": osd_channel_enabled, "name": osd_channel_name,
39+
"pos": osd_channel_pos},
40+
"osdTime": {"enable": osd_time_enabled, "pos": osd_time_pos}
41+
}
42+
}}]
43+
r_data = self._execute_command('SetOsd', body)[0]
44+
if r_data["value"]["rspCode"] == 200:
45+
return True
46+
print("Could not set OSD. Camera responded with status:", r_data["value"])
47+
return False

api/network.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
class NetworkAPIMixin:
2+
"""API calls for network settings."""
3+
def set_net_port(self, http_port=80, https_port=443, media_port=9000, onvif_port=8000, rtmp_port=1935,
4+
rtsp_port=554) -> bool:
5+
"""
6+
Set network ports
7+
If nothing is specified, the default values will be used
8+
:param rtsp_port: int
9+
:param rtmp_port: int
10+
:param onvif_port: int
11+
:param media_port: int
12+
:param https_port: int
13+
:type http_port: int
14+
:return: bool
15+
"""
16+
body = [{"cmd": "SetNetPort", "action": 0, "param": {"NetPort": {
17+
"httpPort": http_port,
18+
"httpsPort": https_port,
19+
"mediaPort": media_port,
20+
"onvifPort": onvif_port,
21+
"rtmpPort": rtmp_port,
22+
"rtspPort": rtsp_port
23+
}}}]
24+
self._execute_command('SetNetPort', body, multi=True)
25+
print("Successfully Set Network Ports")
26+
return True
27+
28+
def set_wifi(self, ssid, password) -> object:
29+
body = [{"cmd": "SetWifi", "action": 0, "param": {
30+
"Wifi": {
31+
"ssid": ssid,
32+
"password": password
33+
}}}]
34+
return self._execute_command('SetWifi', body)
35+
36+
def get_net_ports(self) -> object:
37+
"""
38+
Get network ports
39+
:return: response json
40+
"""
41+
body = [{"cmd": "GetNetPort", "action": 1, "param": {}},
42+
{"cmd": "GetUpnp", "action": 0, "param": {}},
43+
{"cmd": "GetP2p", "action": 0, "param": {}}]
44+
return self._execute_command('GetNetPort', body, multi=True)
45+
46+
def get_wifi(self):
47+
body = [{"cmd": "GetWifi", "action": 1, "param": {}}]
48+
return self._execute_command('GetWifi', body)
49+
50+
def scan_wifi(self):
51+
body = [{"cmd": "ScanWifi", "action": 1, "param": {}}]
52+
return self._execute_command('ScanWifi', body)

0 commit comments

Comments
 (0)