├── setup.cfg ├── requirements.txt ├── sensecam_control ├── __init__.py ├── onvif_control.py ├── vapix_control.py ├── onvif_config.py └── vapix_config.py ├── setup.py ├── LICENSE.txt ├── example.py ├── example_control_vapix.py ├── example_control_onvif.py └── README.md /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | license_file = LICENSE.txt -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | urllib3==1.25.7 2 | requests==2.22.0 3 | beautifulsoup4==4.8.1 4 | onvif-zeep==0.2.12 -------------------------------------------------------------------------------- /sensecam_control/__init__.py: -------------------------------------------------------------------------------- 1 | from .vapix_control import * 2 | from .vapix_config import * 3 | from .onvif_config import * 4 | from .onvif_config import * 5 | 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | REQUIREMENTS = [line for line in open('requirements.txt').read().split('\n') if line != ''] 4 | 5 | VERSION = '0.1.0' 6 | AUTHOR = 'Igor Dias' 7 | EMAIL = 'igorhenriquedias94@gmail.com' 8 | 9 | setuptools.setup( 10 | name='sensecam_control', 11 | version=VERSION, 12 | author=AUTHOR, 13 | author_email=EMAIL, 14 | license='MIT', 15 | description='Implementation of python functions for control and configuration of Axis cameras using Vapix/Onvif.', 16 | long_description=open('README.md').read(), 17 | long_description_content_type="text/markdown", 18 | packages=setuptools.find_packages(), 19 | url="https://github.com/smartsenselab/sensecam-control", 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "License :: OSI Approved :: MIT License", 23 | "Operating System :: OS Independent", 24 | ], 25 | keywords=['ONVIF', 'vapix', 'camera'], 26 | python_requires='>=3.6', 27 | install_requires=REQUIREMENTS, 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Igor Dias 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import sys 3 | import threading 4 | from sensecam_control import vapix_control 5 | 6 | 7 | ip = '000.000.000.000' 8 | login = 'login' 9 | password = 'password' 10 | 11 | exit_program = 0 12 | 13 | 14 | def event_keyboard(k): 15 | global exit_program 16 | 17 | if k == 27: # esc 18 | exit_program = 1 19 | 20 | elif k == ord('w') or k == ord('W'): 21 | X.relative_move(None, 1, None) 22 | 23 | elif k == ord('a') or k == ord('A'): 24 | X.relative_move(-1, None, None) 25 | 26 | elif k == ord('s') or k == ord('S'): 27 | X.relative_move(None, -1, None) 28 | 29 | elif k == ord('d') or k == ord('D'): 30 | X.relative_move(1, None, None) 31 | 32 | elif k == ord('h') or k == ord('H'): 33 | X.go_home_position() 34 | 35 | 36 | def capture(ip_camera): 37 | global exit_program 38 | 39 | #url http login axis camera 40 | ip2 = 'http://' + login + ':' + password + '@' + ip_camera + '/mjpg/1/video.mjpg?' 41 | 42 | #url rtsp axis camera 43 | #ip2 = 'rtsp://' + login + ':' + password + '@' + ip_camera + '/axis-media/media.amp' 44 | 45 | cap = cv2.VideoCapture(ip2) 46 | 47 | while True: 48 | ret, frame = cap.read() 49 | if ret is not False: 50 | break 51 | 52 | while True: 53 | ret, frame = cap.read() 54 | 55 | if exit_program == 1: 56 | sys.exit() 57 | 58 | #cv2.namedWindow('Camera', cv2.WINDOW_NORMAL) 59 | cv2.imshow('Camera', frame) 60 | event_keyboard(cv2.waitKey(1) & 0xff) 61 | 62 | 63 | X = vapix_control.CameraControl(ip, login, password) 64 | t = threading.Thread(target=capture, args=(ip,)) 65 | t.start() 66 | -------------------------------------------------------------------------------- /example_control_vapix.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import sys 3 | import threading 4 | from sensecam_control import vapix_control 5 | 6 | 7 | ip = '000.000.000.000' 8 | login = 'login' 9 | password = 'password' 10 | 11 | exit_program = 0 12 | 13 | 14 | def event_keyboard(k): 15 | global exit_program 16 | 17 | if k == 27: # esc 18 | exit_program = 1 19 | 20 | elif k == ord('w') or k == ord('W'): 21 | X.relative_move(None, 1, None) 22 | 23 | elif k == ord('a') or k == ord('A'): 24 | X.relative_move(-1, None, None) 25 | 26 | elif k == ord('s') or k == ord('S'): 27 | X.relative_move(None, -1, None) 28 | 29 | elif k == ord('d') or k == ord('D'): 30 | X.relative_move(1, None, None) 31 | 32 | elif k == ord('h') or k == ord('H'): 33 | X.go_home_position() 34 | 35 | 36 | def capture(ip_camera): 37 | global exit_program 38 | 39 | #url http login axis camera 40 | ip2 = 'http://' + login + ':' + password + '@' + ip_camera + '/mjpg/1/video.mjpg?' 41 | 42 | #url rtsp axis camera 43 | #ip2 = 'rtsp://' + login + ':' + password + '@' + ip_camera + '/axis-media/media.amp' 44 | 45 | cap = cv2.VideoCapture(ip2) 46 | 47 | while True: 48 | ret, frame = cap.read() 49 | if ret is not False: 50 | break 51 | 52 | while True: 53 | ret, frame = cap.read() 54 | 55 | if exit_program == 1: 56 | sys.exit() 57 | 58 | #cv2.namedWindow('Camera', cv2.WINDOW_NORMAL) 59 | cv2.imshow('Camera', frame) 60 | event_keyboard(cv2.waitKey(1) & 0xff) 61 | 62 | 63 | X = vapix_control.CameraControl(ip, login, password) 64 | 65 | t = threading.Thread(target=capture, args=(ip,)) 66 | t.start() 67 | -------------------------------------------------------------------------------- /example_control_onvif.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import sys 3 | import threading 4 | from sensecam_control import onvif_control 5 | 6 | 7 | ip = '000.000.000.000' 8 | login = 'login' 9 | password = 'password' 10 | 11 | exit_program = 0 12 | 13 | 14 | def event_keyboard(k): 15 | global exit_program 16 | 17 | if k == 27: # esc 18 | exit_program = 1 19 | 20 | elif k == ord('w') or k == ord('W'): 21 | X.relative_move(0, 0.1, 0) 22 | 23 | elif k == ord('a') or k == ord('A'): 24 | X.relative_move(-0.1, 0, 0) 25 | 26 | elif k == ord('s') or k == ord('S'): 27 | X.relative_move(0, -0.1, 0) 28 | 29 | elif k == ord('d') or k == ord('D'): 30 | X.relative_move(0.1, 0, 0) 31 | 32 | elif k == ord('h') or k == ord('H'): 33 | X.go_home_position() 34 | 35 | elif k == ord('z') or k == ord('Z'): 36 | X.relative_move(0, 0, 0.05) 37 | 38 | elif k == ord('x') or k == ord('X'): 39 | X.relative_move(0, 0, -0.05) 40 | 41 | 42 | def capture(ip_camera): 43 | global exit_program 44 | 45 | #url http login axis camera 46 | ip2 = 'http://' + login + ':' + password + '@' + ip_camera + '/mjpg/1/video.mjpg?' 47 | 48 | #url rtsp axis camera 49 | #ip2 = 'rtsp://' + login + ':' + password + '@' + ip_camera + '/axis-media/media.amp' 50 | 51 | cap = cv2.VideoCapture(ip2) 52 | 53 | while True: 54 | ret, frame = cap.read() 55 | if ret is not False: 56 | break 57 | 58 | while True: 59 | ret, frame = cap.read() 60 | 61 | if exit_program == 1: 62 | sys.exit() 63 | 64 | #cv2.namedWindow('Camera', cv2.WINDOW_NORMAL) 65 | cv2.imshow('Camera', frame) 66 | event_keyboard(cv2.waitKey(1) & 0xff) 67 | 68 | 69 | X = onvif_control.CameraControl(ip, login, password) 70 | X.camera_start() 71 | 72 | t = threading.Thread(target=capture, args=(ip,)) 73 | t.start() 74 | -------------------------------------------------------------------------------- /sensecam_control/onvif_control.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library for control AXIS PTZ cameras using Onvif 3 | """ 4 | import logging 5 | from onvif import ONVIFCamera 6 | 7 | logging.basicConfig(filename='teste-onvif.log', filemode='w', level=logging.DEBUG) 8 | logging.info('Started') 9 | 10 | #pylint: disable=R0904 11 | class CameraControl: 12 | """ 13 | Module for control cameras AXIS using Onvif 14 | """ 15 | 16 | def __init__(self, ip, user, password): 17 | self.__cam_ip = ip 18 | self.__cam_user = user 19 | self.__cam_password = password 20 | 21 | @staticmethod 22 | def _map_onvif_to_vapix(value, min_onvif, max_onvif, min_vapix, max_vapix): 23 | return (value - min_onvif) * (max_vapix - min_vapix) / (max_onvif - min_onvif) + min_vapix 24 | 25 | @staticmethod 26 | def _map_vapix_to_onvif(value, min_vapix, max_vapix, min_onvif, max_onvif): 27 | return (value - min_vapix) * (max_onvif - min_onvif) / (max_vapix - min_vapix) + min_onvif 28 | 29 | def camera_start(self): 30 | """ 31 | Creates the connection to the camera using the onvif protocol 32 | 33 | Returns: 34 | Return the ptz service object and media service object 35 | """ 36 | mycam = ONVIFCamera(self.__cam_ip, 80, self.__cam_user, self.__cam_password) 37 | logging.info('Create media service object') 38 | media = mycam.create_media_service() 39 | logging.info('Create ptz service object') 40 | ptz = mycam.create_ptz_service() 41 | logging.info('Get target profile') 42 | media_profile = media.GetProfiles()[0] 43 | logging.info('Camera working!') 44 | 45 | self.mycam = mycam 46 | self.camera_ptz = ptz 47 | self.camera_media_profile = media_profile 48 | self.camera_media = media 49 | 50 | return self.camera_ptz, self.camera_media_profile 51 | 52 | def absolute_move(self, pan: float, tilt: float, zoom: float): 53 | """ 54 | Operation to move pan, tilt or zoom to a absolute destination. 55 | 56 | Args: 57 | pan: Pans the device relative to the (0,0) position. 58 | tilt: Tilts the device relative to the (0,0) position. 59 | zoom: Zooms the device n steps. 60 | 61 | Returns: 62 | Return onvif's response 63 | """ 64 | request = self.camera_ptz.create_type('AbsoluteMove') 65 | request.ProfileToken = self.camera_media_profile.token 66 | request.Position = {'PanTilt': {'x': pan, 'y': tilt}, 'Zoom': zoom} 67 | resp = self.camera_ptz.AbsoluteMove(request) 68 | logging.info('camera_command( aboslute_move(%f, %f, %f) )', pan, tilt, zoom) 69 | return resp 70 | 71 | def continuous_move(self, pan: float, tilt: float, zoom: float): 72 | """ 73 | Operation for continuous Pan/Tilt and Zoom movements. 74 | 75 | Args: 76 | pan: speed of movement of Pan. 77 | tilt: speed of movement of Tilt. 78 | zoom: speed of movement of Zoom. 79 | 80 | Returns: 81 | Return onvif's response. 82 | """ 83 | request = self.camera_ptz.create_type('ContinuousMove') 84 | request.ProfileToken = self.camera_media_profile.token 85 | request.Velocity = {'PanTilt': {'x': pan, 'y': tilt}, 'Zoom': zoom} 86 | resp = self.camera_ptz.ContinuousMove(request) 87 | logging.info('camera_command( continuous_move(%f, %f, %f) )', pan, tilt, zoom) 88 | return resp 89 | 90 | def relative_move(self, pan: float, tilt: float, zoom: float): 91 | """ 92 | Operation for Relative Pan/Tilt and Zoom Move. 93 | 94 | Args: 95 | pan: A positional Translation relative to the pan current position. 96 | tilt: A positional Translation relative to the tilt current position. 97 | zoom: 98 | 99 | Returns: 100 | Return onvif's response 101 | """ 102 | request = self.camera_ptz.create_type('RelativeMove') 103 | request.ProfileToken = self.camera_media_profile.token 104 | request.Translation = {'PanTilt': {'x': pan, 'y': tilt}, 'Zoom': zoom} 105 | resp = self.camera_ptz.RelativeMove(request) 106 | logging.info('camera_command( relative_move(%f, %f, %f) )', pan, tilt, zoom) 107 | return resp 108 | 109 | def stop_move(self): 110 | """ 111 | Operation to stop ongoing pan, tilt and zoom movements of absolute relative and continuous type. 112 | 113 | Returns: 114 | Return onvif's response 115 | """ 116 | request = self.camera_ptz.create_type('Stop') 117 | request.ProfileToken = self.camera_media_profile.token 118 | resp = self.camera_ptz.Stop(request) 119 | logging.info('camera_command( stop_move() )') 120 | return resp 121 | 122 | def set_home_position(self): 123 | """ 124 | Operation to save current position as the home position. 125 | 126 | Returns: 127 | Return onvif's response 128 | """ 129 | request = self.camera_ptz.create_type('SetHomePosition') 130 | request.ProfileToken = self.camera_media_profile.token 131 | resp = self.camera_ptz.SetHomePosition(request) 132 | self.camera_ptz.Stop({'ProfileToken': self.camera_media_profile.token}) 133 | logging.info('camera_command( set_home_position() )') 134 | return resp 135 | 136 | def go_home_position(self): 137 | """ 138 | Operation to move the PTZ device to it's "home" position. 139 | 140 | Returns: 141 | Return onvif's response 142 | """ 143 | request = self.camera_ptz.create_type('GotoHomePosition') 144 | request.ProfileToken = self.camera_media_profile.token 145 | resp = self.camera_ptz.GotoHomePosition(request) 146 | logging.info('camera_command( go_home_position() )') 147 | return resp 148 | 149 | def get_ptz(self): 150 | """ 151 | Operation to request PTZ status. 152 | 153 | Returns: 154 | Returns a list with the values ​​of Pan, Tilt and Zoom 155 | """ 156 | request = self.camera_ptz.create_type('GetStatus') 157 | request.ProfileToken = self.camera_media_profile.token 158 | ptz_status = self.camera_ptz.GetStatus(request) 159 | pan = ptz_status.Position.PanTilt.x 160 | tilt = ptz_status.Position.PanTilt.y 161 | zoom = ptz_status.Position.Zoom.x 162 | ptz_list = (pan, tilt, zoom) 163 | logging.info('camera_command( get_ptz() )') 164 | return ptz_list 165 | 166 | def set_preset(self, preset_name: str): 167 | """ 168 | The command saves the current device position parameters. 169 | Args: 170 | preset_name: Name for preset. 171 | 172 | Returns: 173 | Return onvif's response. 174 | """ 175 | presets = CameraControl.get_preset_complete(self) 176 | request = self.camera_ptz.create_type('SetPreset') 177 | request.ProfileToken = self.camera_media_profile.token 178 | request.PresetName = preset_name 179 | logging.info('camera_command( set_preset%s) )', preset_name) 180 | 181 | for i, _ in enumerate(presets): 182 | if str(presets[i].Name) == preset_name: 183 | logging.warning( 184 | 'Preset (\'%s\') not created. Preset already exists!', preset_name) 185 | return None 186 | 187 | ptz_set_preset = self.camera_ptz.SetPreset(request) 188 | logging.info('Preset (\'%s\') created!', preset_name) 189 | return ptz_set_preset 190 | 191 | def get_preset(self): 192 | """ 193 | Operation to request all PTZ presets. 194 | 195 | Returns: 196 | Returns a list of tuples with the presets. 197 | """ 198 | ptz_get_presets = CameraControl.get_preset_complete(self) 199 | logging.info('camera_command( get_preset() )') 200 | 201 | presets = [] 202 | for i, _ in enumerate(ptz_get_presets): 203 | presets.append((i, ptz_get_presets[i].Name)) 204 | return presets 205 | 206 | def get_preset_complete(self): 207 | """ 208 | Operation to request all PTZ presets. 209 | 210 | Returns: 211 | Returns the complete presets Onvif. 212 | """ 213 | request = self.camera_ptz.create_type('GetPresets') 214 | request.ProfileToken = self.camera_media_profile.token 215 | ptz_get_presets = self.camera_ptz.GetPresets(request) 216 | return ptz_get_presets 217 | 218 | def remove_preset(self, preset_name: str): 219 | """ 220 | Operation to remove a PTZ preset. 221 | 222 | Args: 223 | preset_name: Preset name. 224 | 225 | Returns: 226 | Return onvif's response. 227 | """ 228 | presets = CameraControl.get_preset_complete(self) 229 | request = self.camera_ptz.create_type('RemovePreset') 230 | request.ProfileToken = self.camera_media_profile.token 231 | logging.info('camera_command( remove_preset(%s) )', preset_name) 232 | for i, _ in enumerate(presets): 233 | if str(presets[i].Name) == preset_name: 234 | request.PresetToken = presets[i].token 235 | ptz_remove_preset = self.camera_ptz.RemovePreset(request) 236 | logging.info('Preset (\'%s\') removed!', preset_name) 237 | return ptz_remove_preset 238 | logging.warning("Preset (\'%s\') not found!", preset_name) 239 | return None 240 | 241 | def go_to_preset(self, preset_position: str): 242 | """ 243 | Operation to go to a saved preset position. 244 | 245 | Args: 246 | preset_position: preset name. 247 | 248 | Returns: 249 | Return onvif's response. 250 | """ 251 | presets = CameraControl.get_preset_complete(self) 252 | request = self.camera_ptz.create_type('GotoPreset') 253 | request.ProfileToken = self.camera_media_profile.token 254 | logging.info('camera_command( go_to_preset(%s) )', preset_position) 255 | for i, _ in enumerate(presets): 256 | str1 = str(presets[i].Name) 257 | if str1 == preset_position: 258 | request.PresetToken = presets[i].token 259 | resp = self.camera_ptz.GotoPreset(request) 260 | logging.info("Goes to (\'%s\')", preset_position) 261 | return resp 262 | logging.warning("Preset (\'%s\') not found!", preset_position) 263 | return None 264 | -------------------------------------------------------------------------------- /sensecam_control/vapix_control.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library for control AXIS PTZ cameras using Vapix 3 | """ 4 | import time 5 | import logging 6 | import sys 7 | import requests 8 | from requests.auth import HTTPDigestAuth 9 | from bs4 import BeautifulSoup 10 | 11 | logging.basicConfig(filename='vapix.log', filemode='w', level=logging.DEBUG) 12 | logging.info('Started') 13 | 14 | # pylint: disable=R0904 15 | 16 | 17 | class CameraControl: 18 | """ 19 | Module for control cameras AXIS using Vapix 20 | """ 21 | 22 | def __init__(self, ip, user, password): 23 | self.__cam_ip = ip 24 | self.__cam_user = user 25 | self.__cam_password = password 26 | 27 | @staticmethod 28 | def __merge_dicts(*dict_args) -> dict: 29 | """ 30 | Given any number of dicts, shallow copy and merge into a new dict, 31 | precedence goes to key value pairs in latter dicts 32 | 33 | Args: 34 | *dict_args: argument dictionary 35 | 36 | Returns: 37 | Return a merged dictionary 38 | """ 39 | result = {} 40 | for dictionary in dict_args: 41 | result.update(dictionary) 42 | return result 43 | 44 | def _camera_command(self, payload: dict): 45 | """ 46 | Function used to send commands to the camera 47 | Args: 48 | payload: argument dictionary for camera control 49 | 50 | Returns: 51 | Returns the response from the device to the command sent 52 | 53 | """ 54 | logging.info('camera_command(%s)', payload) 55 | 56 | base_q_args = { 57 | 'camera': 1, 58 | 'html': 'no', 59 | 'timestamp': int(time.time()) 60 | } 61 | 62 | payload2 = CameraControl.__merge_dicts(payload, base_q_args) 63 | 64 | url = 'http://' + self.__cam_ip + '/axis-cgi/com/ptz.cgi' 65 | 66 | resp = requests.get(url, auth=HTTPDigestAuth(self.__cam_user, self.__cam_password), 67 | params=payload2) 68 | 69 | if (resp.status_code != 200) and (resp.status_code != 204): 70 | soup = BeautifulSoup(resp.text, features="lxml") 71 | logging.error('%s', soup.get_text()) 72 | if resp.status_code == 401: 73 | sys.exit(1) 74 | 75 | return resp 76 | 77 | def absolute_move(self, pan: float = None, tilt: float = None, zoom: int = None, 78 | speed: int = None): 79 | """ 80 | Operation to move pan, tilt or zoom to a absolute destination. 81 | 82 | Args: 83 | pan: pans the device relative to the (0,0) position. 84 | tilt: tilts the device relative to the (0,0) position. 85 | zoom: zooms the device n steps. 86 | speed: speed move camera. 87 | 88 | Returns: 89 | Returns the response from the device to the command sent. 90 | 91 | """ 92 | return self._camera_command({'pan': pan, 'tilt': tilt, 'zoom': zoom, 'speed': speed}) 93 | 94 | def continuous_move(self, pan: int = None, tilt: int = None, zoom: int = None): 95 | """ 96 | Operation for continuous Pan/Tilt and Zoom movements. 97 | 98 | Args: 99 | pan: speed of movement of Pan. 100 | tilt: speed of movement of Tilt. 101 | zoom: speed of movement of Zoom. 102 | 103 | Returns: 104 | Returns the response from the device to the command sent. 105 | 106 | """ 107 | pan_tilt = str(pan) + "," + str(tilt) 108 | return self._camera_command({'continuouspantiltmove': pan_tilt, 'continuouszoommove': zoom}) 109 | 110 | def relative_move(self, pan: float = None, tilt: float = None, zoom: int = None, 111 | speed: int = None): 112 | """ 113 | Operation for Relative Pan/Tilt and Zoom Move. 114 | 115 | Args: 116 | pan: pans the device n degrees relative to the current position. 117 | tilt: tilts the device n degrees relative to the current position. 118 | zoom: zooms the device n steps relative to the current position. 119 | speed: speed move camera. 120 | 121 | Returns: 122 | Returns the response from the device to the command sent. 123 | 124 | """ 125 | return self._camera_command({'rpan': pan, 'rtilt': tilt, 'rzoom': zoom, 'speed': speed}) 126 | 127 | def stop_move(self): 128 | """ 129 | Operation to stop ongoing pan, tilt and zoom movements of absolute relative and 130 | continuous type 131 | 132 | Returns: 133 | Returns the response from the device to the command sent 134 | 135 | """ 136 | return self._camera_command({'continuouspantiltmove': '0,0', 'continuouszoommove': 0}) 137 | 138 | def center_move(self, pos_x: int = None, pos_y: int = None, speed: int = None): 139 | """ 140 | Used to send the coordinates for the point in the image where the user clicked. This 141 | information is then used by the server to calculate the pan/tilt move required to 142 | (approximately) center the clicked point. 143 | 144 | Args: 145 | pos_x: value of the X coordinate. 146 | pos_y: value of the Y coordinate. 147 | speed: speed move camera. 148 | 149 | Returns: 150 | Returns the response from the device to the command sent 151 | 152 | """ 153 | pan_tilt = str(pos_x) + "," + str(pos_y) 154 | return self._camera_command({'center': pan_tilt, 'speed': speed}) 155 | 156 | def area_zoom(self, pos_x: int = None, pos_y: int = None, zoom: int = None, 157 | speed: int = None): 158 | """ 159 | Centers on positions x,y (like the center command) and zooms by a factor of z/100. 160 | 161 | Args: 162 | pos_x: value of the X coordinate. 163 | pos_y: value of the Y coordinate. 164 | zoom: zooms by a factor. 165 | speed: speed move camera. 166 | 167 | Returns: 168 | Returns the response from the device to the command sent 169 | 170 | """ 171 | xyzoom = str(pos_x) + "," + str(pos_y) + "," + str(zoom) 172 | return self._camera_command({'areazoom': xyzoom, 'speed': speed}) 173 | 174 | def move(self, position: str = None, speed: float = None): 175 | """ 176 | Moves the device 5 degrees in the specified direction. 177 | 178 | Args: 179 | position: position to move. (home, up, down, left, right, upleft, upright, downleft...) 180 | speed: speed move camera. 181 | 182 | Returns: 183 | Returns the response from the device to the command sent 184 | 185 | """ 186 | return self._camera_command({'move': str(position), 'speed': speed}) 187 | 188 | def go_home_position(self, speed: int = None): 189 | """ 190 | Operation to move the PTZ device to it's "home" position. 191 | 192 | Args: 193 | speed: speed move camera. 194 | 195 | Returns: 196 | Returns the response from the device to the command sent 197 | 198 | """ 199 | return self._camera_command({'move': 'home', 'speed': speed}) 200 | 201 | def get_ptz(self): 202 | """ 203 | Operation to request PTZ status. 204 | 205 | Returns: 206 | Returns a tuple with the position of the camera (P, T, Z) 207 | 208 | """ 209 | resp = self._camera_command({'query': 'position'}) 210 | pan = float(resp.text.split()[0].split('=')[1]) 211 | tilt = float(resp.text.split()[1].split('=')[1]) 212 | zoom = float(resp.text.split()[2].split('=')[1]) 213 | ptz_list = (pan, tilt, zoom) 214 | 215 | return ptz_list 216 | 217 | def go_to_server_preset_name(self, name: str = None, speed: int = None): 218 | """ 219 | Move to the position associated with the preset on server. 220 | 221 | Args: 222 | name: name of preset position server. 223 | speed: speed move camera. 224 | 225 | Returns: 226 | Returns the response from the device to the command sent 227 | 228 | """ 229 | return self._camera_command({'gotoserverpresetname': name, 'speed': speed}) 230 | 231 | def go_to_server_preset_no(self, number: int = None, speed: int = None): 232 | """ 233 | Move to the position associated with the specified preset position number. 234 | 235 | Args: 236 | number: number of preset position server. 237 | speed: speed move camera. 238 | 239 | Returns: 240 | Returns the response from the device to the command sent 241 | 242 | """ 243 | return self._camera_command({'gotoserverpresetno': number, 'speed': speed}) 244 | 245 | def go_to_device_preset(self, preset_pos: int = None, speed: int = None): 246 | """ 247 | Bypasses the presetpos interface and tells the device to go directly to the preset 248 | position number stored in the device, where the is a device-specific preset position number. 249 | 250 | Args: 251 | preset_pos: number of preset position device 252 | speed: speed move camera 253 | 254 | Returns: 255 | Returns the response from the device to the command sent 256 | 257 | """ 258 | return self._camera_command({'gotodevicepreset': preset_pos, 'speed': speed}) 259 | 260 | def list_preset_device(self): 261 | """ 262 | List the presets positions stored in the device. 263 | 264 | Returns: 265 | Returns the list of presets positions stored on the device. 266 | 267 | """ 268 | return self._camera_command({'query': 'presetposcam'}) 269 | 270 | def list_all_preset(self): 271 | """ 272 | List all available presets position. 273 | 274 | Returns: 275 | Returns the list of all presets positions. 276 | 277 | """ 278 | resp = self._camera_command({'query': 'presetposall'}) 279 | soup = BeautifulSoup(resp.text, features="lxml") 280 | resp_presets = soup.text.split('\n') 281 | presets = [] 282 | 283 | for i in range(1, len(resp_presets)-1): 284 | preset = resp_presets[i].split("=") 285 | presets.append((int(preset[0].split('presetposno')[1]), preset[1].rstrip('\r'))) 286 | 287 | return presets 288 | 289 | def set_speed(self, speed: int = None): 290 | """ 291 | Sets the head speed of the device that is connected to the specified camera. 292 | Args: 293 | speed: speed value. 294 | 295 | Returns: 296 | Returns the response from the device to the command sent. 297 | 298 | """ 299 | return self._camera_command({'speed': speed}) 300 | 301 | def get_speed(self): 302 | """ 303 | Requests the camera's speed of movement. 304 | 305 | Returns: 306 | Returns the camera's move value. 307 | 308 | """ 309 | resp = self._camera_command({'query': 'speed'}) 310 | return int(resp.text.split()[0].split('=')[1]) 311 | 312 | def info_ptz_comands(self): 313 | """ 314 | Returns a description of available PTZ commands. No PTZ control is performed. 315 | 316 | Returns: 317 | Success (OK and system log content text) or Failure (error and description). 318 | 319 | """ 320 | resp = self._camera_command({'info': '1'}) 321 | return resp.text 322 | -------------------------------------------------------------------------------- /sensecam_control/onvif_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library for control AXIS PTZ cameras using Onvif 3 | """ 4 | import logging 5 | from onvif import ONVIFCamera 6 | 7 | logging.basicConfig(filename='log-onvif-config.log', filemode='w', level=logging.DEBUG) 8 | logging.info('Started') 9 | 10 | 11 | #pylint: disable=R0904 12 | class CameraConfiguration: 13 | """ 14 | Module for configuration cameras AXIS using Onvif 15 | """ 16 | 17 | def __init__(self, ip, user, password): 18 | self.__cam_ip = ip 19 | self.__cam_user = user 20 | self.__cam_password = password 21 | 22 | 23 | def camera_start(self): 24 | """ 25 | Creates the connection to the camera using the onvif protocol 26 | 27 | Returns: 28 | Return the ptz service object and media service object 29 | """ 30 | mycam = ONVIFCamera(self.__cam_ip, 80, self.__cam_user, self.__cam_password) 31 | logging.info('Create media service object') 32 | media = mycam.create_media_service() 33 | logging.info('Get target profile') 34 | media_profile = media.GetProfiles()[0] 35 | logging.info('Camera working!') 36 | 37 | self.mycam = mycam 38 | self.camera_media_profile = media_profile 39 | self.camera_media = media 40 | self.mycam = mycam 41 | 42 | return self.mycam 43 | 44 | ######## DEVICEMGMT ######### 45 | # https://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl 46 | 47 | def set_user(self, name, password, user_level): 48 | """ 49 | This operation updates the settings for one or several users on a device for 50 | authentication purposes. 51 | Args: 52 | name: user name. 53 | password: user password. 54 | user_level: user level. 55 | 56 | Returns: 57 | Return onvif's response 58 | """ 59 | params = {'Username': name, 'Password': password, 'UserLevel': user_level} 60 | return self.mycam.devicemgmt.SetUser(params) 61 | 62 | def create_user(self, username, password, user_level): 63 | """ 64 | This operation creates new device users and corresponding credentials on a device 65 | for authentication. 66 | Args: 67 | username: user name 68 | password: user password 69 | user_level: user level 70 | 71 | Returns: 72 | Return onvif's response 73 | """ 74 | params = self.mycam.devicemgmt.create_type('CreateUsers') 75 | params.User = {'Username': username, 'Password': password, 'UserLevel': user_level} 76 | return self.mycam.devicemgmt.CreateUsers(params) 77 | 78 | def delete_users(self, username): 79 | """ 80 | This operation deletes users on a device. 81 | Args: 82 | username: user name 83 | 84 | Returns: 85 | Return onvif's response 86 | """ 87 | params = self.mycam.devicemgmt.create_type('DeleteUsers') 88 | params.Username = username 89 | return self.mycam.devicemgmt.DeleteUsers(params) 90 | 91 | def set_discovery_mode(self, discovery_mode): # enum { 'Discoverable', 'NonDiscoverable' } 92 | """ 93 | This operation sets the discovery mode operation of a device. 94 | Args: 95 | discovery_mode: Indicator of discovery mode. (Discoverable, NonDiscoverable) 96 | 97 | Returns: 98 | Return onvif's response. 99 | """ 100 | params = self.mycam.devicemgmt.create_type('SetDiscoveryMode') 101 | params.DiscoveryMode = discovery_mode 102 | return self.mycam.devicemgmt.SetDiscoveryMode(params) 103 | 104 | def set_dns(self, type_dns, ipv4, ipv6): 105 | """ 106 | This operation sets the DNS settings on a device. 107 | Args: 108 | type_dns: Indicates if the address is an IPv4 or IPv6 address. 109 | ipv4: IPv4 address. 110 | ipv6: IPv6 address. 111 | 112 | Returns: 113 | Return onvif's response. 114 | """ 115 | params = self.mycam.devicemgmt.create_type('SetDNS') 116 | params.FromDHCP = 1 117 | params.SearchDomain = 0 118 | params.DNSManual = {'Type': type_dns, 'IPv4Address': ipv4, 'IPv6Address': ipv6} 119 | return self.mycam.devicemgmt.SetDNS(params) 120 | 121 | def get_hostname(self): 122 | """ 123 | This operation is used to get the hostname from a device. 124 | 125 | Returns: 126 | Return its hostname configurations. 127 | """ 128 | return self.mycam.devicemgmt.GetHostname() 129 | 130 | def get_ip_address_filter(self): 131 | """ 132 | This operation gets the IP address filter settings from a device. 133 | 134 | Returns: 135 | Return its ip address configurations 136 | """ 137 | return self.mycam.devicemgmt.GetIPAddressFilter() 138 | 139 | def get_device_information(self): 140 | """ 141 | This operation gets basic device information from the device. 142 | 143 | Returns: 144 | Return camera information. (Manufacturer, Model, FirmwareVersion, etc) 145 | """ 146 | return self.mycam.devicemgmt.GetDeviceInformation() 147 | 148 | def get_discovery_mode(self): 149 | """ 150 | This operation gets the discovery mode of a device. 151 | 152 | Returns: 153 | Return discovery information. 154 | """ 155 | return self.mycam.devicemgmt.GetDiscoveryMode() 156 | 157 | def get_dns(self): 158 | """ 159 | This operation gets the DNS settings from a device. 160 | 161 | Returns: 162 | Return its DNS configurations. 163 | """ 164 | return self.mycam.devicemgmt.GetDNS() 165 | 166 | def get_dynamic_dns(self): 167 | """ 168 | This operation gets the dynamic DNS settings from a device. 169 | 170 | Returns: 171 | Return its dynamic DNS configurations 172 | """ 173 | return self.mycam.devicemgmt.GetDynamicDNS() 174 | 175 | def get_network_default_gateway(self): 176 | """ 177 | This operation gets the default gateway settings from a device. 178 | 179 | Returns: 180 | Return configured default gateway address(es) 181 | """ 182 | return self.mycam.devicemgmt.GetNetworkDefaultGateway() 183 | 184 | def get_network_interfaces(self): 185 | """ 186 | This operation gets the network interface configuration from a device. 187 | 188 | Returns: 189 | Return of network interface configuration settings as defined by the 190 | NetworkInterface type 191 | """ 192 | return self.mycam.devicemgmt.GetNetworkInterfaces() 193 | 194 | def get_network_protocols(self): 195 | """ 196 | This operation gets defined network protocols from a device. 197 | 198 | Returns: 199 | return configured network protocols 200 | """ 201 | return self.mycam.devicemgmt.GetNetworkProtocols() 202 | 203 | def get_ntp(self): 204 | """ 205 | This operation gets the NTP settings from a device. 206 | 207 | Returns: 208 | Return NTP server settings 209 | """ 210 | return self.mycam.devicemgmt.GetNTP() 211 | 212 | def get_system_date_and_time(self): 213 | """ 214 | This operation gets the device system date and time. 215 | 216 | Returns: 217 | Return of the daylight saving setting and of the manual system date and time 218 | (if applicable) or indication of NTP time (if applicable) 219 | """ 220 | return self.mycam.devicemgmt.GetSystemDateAndTime() 221 | 222 | def get_users(self): 223 | """ 224 | This operation lists the registered users and corresponding credentials on a device. 225 | 226 | Returns: 227 | Return registered device users and their credentials (onvif users) 228 | """ 229 | return self.mycam.devicemgmt.GetUsers() 230 | 231 | def get_wsdl_url(self): 232 | """ 233 | Request a URL that can be used to retrieve the complete schema and WSDL definitions of a 234 | device. 235 | 236 | Returns: 237 | Return a URL entry point where all the necessary product specific WSDL and schema 238 | definitions can be retrieved 239 | """ 240 | return self.mycam.devicemgmt.GetWsdlUrl() 241 | 242 | def set_hostname(self, new_hostname): 243 | """ 244 | This operation sets the hostname on a device. 245 | 246 | Args: 247 | new_hostname: new hostname 248 | 249 | Returns: 250 | Return onvif's response 251 | """ 252 | return self.mycam.devicemgmt.SetHostname(new_hostname) 253 | 254 | def system_reboot(self): 255 | """ 256 | This operation reboots the device. 257 | 258 | Returns: 259 | Return contains the reboot message sent by the device 260 | """ 261 | confirmation = input("Do you want to reboot the camera? (Y or N)\n") 262 | if confirmation in ('Y', 'y'): 263 | return self.mycam.devicemgmt.SystemReboot() 264 | return None 265 | 266 | def start_system_restore(self): 267 | """ 268 | This operation initiates a system restore from backed up configuration data using the 269 | HTTP POST mechanism. 270 | 271 | Returns: 272 | Return HTTP URL to which the backup file may be uploaded and expected down time 273 | """ 274 | confirmation = input("Do you want to system restore? (Y or N)\n") 275 | if confirmation in ('Y', 'y'): 276 | return self.mycam.devicemgmt.StartSystemRestore() 277 | return None 278 | 279 | 280 | 281 | ######## MEDIA ######### 282 | # https://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl 283 | 284 | def get_profiles(self): #video profiles 285 | """ 286 | This command lists all configured video profiles in a device. 287 | 288 | Returns: 289 | Returns list of video settings. 290 | """ 291 | return self.camera_media.GetProfiles() 292 | 293 | def get_audio_decoder_configurations(self): 294 | """ 295 | This operation requests decoder configuration. 296 | 297 | Returns: 298 | Return decoder configuration. 299 | """ 300 | return self.camera_media.GetAudioDecoderConfigurations() 301 | 302 | def get_video_analytics_configurations(self): 303 | """ 304 | This operation fetches the video analytics configuration. 305 | 306 | Returns: 307 | Return video analytics configuration 308 | """ 309 | return self.camera_media.GetVideoAnalyticsConfigurations() 310 | 311 | def get_video_encoder_configurations(self): 312 | """ 313 | This operation request the encoder configuration. 314 | 315 | Returns: 316 | Return encoder configuration 317 | """ 318 | return self.camera_media.GetVideoEncoderConfigurations() 319 | 320 | def get_video_source_configurations(self): 321 | """ 322 | This operation request the video source configuration. 323 | 324 | Returns: 325 | Return video source configuration. 326 | """ 327 | return self.camera_media.GetVideoSourceConfigurations() 328 | 329 | def get_video_sources(self): 330 | """ 331 | This operation lists all available physical video inputs of the device. 332 | 333 | Returns: 334 | Return all available physical video inputs 335 | """ 336 | return self.camera_media.GetVideoSources() 337 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Axis Vapix/Onvif Python 2 | 3 | This library is designed to provide control and configuration of Axis cameras using the Onvif and Vapix protocol. 4 | 5 | **VAPIX®** is Axis' own open API (Application Programming Interface) using standard protocols enabling integration into a wide range of solutions on different platforms. 6 | 7 | **VAPIX®** provides functionality for requesting images, controlling Pan Tilt Zoom, controlling Input and Output ports, retrieve and control internal settings, to manage Events, record and retrieve video to/from the SD card, and much, much more. Almost all functionality available in Axis products can be controlled using VAPIX®, some functions are even only supported via VAPIX®, for example, to retrieve Bitmap images. 8 | 9 | **ONVIF (Open Network Video Interface Forum)** is a global and open industry forum with the goal of facilitating the development and use of a global open standard for the interface of physical IP-based security products. ONVIF creates a standard for how IP products within video surveillance and other physical security areas can communicate with each other. ONVIF is an organization started in 2008 by Axis Communications, Bosch Security Systems and Sony. 10 | 11 | ## Installation 12 | Install the package through pip: 13 | 14 | ```` 15 | pip install sensecam-control 16 | ```` 17 | ## Execution 18 | 19 | Example of use: 20 | 21 | ```` 22 | from sensecam_control import vapix_control 23 | from sensecam_control import onvif_control 24 | 25 | 26 | Camera1 = vapix_control.CameraControl(, , ) 27 | Camera2 = onvif_control.CameraControl(, , ) 28 | 29 | Camera1.absolute_move(10, 20, 1) 30 | Camera1.absolute_move(10, 20, 1, 50) 31 | 32 | Camera2.absolute_move(0.02, 0.60, 0.0) 33 | Camera2.relative_move(0.3, -0.2, 0) 34 | 35 | ```` 36 | 37 | 38 | 39 | ## Functions 40 | ### Vapix Control 41 | 42 | * `absolute_move(pan, tilt, zoom, speed)` - Operation to move pan, tilt or zoom to a absolute destination. 43 | - pan (float): pans the device relative to the (0,0) position. Values (-180.0 … 180.0) 44 | - tilt (float): tilts the device relative to the (0,0) position. Values (-180.0 … 180.0) 45 | - zoom (int): zooms the device n steps. Values (1 … 9999) 46 | - speed (int): speed move camera. Values (1 … 100) 47 | 48 | * `continuous_move(pan, tilt, zoom)` - Operation for continuous Pan/Tilt and Zoom movements. 49 | - pan (int): Speed of movement of Pan. (-100 … 100) 50 | - tilt (int): Speed of movement of Tilt. (-100 … 100) 51 | - zoom (int): Speed of movement of Zoom. (-100 … 100) 52 | 53 | * `relative_move(pan, tilt, zoom, speed)` - Operation for Relative Pan/Tilt and Zoom Move. 54 | - pan (float): Pans the device n degrees relative to the current position. (-360.0 … 360.0) 55 | - tilt (float): Tilts the device n degrees relative to the current position. (-360.0 … 360.0) 56 | - zoom (int): Zooms the device n steps relative to the current position. (-9999 … 9999) 57 | - speed (int): speed move camera. (1 … 100) 58 | 59 | * `stop_move()` - Operation to stop ongoing pan, tilt and zoom movements of absolute relative and continuous type. 60 | 61 | * `center_move(pos_x, pos_y, speed)` - Used to send the coordinates for the point in the image where the user clicked. This information is then used by the server to calculate the pan/tilt move required to (approximately) center the clicked point. 62 | - pos_x (int): value of the X coordinate. 63 | - pos_y (int): value of the Y coordinate. 64 | - speed (int): speed move camera. (-100 … 100) 65 | 66 | * `area_zoom(pos_x, pos_y, zoom, speed)` - Centers on positions x,y (like the center command) and zooms by a factor of z/100. 67 | - pos_x (int): value of the X coordinate. 68 | - pos_y (int): value of the Y coordinate. 69 | - zoom (int): zooms by a factor. 70 | - speed (int): speed move camera. (-100 … 100) 71 | 72 | * `move(position, speed)` - Moves the device 5 degrees in the specified direction. 73 | - position (str): position to move. (home, up, down, left, right, upleft, upright, downleft...) 74 | - speed (int): speed move camera. 75 | 76 | * `go_home_position(speed)` - Operation to move the PTZ device to it's "home" position. 77 | - speed (int): speed move camera. (-100 … 100) 78 | 79 | * `get_ptz()` - Operation to request PTZ status. 80 | 81 | * `go_to_server_preset_name(name, speed)` - Move to the position associated with the preset on server. 82 | - name (str): name of preset position server. 83 | - speed (int): speed move camera. (-100 … 100) 84 | 85 | * `go_to_server_preset_no(number, speed)` - Move to the position associated with the specified preset position number. 86 | - number (int): number of preset position server. 87 | - speed (int): speed move camera. (-100 … 100) 88 | 89 | * `go_to_device_preset(preset_pos, speed)` - Bypasses the presetpos interface and tells the device to go directly to the preset position number stored in the device, where the is a device-specific preset position number. 90 | - preset_pos (int): number of preset position device. 91 | - speed (int): speed move camera. (-100 … 100) 92 | 93 | * `list_preset_device()` - List the presets positions stored in the device. 94 | 95 | * `list_all_preset()` - List all available presets position. 96 | 97 | * `set_speed(speed)` - Sets the head speed of the device that is connected to the specified camera. 98 | - speed (int): speed value. (-100 … 100) 99 | 100 | * `get_speed()` - Requests the camera's speed of movement. 101 | 102 | ### Vapix Configuration 103 | * `factory_reset_default()` - Reload factory default. All parameters except Network.BootProto, Network.IPAddress, Network. SubnetMask, Network.Broadcast and Network.DefaultRouter are set to their factory default values. 104 | 105 | * `hard_factory_reset_default()` - Reload factory default. All parameters are set to their factory default value. 106 | 107 | * `restart_server()` - Restart server. 108 | 109 | * `get_server_report()` - This CGI request generates and returns a server report. This report is useful as an input when requesting support. The report includes product information, parameter settings and system logs. 110 | 111 | * `get_system_log()` - Retrieve system log information. The level of information included in the log is set in the Log. System parameter group. 112 | 113 | * `get_system_access_log()` - Retrieve client access log information. The level of information included in the log is set in the Log.Access parameter group. 114 | 115 | * `get_date_and_time()` - Get the system date and time. 116 | 117 | * `set_date(year_date, month_date, day_date)` - Change the system date. 118 | - year_date (int): current year. 119 | - month_date (int): current month. 120 | - day_date (int): current day. 121 | 122 | * `set_time(hour, minute, second, timezone)` - Change the system time. 123 | - hour (int): current hour. 124 | - minute (int): current minute. 125 | - second (int): current second. 126 | - timezone (str): specifies the time zone that the new date and/or time is given in. The camera 127 | translates the time into local time using whichever time zone has been specified through 128 | the web configuration. 129 | 130 | * `get_image_size()` - Retrieve the actual image size with default image settings or with given parameters. 131 | 132 | * `get_video_status(camera_status)` - Video encoders only. Check the status of one or more video sources. 133 | - camera_status (int): video source. 134 | 135 | * `get_bitmap_request(resolution, camera, square_pixel)` - Request a bitmap image. 136 | - resolution (str): resolution of the returned image. Check the product’s Release notes for 137 | supported resolutions. 138 | - camera (str): select a video source or the quad stream. (1, 2, ...,quad) 139 | - square_pixel (int): enable/disable square pixel correction. Applies only to video encoders. 140 | 141 | 142 | * `get_jpeg_request(resolution, camera, square_pixel, compression, clock, date, text, text_string, text_color, text_background_color, rotation, text_position, overlay_image, overlay_position)` - The requests specified in the JPEG/MJPG section are supported by those video products that use JPEG and MJPG encoding. 143 | - resolution (str): Resolution of the returned image. Check the product’s Release notes. 144 | - camera (str): selects the source camera or the quad stream. (1, 2, ...,quad) 145 | - square_pixel (int): enable/disable square pixel correction. Applies only to video encoders. (1, 0) 146 | - compression (int): adjusts the compression level of the image. (0 … 100) 147 | - clock (int): shows/hides the time stamp. (0 = hide, 1 = show) 148 | - date (int): shows/hides the date. (0 = hide, 1 = show) 149 | - text (int): shows/hides the text. (0 = hide, 1 = show) 150 | - text_string (str): the text shown in the image, the string must be URL encoded. 151 | - text_color (str): the color of the text shown in the image. (black, white) 152 | - text_background_color (str): the color of the text background shown in the image. (black, white, transparent, semitransparent) 153 | - rotation (int): totate the image clockwise. (0, 90, 180, 270) 154 | - text_position (str): the position of the string shown in the image. (top, bottom) 155 | - overlay_image (int): tnable/disable overlay image.(0 = disable, 1 = enable) 156 | - overlay_position (str): the x and y coordinates defining the position of the overlay image. ('< int >x< int >' or < int >,< int >) 157 | 158 | * `get_type_camera()` - Request type camera. 159 | 160 | * `get_dynamic_text_overlay()` - Get dynamic text overlay in the image. 161 | 162 | * `set_dynamic_text_overlay(text, camera)` - Set dynamic text overlay in the image. 163 | - text (str): text to set overlay. 164 | - camera (str): select video source or the quad stream. ( default: default camera) 165 | 166 | * `check_profile(name)` - Check if the profile exists. 167 | - name (str): profile name 168 | 169 | * `create_profile(name: str, *, resolution, videocodec, fps, compression, h264profile, gop, bitrate, bitratepriority)` - Create stream profile. 170 | - name (str): profile name. 171 | - resolution (str): resolution. 172 | - videocodec (str): video codec. (h264, mjpg) 173 | - fps (int): frame rate. 174 | - compression (int): adjusts the compression level of the image. (0 … 100) 175 | - h264profile (str): profile h264. (high, main, baseline) 176 | - gop (int): Group of pictures. (1 ... 1023) 177 | - bitrate (int): video bitrate. 178 | - bitratepriority (str): video bitrate priority. (framerate, quality) 179 | 180 | * `create_user(user, password, sgroup, *, group, comment)` - Create user. 181 | - user (str): the user account name (1-14 characters), a non-existing user account name. Valid characters are a-z, A-Z and 0-9. 182 | - password (str): the unencrypted password (1-64 characters) for the account. ASCII characters from character code 32 to 126 are valid. 183 | - group (str): an existing primary group name of the account. The recommended value for this argument is 'users' [default]. (users, root) 184 | - sgroup (str): security group. (admin, operator, viewer, ptz) 185 | - comment (str): user description. 186 | 187 | * `update_user(user, password, *, group, sgroup, comment)` - Update user params. 188 | - user (str): user name 189 | - password (str): new password or current password to change others params. 190 | - group (str): an existing primary group name of the account. The recommended value for this argument is 'users' [default]. (users, root) 191 | - sgroup (str): security group. (admin, operator, viewer, ptz) 192 | - comment (str): user description. 193 | 194 | * `remove_user(user)` - Remove user. 195 | - user (str): user name 196 | 197 | * `check_user(name)` - Check if user exists. 198 | - user (str): user name 199 | 200 | * `set_hostname(hostname, *, set_dhcp)` - Configure how the device selects a hostname, with the possibility to set a static hostname and/or enable auto-configuration by DHCP. 201 | - hostname (str): hostname 202 | - set_dhcp: enable auto-configuration by DHCP. (yes, no) 203 | 204 | * `set_stabilizer( stabilizer, *, stabilizer_margin)` - Set electronic image stabilization (EIS). 205 | - stabilizer (str): stabilizer value (on, off) 206 | - stabilizer_margin: stabilization margin (0 ... 200) 207 | 208 | * `set_capture_mode(capture_mode)` - Set capture mode. 209 | - capture_mode (str): capture mode. 210 | 211 | * `set_wdr(wdr, *, contrast)` - WDR - Forensic Capture - Wide Dynamic Range can improve the exposure when there is a considerable contrast between light and dark areas in an image. 212 | - wdr (str): WDR value (on, off) 213 | - contrast (int): contrast level. 214 | 215 | * `set_appearance(*, brightness, contrast, saturation, sharpness)` - Image Appearance Setting. 216 | - brightness (int): adjusts the image brightness. 217 | - contrast (int): adjusts the image's contrast. 218 | - saturation (int): adjusts color saturation - color level. 219 | - sharpness (int): controls the amount of sharpening applied to the image. 220 | 221 | * `set_ir_cut_filter(ir_cut, *, shift_level)` - IR cut filter settings. 222 | - ir_cut (str): IR value. (on, off, auto) 223 | - shift_level (int): This setting can be used to change when the camera shifts into night mode. 224 | 225 | 226 | * `set_exposure(*, exposure, exposure_window, max_exposure_time, max_gain, exposure_priority_normal, lock_aperture, exposure_value)` - Exposure Settings. 227 | - exposure (str): exposure mode. (flickerfree60, flickerfree50, flickerreduced60, 228 | flickerreduced50, auto, hold) 229 | - exposure_window (str): This setting determines which part of the image will be used to 230 | calculate the exposure. (auto, center, spot, upper, lower, left, right, custom) 231 | - max_exposure_time (int): maximum shutter time (MS). 232 | - max_gain (int): maximum gain. 233 | - exposure_priority_normal (int): commitment blur / noise. 234 | - lock_aperture (str): lock the shutter aperture. 235 | - exposure_value (int): exposure level. 236 | 237 | * `set_custom_exposure_window(top, bottom, left, right)` - Set custom exposition zone. 238 | - top (int): upper limit. 239 | - bottom (int): lower limit. 240 | - left (int): left limit. 241 | - right (int): right limit. 242 | 243 | * `set_backlight(backlight)` - Backlight compensation makes the subject appear clearer when the image background is too bright, or the subject is too dark. 244 | - backlight (str): backlight value. (true, false) 245 | 246 | * `set_highlight(highlight)` - The Axis product will detect a bright light from a source such as a torch or car headlights and mask that image area. This setting is useful when the camera operates in a very dark area where a bright light may overexpose part of the image and prevent the operator from seeing other parts of the scene. 247 | - highlight (str): highlight value. (0, 1) 248 | 249 | * `set_image_setings(*, defog, noise_reduction, noise_reduction_tuning, image_freeze_ptz)` - Image Settings. 250 | - defog (str): detect the fog effect and automatically remove it to get a clear image. (on, off) 251 | - noise_reduction (str): noise reduction function (on, off) 252 | - noise_reduction_tuning (int): Noise Reduction Adjustment level (0 to 100) 253 | - image_freeze_ptz (str): freeze the image while the camera is moving during a pan, tilt or zoom operation. (on, off) 254 | 255 | * `set_ntp_server(ntp_server)` - Configure NTP server. 256 | - ntp_server (str): ntp server. 257 | 258 | * `set_pan_tilt_zoom_enable(*, pan_enable, tilt_enable, zoom_enable)` - Turns PTZ control on and off. 259 | - pan_enable (str): pan enabled value (true, false) 260 | - tilt_enable (str): tilt enabled value (true, false) 261 | - zoom_enable (str): zoom enabled value (true, false) 262 | 263 | * `auto_focus(focus)` - Enable or disable automatic focus. 264 | - focus (str): focus value (on, off) 265 | 266 | * `auto_iris(iris)` - Enable or disable automatic iris control. 267 | - iris (str): iris value (on, off) 268 | 269 | ### Onvif Control 270 | * `absolute_move(pan, tilt, zoom)` - Operation to move pan, tilt or zoom to a absolute destination. 271 | - pan (float): pans the device relative to the (0,0) position. 272 | - tilt (float): tilts the device relative to the (0,0) position. 273 | - zoom (float): zooms the device n steps. 274 | 275 | * `continuous_move(pan, tilt, zoom)` - Operation for continuous Pan/Tilt and Zoom movements. 276 | - pan (float): speed of movement of Pan. 277 | - tilt (float): speed of movement of Tilt. 278 | - zoom (float): speed of movement of Zoom. 279 | 280 | * `relative_move(pan, tilt, zoom)` - Operation for Relative Pan/Tilt and Zoom Move. 281 | - pan (float): pans the device n degrees relative to the current position. 282 | - tilt (float): tilts the device n degrees relative to the current position. 283 | - zoom (float): zooms the device n steps relative to the current position. 284 | 285 | * `stop_move()` - Operation to stop ongoing pan, tilt and zoom movements of absolute relative and continuous type. 286 | 287 | * `set_home_position()` - Operation to save current position as the home position. 288 | 289 | * `go_home_position()` - Operation to move the PTZ device to it's "home" position. 290 | 291 | * `get_ptz()` - Operation to request PTZ status. 292 | 293 | * `set_preset(preset_name)` - The command saves the current device position parameters. 294 | - preset_name (str): Name for preset. 295 | 296 | * `get_preset()` - Operation to request all PTZ presets. 297 | 298 | * `get_preset_complete()` - Operation to request all PTZ presets. 299 | 300 | * `remove_preset(preset_name)` - Operation to remove a PTZ preset. 301 | - preset_name (str): Preset name. 302 | 303 | * `go_to_preset(preset_position)` - Operation to go to a saved preset position. 304 | - preset_position (str): preset name. 305 | 306 | ### Onvif Config 307 | * `set_user(name, password, user_level)` - This operation updates the settings for one or several users on a device for authentication purposes. 308 | - name (str): user name. 309 | - password (str): user password. 310 | - user_level (str): user level. 311 | 312 | * `create_user(username, password, user_level)` - This operation creates new device users and corresponding credentials on a device for authentication. 313 | - username (str): user name. 314 | - password (str): user password. 315 | - user_level (str): user level. 316 | 317 | * `delete_users(username)` - This operation deletes users on a device. 318 | - username (str): user name. 319 | 320 | * `get_users()` - This operation lists the registered users and corresponding credentials on a device. 321 | 322 | * `set_discovery_mode(discovery_mode)` - This operation sets the discovery mode operation of a device. 323 | - discovery_mode (str): Indicator of discovery mode. (Discoverable, NonDiscoverable) 324 | 325 | * `set_dns(type_dns, ipv4, ipv6)` - This operation sets the DNS settings on a device. 326 | - type_dns (str): Indicates if the address is an IPv4 or IPv6 address. 327 | - ipv4 (str): IPv4 address. 328 | - ipv6 (str): IPv6 address. 329 | 330 | * `get_hostname()` - This operation is used to get the hostname from a device. 331 | 332 | * `set_hostname(new_hostname)` - This operation sets the hostname on a device. 333 | - new_hostname (str): new hostname. 334 | 335 | * ` get_ip_address_filter()` - This operation gets the IP address filter settings from a device. 336 | 337 | * `get_device_information()` - This operation gets basic device information from the device. 338 | 339 | * `get_discovery_mode()` -This operation gets the discovery mode of a device. 340 | 341 | * `get_dns()` - This operation gets the DNS settings from a device. 342 | 343 | * `get_dynamic_dns()` - This operation gets the dynamic DNS settings from a device. 344 | 345 | * `get_network_default_gateway()` - This operation gets the default gateway settings from a device. 346 | 347 | * `get_network_interfaces()` - This operation gets the network interface configuration from a device. 348 | 349 | * `get_network_protocols()` - This operation gets defined network protocols from a device. 350 | 351 | * `get_ntp()` - This operation gets the NTP settings from a device. 352 | 353 | * `get_system_date_and_time()` - This operation gets the device system date and time. 354 | 355 | * `get_wsdl_url()` - Request a URL that can be used to retrieve the complete schema and WSDL definitions of a device. 356 | 357 | * `system_reboot()` - This operation reboots the device. 358 | 359 | * `start_system_restore()` - This operation initiates a system restore from backed up configuration data using the HTTP POST mechanism. 360 | 361 | * `get_profiles()` - This command lists all configured video profiles in a device. 362 | 363 | * `get_audio_decoder_configurations()` - This operation requests decoder configuration. 364 | 365 | * `get_video_analytics_configurations()` - This operation fetches the video analytics configuration. 366 | 367 | * `get_video_encoder_configurations()` - This operation request the encoder configuration. 368 | 369 | * `get_video_source_configurations()` - This operation request the video source configuration. 370 | 371 | * `get_video_sources()` - This operation lists all available physical video inputs of the device. 372 | -------------------------------------------------------------------------------- /sensecam_control/vapix_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Library for configuring AXIS cameras 3 | """ 4 | import urllib.parse 5 | import datetime 6 | import requests 7 | from bs4 import BeautifulSoup 8 | from requests.auth import HTTPDigestAuth 9 | 10 | 11 | # pylint: disable= #R0904 12 | # pylint: disable= #R0914 13 | 14 | 15 | class CameraConfiguration: 16 | """ 17 | Module for configuration cameras AXIS 18 | """ 19 | 20 | def __init__(self, ip, user, password): 21 | self.cam_ip = ip 22 | self.cam_user = user 23 | self.cam_password = password 24 | 25 | def factory_reset_default(self): # 5.1.3 26 | """ 27 | Reload factory default. All parameters except Network.BootProto, Network.IPAddress, 28 | Network. SubnetMask, Network.Broadcast and Network.DefaultRouter are set to their factory 29 | default values. 30 | 31 | Returns: 32 | Success (OK) or Failure (Settings or syntax are probably incorrect). 33 | 34 | 35 | """ 36 | url = 'http://' + self.cam_ip + '/axis-cgi/factorydefault.cgi' 37 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 38 | 39 | if resp.status_code == 200: 40 | return resp.text 41 | 42 | text = str(resp) 43 | text += str(resp.text) 44 | return text 45 | 46 | def hard_factory_reset_default(self): # 5.1.4 47 | """ 48 | Reload factory default. All parameters are set to their factory default value. 49 | 50 | Returns: 51 | Success (OK) or Failure (error and description). 52 | 53 | """ 54 | url = 'http://' + self.cam_ip + '/axis-cgi/hardfactorydefault.cgi' 55 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 56 | 57 | if resp.status_code == 200: 58 | return resp.text 59 | 60 | text = str(resp) 61 | text += str(resp.text) 62 | return text 63 | 64 | def restart_server(self): # 5.1.6 65 | """ 66 | Restart server. 67 | 68 | Returns: 69 | Success (OK) or Failure (error and description). 70 | 71 | """ 72 | url = 'http://' + self.cam_ip + '/axis-cgi/restart.cgi' 73 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 74 | 75 | if resp.status_code == 200: 76 | return resp.text 77 | 78 | text = str(resp) 79 | text += str(resp.text) 80 | return text 81 | 82 | def get_server_report(self): # 5.1.7 83 | """ 84 | This CGI request generates and returns a server report. This report is useful as an 85 | input when requesting support. The report includes product information, parameter 86 | settings and system logs. 87 | 88 | Returns: 89 | Success (OK and server report content text) or Failure (error and description). 90 | 91 | """ 92 | url = 'http://' + self.cam_ip + '/axis-cgi/serverreport.cgi' 93 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 94 | 95 | if resp.status_code == 200: 96 | return resp.text 97 | 98 | text = str(resp) 99 | text += str(resp.text) 100 | return text 101 | 102 | def get_system_log(self): # 5.1.8.1 103 | """ 104 | Retrieve system log information. The level of information included in the log is set 105 | in the Log. System parameter group. 106 | 107 | Returns: 108 | Success (OK and system log content text) or Failure (error and description). 109 | 110 | """ 111 | url = 'http://' + self.cam_ip + '/axis-cgi/systemlog.cgi' 112 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 113 | 114 | if resp.status_code == 200: 115 | return resp.text 116 | 117 | text = str(resp) 118 | text += str(resp.text) 119 | return text 120 | 121 | def get_system_access_log(self): # 5.1.8.2 122 | """ 123 | Retrieve client access log information. The level of information included in the log 124 | is set in the Log.Access parameter group. 125 | 126 | Returns: 127 | Success (OK and access log content text) or Failure (error and description). 128 | 129 | """ 130 | url = 'http://' + self.cam_ip + '/axis-cgi/accesslog.cgi' 131 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 132 | 133 | if resp.status_code == 200: 134 | return resp.text 135 | 136 | text = str(resp) 137 | text += str(resp.text) 138 | return text 139 | 140 | def get_date_and_time(self): # 5.1.9.1 141 | """ 142 | Get the system date and time. 143 | 144 | Returns: 145 | Success (OK and time and date content text) or Failure (error and description). 146 | example: , :: 147 | Error example: Request failed: 148 | 149 | """ 150 | url = 'http://' + self.cam_ip + '/axis-cgi/date.cgi?action=get' 151 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 152 | 153 | if resp.status_code == 200: 154 | return resp.text 155 | 156 | text = str(resp) 157 | text += str(resp.text) 158 | return text 159 | 160 | def set_date(self, year_date: int = None, month_date: int = None, 161 | day_date: int = None): # 5.1.9.2 162 | """ 163 | Change the system date. 164 | 165 | Args: 166 | year_date: current year. 167 | month_date: current month. 168 | day_date: current day. 169 | 170 | Returns: 171 | Success (OK) or Failure (Request failed: ). 172 | 173 | """ 174 | payload = { 175 | 'action': 'set', 176 | 'year': year_date, 177 | 'month': month_date, 178 | 'day': day_date 179 | } 180 | 181 | url = 'http://' + self.cam_ip + '/axis-cgi/date.cgi' 182 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 183 | params=payload) 184 | 185 | if resp.status_code == 200: 186 | return resp.text 187 | 188 | text = str(resp) 189 | text += str(resp.text) 190 | return text 191 | 192 | def set_time(self, hour: int = None, minute: int = None, second: int = None, 193 | timezone: str = None): # 5.1.9.2 194 | """ 195 | Change the system time. 196 | 197 | Args: 198 | hour: current hour. 199 | minute: current minute. 200 | second: current second. 201 | timezone: specifies the time zone that the new date and/or time is given in. The camera 202 | translates the time into local time using whichever time zone has been specified through 203 | the web configuration. 204 | 205 | Returns: 206 | Success (OK) or Failure (Request failed: ). 207 | 208 | """ 209 | payload = { 210 | 'action': 'set', 211 | 'hour': hour, 212 | 'minute': minute, 213 | 'second': second, 214 | 'timezone': timezone 215 | } 216 | 217 | url = 'http://' + self.cam_ip + '/axis-cgi/date.cgi' 218 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 219 | params=payload) 220 | 221 | if resp.status_code == 200: 222 | return resp.text 223 | 224 | text = str(resp) 225 | text += str(resp.text) 226 | return text 227 | 228 | def get_image_size(self): # 5.2.1 229 | """ 230 | Retrieve the actual image size with default image settings or with given parameters. 231 | 232 | Returns: 233 | Success (OK and image size content text) or Failure (Error and description). 234 | example: 235 | image width = 236 | image height = 237 | """ 238 | url = 'http://' + self.cam_ip + '/axis-cgi/imagesize.cgi?camera=1' 239 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 240 | 241 | if resp.status_code == 200: 242 | # vector = resp.text.split() 243 | # print(vector[3], 'x', vector[7]) 244 | return resp.text 245 | 246 | text = str(resp) 247 | text += str(resp.text) 248 | return text 249 | 250 | def get_video_status(self, camera_status: int = None): # 5.2.2 251 | """ 252 | Video encoders only. Check the status of one or more video sources. 253 | 254 | Args: 255 | camera_status: video source 256 | 257 | Returns: 258 | Success (OK and video status content text) or Failure (Error and description). 259 | example: 260 | Video 1 = 261 | """ 262 | payload = { 263 | 'status': camera_status 264 | } 265 | url = 'http://' + self.cam_ip + '/axis-cgi/videostatus.cgi?' 266 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 267 | params=payload) 268 | 269 | if resp.status_code == 200: 270 | return resp.text 271 | 272 | text = str(resp) 273 | text += str(resp.text) 274 | return text 275 | 276 | def get_bitmap_request(self, resolution: str = None, camera: str = None, 277 | square_pixel: int = None): # 5.2.3.1 278 | """ 279 | Request a bitmap image. 280 | 281 | Args: 282 | resolution: resolution of the returned image. Check the product’s Release notes for 283 | supported resolutions. 284 | camera: select a video source or the quad stream. 285 | square_pixel: enable/disable square pixel correction. Applies only to video encoders. 286 | 287 | Returns: 288 | Success ('image save' and save the image in the file folder) or Failure (Error and 289 | description). 290 | 291 | """ 292 | payload = { 293 | 'resolution': resolution, 294 | 'camera': camera, 295 | 'square_pixel': square_pixel 296 | } 297 | url = 'http://' + self.cam_ip + '/axis-cgi/bitmap/image.bmp' 298 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 299 | params=payload) 300 | 301 | if resp.status_code == 200: 302 | now = datetime.datetime.now() 303 | with open(str(now.strftime("%d-%m-%Y_%Hh%Mm%Ss")) + ".bmp", 'wb') as var: 304 | var.write(resp.content) 305 | return str('Image saved') 306 | 307 | text = str(resp) 308 | text += str(resp.text) 309 | return text 310 | 311 | def get_jpeg_request(self, resolution: str = None, camera: str = None, 312 | square_pixel: int = None, compression: int = None, 313 | clock: int = None, date: int = None, text: int = None, 314 | text_string: str = None, text_color: str = None, 315 | text_background_color: str = None, rotation: int = None, 316 | text_position: str = None, overlay_image: int = None, 317 | overlay_position: str = None): # 5.2.4.1 318 | """ 319 | The requests specified in the JPEG/MJPG section are supported by those video products 320 | that use JPEG and MJPG encoding. 321 | 322 | Args: 323 | resolution: Resolution of the returned image. Check the product’s Release notes. 324 | camera: Selects the source camera or the quad stream. 325 | square_pixel: Enable/disable square pixel correction. Applies only to video encoders. 326 | compression: Adjusts the compression level of the image. 327 | clock: Shows/hides the time stamp. (0 = hide, 1 = show) 328 | date: Shows/hides the date. (0 = hide, 1 = show) 329 | text: Shows/hides the text. (0 = hide, 1 = show) 330 | text_string: The text shown in the image, the string must be URL encoded. 331 | text_color: The color of the text shown in the image. (black, white) 332 | text_background_color: The color of the text background shown in the image. 333 | (black, white, transparent, semitransparent) 334 | rotation: Rotate the image clockwise. 335 | text_position: The position of the string shown in the image. (top, bottom) 336 | overlay_image: Enable/disable overlay image.(0 = disable, 1 = enable) 337 | overlay_position:The x and y coordinates defining the position of the overlay image. 338 | (x) 339 | 340 | Returns: 341 | Success ('image save' and save the image in the file folder) or Failure (Error and 342 | description). 343 | 344 | """ 345 | payload = { 346 | 'resolution': resolution, 347 | 'camera': camera, 348 | 'square_pixel': square_pixel, 349 | 'compression': compression, 350 | 'clock': clock, 351 | 'date': date, 352 | 'text': text, 353 | 'text_string': text_string, 354 | 'text_color': text_color, 355 | 'text_background_color': text_background_color, 356 | 'rotation': rotation, 357 | 'text_position': text_position, 358 | 'overlay_image': overlay_image, 359 | 'overlay_position': overlay_position 360 | } 361 | url = 'http://' + self.cam_ip + '/axis-cgi/jpg/image.cgi' 362 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 363 | params=payload) 364 | 365 | if resp.status_code == 200: 366 | now = datetime.datetime.now() 367 | with open(str(now.strftime("%d-%m-%Y_%Hh%Mm%Ss")) + ".jpg", 'wb') as var: 368 | var.write(resp.content) 369 | return str('Image saved') 370 | 371 | text = str(resp) 372 | text += str(resp.text) 373 | return text 374 | 375 | def get_type_camera(self): 376 | """ 377 | Request type camera. 378 | 379 | Returns: 380 | return type camera, Network camera or ptz camera 381 | 382 | """ 383 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi?action=list&group=Brand.ProdType' 384 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 385 | 386 | if resp.status_code == 200: 387 | vector = resp.text.split('=') 388 | return vector[1].replace('\r', '') 389 | 390 | text = str(resp) 391 | text += str(resp.text) 392 | return text 393 | 394 | def get_dynamic_text_overlay(self): # 5.2.5.1 395 | """ 396 | Get dynamic text overlay in the image. 397 | 398 | Returns: 399 | Success (dynamic text overlay) or Failure (Error and description). 400 | 401 | """ 402 | url = 'http://' + self.cam_ip + '/axis-cgi/dynamicoverlay.cgi?action=gettext' 403 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password)) 404 | 405 | if resp.status_code == 200: 406 | return resp.text 407 | 408 | text = str(resp) 409 | text += str(resp.text) 410 | return text 411 | 412 | def set_dynamic_text_overlay(self, text: str = None, camera: str = None): # 5.2.5.1 413 | """ 414 | Set dynamic text overlay in the image. 415 | 416 | Args: 417 | text: text to set overlay 418 | camera: select video source or the quad stream. ( default: default camera) 419 | 420 | Returns: 421 | OK if the camera set text overlay or error and description 422 | 423 | """ 424 | payload = { 425 | 'action': 'settext', 426 | 'text': text, 427 | 'camera': camera 428 | } 429 | 430 | url = 'http://' + self.cam_ip + '/axis-cgi/dynamicoverlay.cgi' 431 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 432 | params=payload) 433 | 434 | if resp.status_code == 200: 435 | return resp.text 436 | 437 | soup = BeautifulSoup(resp.text, features="lxml") 438 | text2 = str(resp) 439 | text2 += str(soup.get_text()) 440 | return text2 441 | 442 | def check_profile(self, name: str = None): # 0 443 | """ 444 | Check if the profile exists 445 | 446 | Args: 447 | name: profile name 448 | 449 | Returns: 450 | Return 1 or 0 451 | 452 | """ 453 | payload = { 454 | 'action': 'list', 455 | 'group': 'root.StreamProfile' 456 | } 457 | 458 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 459 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 460 | params=payload) 461 | 462 | text2 = resp.text.split('\n') 463 | if resp.status_code == 200: 464 | for i, _ in enumerate(text2): 465 | text3 = text2[i].split('Name=') 466 | if len(text3) > 1 and text3[1] == name: 467 | return 1 468 | return 0 469 | 470 | soup = BeautifulSoup(resp.text, features="lxml") 471 | text2 = str(resp) 472 | text2 += str(soup.get_text()) 473 | return text2 474 | 475 | def create_profile(self, name: str, *, resolution: str = None, video_codec: str = None, 476 | fps: int = None, compression: int = None, h264_profile: str = None, 477 | gop: int = None, bitrate: int = None, bitrate_priority: str = None): 478 | """ 479 | Create stream profile. 480 | 481 | Args: 482 | name: profile name (str) 483 | resolution: resolution. (str : "1920x1080") 484 | video_codec: video codec. (str : "h264") 485 | fps: frame rate. 486 | compression: axis compression. 487 | h264_profile: profile h264. (str: "high") 488 | gop: Group of pictures. 489 | bitrate: video bitrate. 490 | bitrate_priority: video bitrate priority. 491 | 492 | Returns: 493 | Profile code and OK if the profile create or error and description. 494 | 495 | """ 496 | if self.check_profile(name): 497 | return name + ' already exists. Remove the previous profile or change the name of ' \ 498 | 'the profile to be created.' 499 | 500 | params = { 501 | 'resolution': resolution, 502 | 'videocodec': video_codec, 503 | 'fps': fps, 504 | 'compression': compression, 505 | 'h264profile': h264_profile, 506 | 'videokeyframeinterval': gop, 507 | 'videobitrate': bitrate, 508 | 'videobitratepriority': bitrate_priority 509 | } 510 | 511 | params_filtred = {key: value for (key, value) in params.items() if value is not None} 512 | text_params = urllib.parse.urlencode(params_filtred) 513 | payload = { 514 | 'action': 'add', 515 | 'template': 'streamprofile', 516 | 'group': 'StreamProfile', 517 | 'StreamProfile.S.Name': name, 518 | 'StreamProfile.S.Parameters': text_params 519 | } 520 | 521 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 522 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 523 | params=payload) 524 | 525 | soup = BeautifulSoup(resp.text, features="lxml") 526 | if resp.status_code == 200: 527 | return soup.body.get_text() 528 | 529 | text2 = str(resp) 530 | text2 += str(soup.get_text()) 531 | return text2 532 | 533 | def create_user(self, user: str, password: str, sgroup: str, *, group: str = 'users', comment: str = None): 534 | # 5.1.2 535 | """ 536 | Create user. 537 | 538 | Args: 539 | user: user name 540 | password: password 541 | group: An existing primary group name of the account. 542 | sgroup: security group (admin, operator, viewer) 543 | comment: user description 544 | 545 | Returns: 546 | Success (Created account ) or Failure (Error and description). 547 | 548 | """ 549 | if self.check_user(user): 550 | return user + ' already exists.' 551 | 552 | if sgroup == 'admin': 553 | sgroup = 'admin:operator:viewer:ptz' 554 | elif sgroup == 'operator': 555 | sgroup = 'operator:viewer:ptz' 556 | elif sgroup == 'ptz': 557 | sgroup = 'viewer:ptz' 558 | 559 | payload = { 560 | 'action': 'add', 561 | 'user': user, 562 | 'pwd': password, 563 | 'grp': group, 564 | 'sgrp': sgroup, 565 | 'comment': comment 566 | } 567 | 568 | url = 'http://' + self.cam_ip + '/axis-cgi/pwdgrp.cgi' 569 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 570 | params=payload) 571 | 572 | soup = BeautifulSoup(resp.text, features="lxml") 573 | if resp.status_code == 200: 574 | return soup.body.get_text() 575 | 576 | text2 = str(resp) 577 | text2 += str(soup.get_text()) 578 | return text2 579 | 580 | def update_user(self, user: str, *, password: str = None, group: str = 'users', 581 | sgroup: str = None, comment: str = None): # 5.1.2 582 | """ 583 | Update user params. 584 | 585 | Args: 586 | user: user name 587 | password: new password or current password to change others params. 588 | group: An existing primary group name of the account. 589 | sgroup: security group. (admin, operator, viewer) 590 | comment: user description. 591 | 592 | Returns: 593 | Success (OK) or Failure (Error and description). 594 | 595 | """ 596 | if not self.check_user(user): 597 | return user + ' does not exists.' 598 | 599 | if sgroup == 'admin': 600 | sgroup = 'admin:operator:viewer:ptz' 601 | elif sgroup == 'operator': 602 | sgroup = 'operator:viewer:ptz' 603 | elif sgroup == 'ptz': 604 | sgroup = 'viewer:ptz' 605 | 606 | payload = { 607 | 'action': 'update', 608 | 'user': user, 609 | 'pwd': password, 610 | 'grp': group, 611 | 'sgrp': sgroup, 612 | 'comment': comment 613 | } 614 | url = 'http://' + self.cam_ip + '/axis-cgi/pwdgrp.cgi' 615 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 616 | params=payload) 617 | 618 | soup = BeautifulSoup(resp.text, features="lxml") 619 | if resp.status_code == 200: 620 | return soup.body.get_text() 621 | 622 | text2 = str(resp) 623 | text2 += str(soup.get_text()) 624 | return text2 625 | 626 | def remove_user(self, user: str): # 5.1.2 627 | """ 628 | Remove user. 629 | Args: 630 | user: user name 631 | 632 | Returns: 633 | Success (OK) or Failure (Error and description). 634 | """ 635 | if not self.check_user(user): 636 | return user + 'does not exists.' 637 | 638 | payload = { 639 | 'action': 'remove', 640 | 'user': user 641 | } 642 | 643 | url = 'http://' + self.cam_ip + '/axis-cgi/pwdgrp.cgi' 644 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 645 | params=payload) 646 | 647 | soup = BeautifulSoup(resp.text, features="lxml") 648 | if resp.status_code == 200: 649 | return soup.body.get_text() 650 | 651 | text2 = str(resp) 652 | text2 += str(soup.get_text()) 653 | return text2 654 | 655 | def check_user(self, name: str): # 0 656 | """ 657 | Check if user exists 658 | Args: 659 | name: user name 660 | 661 | Returns: 662 | Success (0 = doesn't exist, 1 exist) or Failure (Error and description). 663 | """ 664 | payload = { 665 | 'action': 'get' 666 | } 667 | url = 'http://' + self.cam_ip + '/axis-cgi/pwdgrp.cgi' 668 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 669 | params=payload) 670 | 671 | if resp.status_code == 200: 672 | text2 = resp.text.split('\n') 673 | for i, _ in enumerate(text2): # for i in range(len(text2)): 674 | text3 = text2[i].split('users=') 675 | if len(text3) > 1: 676 | text4 = text3[1].replace('"', '').replace('\r', '').split(',') 677 | for j, _ in enumerate(text4): 678 | if text4[j] == name: 679 | return 1 680 | return 0 681 | 682 | soup = BeautifulSoup(resp.text, features="lxml") 683 | text2 = str(resp) 684 | text2 += str(soup.resp_text()) 685 | return text2 686 | 687 | def set_hostname(self, hostname: str = None, *, set_dhcp: str = None): # 0 688 | """ 689 | Configure how the device selects a hostname, with the possibility to set a static hostname and/or enable 690 | auto-configuration by DHCP. 691 | 692 | Args: 693 | hostname: hostname 694 | set_dhcp: auto-configuration by DHCP. (yes, no) 695 | 696 | Returns: 697 | Success (OK) or Failure (Error and description). 698 | 699 | """ 700 | payload = { 701 | 'action': 'update', 702 | 'Network.HostName': hostname, 703 | 'Network.VolatileHostName.ObtainFromDHCP': set_dhcp 704 | } 705 | 706 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 707 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 708 | params=payload) 709 | 710 | if resp.status_code == 200: 711 | return resp.text 712 | 713 | soup = BeautifulSoup(resp.text, features="lxml") 714 | text2 = str(resp) 715 | text2 += str(soup.get_text()) 716 | return text2 717 | 718 | def set_stabilizer(self, stabilizer: str = None, *, stabilizer_margin: int = None): # 0 719 | """ 720 | Set electronic image stabilization (EIS). 721 | 722 | Args: 723 | stabilizer: stabilizer value ("on" or "off") 724 | stabilizer_margin: stabilization margin (0 to 200) 725 | 726 | Returns: 727 | Success (OK) or Failure (Error and description). 728 | 729 | """ 730 | payload = { 731 | 'action': 'update', 732 | 'ImageSource.I0.Sensor.Stabilizer': stabilizer, 733 | 'ImageSource.I0.Sensor.StabilizerMargin': stabilizer_margin # 0 a 200 734 | } 735 | 736 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 737 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 738 | params=payload) 739 | 740 | if resp.status_code == 200: 741 | return resp.text 742 | 743 | soup = BeautifulSoup(resp.text, features="lxml") 744 | text2 = str(resp) 745 | text2 += str(soup.get_text()) 746 | return text2 747 | 748 | def set_capture_mode(self, capture_mode: str = None): 749 | """ 750 | Set capture mode. 751 | 752 | Args: 753 | capture_mode: capture mode. (1 = 1080, 2 = 720 - camera Full HD) 754 | 755 | Returns: 756 | Success (OK) or Failure (Error and description). 757 | 758 | """ 759 | payload = { 760 | 'action': 'update', 761 | 'ImageSource.I0.Sensor': capture_mode 762 | } 763 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 764 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 765 | params=payload) 766 | 767 | if resp.status_code == 200: 768 | return resp.text 769 | 770 | soup = BeautifulSoup(resp.text, features="lxml") 771 | text2 = str(resp) 772 | text2 += str(soup.get_text()) 773 | return text2 774 | 775 | def set_wdr(self, wdr: str = None, *, contrast: int = None): 776 | """ 777 | WDR - Forensic Capture - Wide Dynamic Range can improve the exposure when there is a 778 | considerable contrast between light and dark areas in an image. 779 | Args: 780 | wdr: WDR value (on, off) 781 | contrast: contrast level. 782 | 783 | Returns: 784 | Success (OK) or Failure (Error and description). 785 | 786 | """ 787 | payload = { 788 | 'action': 'update', 789 | 'ImageSource.I0.Sensor.WDR': wdr, 790 | 'ImageSource.I0.Sensor.LocalContrast': contrast 791 | } 792 | 793 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 794 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 795 | params=payload) 796 | 797 | if resp.status_code == 200: 798 | return resp.text 799 | 800 | soup = BeautifulSoup(resp.text, features="lxml") 801 | text2 = str(resp) 802 | text2 += str(soup.get_text()) 803 | return text2 804 | 805 | def set_appearance(self, *, brightness: int = None, contrast: int = None, 806 | saturation: int = None, sharpness: int = None): 807 | """ 808 | Image Appearance Setting. 809 | 810 | Args: 811 | brightness: adjusts the image brightness. 812 | contrast: adjusts the image's contrast. 813 | saturation: adjusts color saturation. (Color level) 814 | sharpness: controls the amount of sharpening applied to the image. 815 | 816 | Returns: 817 | Success (OK) or Failure (Error and description). 818 | 819 | """ 820 | payload = { 821 | 'action': 'update', 822 | 'ImageSource.I0.Sensor.Brightness': brightness, 823 | 'ImageSource.I0.Sensor.ColorLevel': saturation, 824 | 'ImageSource.I0.Sensor.Sharpness': sharpness, 825 | 'ImageSource.I0.Sensor.Contrast': contrast 826 | } 827 | 828 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 829 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 830 | params=payload) 831 | 832 | if resp.status_code == 200: 833 | return resp.text 834 | 835 | soup = BeautifulSoup(resp.text, features="lxml") 836 | text2 = str(resp) 837 | text2 += str(soup.get_text()) 838 | return text2 839 | 840 | def set_ir_cut_filter(self, ir_cut: str = None, *, shift_level: int = None): 841 | """ 842 | IR cut filter settings. 843 | 844 | Args: 845 | ir_cut: IR value. (Off to allow the camera to 'see' infrared light, set to On during 846 | daylight or bright light conditions to cut out infrared light, Automatic the camera will 847 | automatically switch between On and Off, according to the current lighting conditions) 848 | shift_level: This setting can be used to change when the camera shifts into night mode. 849 | 850 | Returns: 851 | Success (OK) or Failure (Error and description). 852 | 853 | """ 854 | payload = { 855 | 'action': 'update', 856 | 'ImageSource.I0.DayNight.IrCutFilter': ir_cut, 857 | 'ImageSource.I0.DayNight.ShiftLevel': shift_level 858 | } 859 | 860 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 861 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), params=payload) 862 | 863 | if resp.status_code == 200: 864 | return resp.text 865 | 866 | text2 = str(resp) 867 | text2 += str(resp.text) 868 | return text2 869 | 870 | # "flickerfree60" "flickerfree50" "flickerreduced60" "flickerreduced50" "auto" "hold" 871 | # "auto" "center" "spot"(pontual) "upper" "lower" "left" "right" "custom" 872 | def set_exposure(self, *, exposure: str = None, exposure_window: str = None, 873 | max_exposure_time: int = None, 874 | max_gain: int = None, exposure_priority_normal: int = None, 875 | lock_aperture: str = None, exposure_value: int = None): 876 | """ 877 | Exposure Settings. 878 | 879 | Args: 880 | exposure: exposure mode. (flickerfree60, flickerfree50, flickerreduced60, 881 | flickerreduced50, auto, hold) 882 | exposure_window: This setting determines which part of the image will be used to 883 | calculate the exposure. (auto, center, spot, upper, lower, left, right, custom) 884 | max_exposure_time: maximum shutter time (MS) 885 | max_gain: maximum gain 886 | exposure_priority_normal: commitment blur / noise 887 | lock_aperture: lock the shutter aperture 888 | exposure_value: exposure level 889 | 890 | Returns: 891 | Success (OK) or Failure (Error and description). 892 | 893 | """ 894 | payload = { 895 | 'action': 'update', 896 | 'ImageSource.I0.Sensor.Exposure': exposure, # modo de exposição (exposure) 897 | 'ImageSource.I0.Sensor.ExposureWindow': exposure_window, # zona de exposição 898 | 'ImageSource.I0.Sensor.MaxExposureTime': max_exposure_time, # Obturador maximo em MS 899 | 'ImageSource.I0.Sensor.MaxGain': max_gain, # ganho maximo 900 | 'ImageSource.I0.Sensor.ExposurePriorityNormal': exposure_priority_normal, 901 | # compromisso desfoque/ruido 902 | 'ImageSource.I0.DCIris.Enable': lock_aperture, # travar abertura - yes or no 903 | 'ImageSource.I0.Sensor.ExposureValue': exposure_value # nivel de exposição 904 | } 905 | 906 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 907 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 908 | params=payload) 909 | 910 | if resp.status_code == 200: 911 | return resp.text 912 | 913 | text2 = str(resp) 914 | text2 += str(resp.text) 915 | return text2 916 | 917 | def set_custom_exposure_window(self, top: int = None, bottom: int = None, left: int = None, 918 | right: int = None): 919 | """ 920 | Set custom exposition zone. 921 | 922 | Args: 923 | top: upper limit 924 | bottom: lower limit 925 | left: left limit 926 | right: right limit 927 | 928 | Returns: 929 | Success (OK) or Failure (Error and description). 930 | 931 | """ 932 | # se passar como pixel atualizar para os valores de 0 a 9999 933 | payload = { 934 | 'action': 'update', 935 | 'ImageSource.I0.Sensor.CustomExposureWindow.C0.Top': top, 936 | 'ImageSource.I0.Sensor.CustomExposureWindow.C0.Bottom': bottom, 937 | 'ImageSource.I0.Sensor.CustomExposureWindow.C0.Left': left, 938 | 'ImageSource.I0.Sensor.CustomExposureWindow.C0.Right': right 939 | } 940 | 941 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 942 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 943 | params=payload) 944 | 945 | if resp.status_code == 200: 946 | return resp.text 947 | 948 | text2 = str(resp) 949 | text2 += str(resp.text) 950 | return text2 951 | 952 | def set_backlight(self, backlight: str = None): 953 | """ 954 | Backlight compensation makes the subject appear clearer when the image background is too 955 | bright, or the subject is too dark. 956 | 957 | Args: 958 | backlight: backlight value. (true, false) 959 | 960 | Returns: 961 | Success (OK) or Failure (Error and description). 962 | 963 | """ 964 | payload = { 965 | 'action': 'update', 966 | 'PTZ.Various.V1.BackLight': backlight, 967 | 968 | } 969 | 970 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 971 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 972 | params=payload) 973 | 974 | if resp.status_code == 200: 975 | return resp.text 976 | 977 | text2 = str(resp) 978 | text2 += str(resp.text) 979 | return text2 980 | 981 | def set_highlight(self, highlight: int = None): 982 | """ 983 | The Axis product will detect a bright light from a source such as a torch or car headlights 984 | and mask that image area. This setting is useful when the camera operates in a very dark 985 | area where a bright light may overexpose part of the image and prevent the operator from 986 | seeing other parts of the scene. 987 | 988 | Args: 989 | highlight: highlight value. (0, 1) 990 | 991 | Returns: 992 | Success (OK) or Failure (Error and description). 993 | """ 994 | payload = { 995 | 'action': 'update', 996 | 'ImageSource.I0.Sensor.HLCSensitivity': highlight, 997 | 998 | } 999 | 1000 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 1001 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1002 | params=payload) 1003 | 1004 | if resp.status_code == 200: 1005 | return resp.text 1006 | 1007 | text2 = str(resp) 1008 | text2 += str(resp.text) 1009 | return text2 1010 | 1011 | def set_image_setings(self, *, defog: str = None, noise_reduction: str = None, 1012 | noise_reduction_tuning: int = None, image_freeze_ptz: str = None): 1013 | """ 1014 | Image Settings. 1015 | 1016 | Args: 1017 | defog: detect the fog effect and automatically remove it to get a clear image. (on, off) 1018 | noise_reduction: noise reduction function (on, off) 1019 | noise_reduction_tuning: Noise Reduction Adjustment level (0 to 100) 1020 | image_freeze_ptz: freeze the image while the camera is moving during a pan, tilt or zoom 1021 | operation. (on, off) 1022 | 1023 | Returns: 1024 | Success (OK) or Failure (Error and description). 1025 | 1026 | """ 1027 | payload = { 1028 | 'action': 'update', 1029 | 'ImageSource.I0.Sensor.Defog': defog, 1030 | 'ImageSource.I0.Sensor.NoiseReduction': noise_reduction, 1031 | 'ImageSource.I0.Sensor.NoiseReductionTuning': noise_reduction_tuning, 1032 | 'PTZ.UserAdv.U1.ImageFreeze': image_freeze_ptz 1033 | } 1034 | 1035 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 1036 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1037 | params=payload) 1038 | 1039 | if resp.status_code == 200: 1040 | return resp.text 1041 | 1042 | text2 = str(resp) 1043 | text2 += str(resp.text) 1044 | return text2 1045 | 1046 | def set_ntp_server(self, ntp_server: str = None): 1047 | """ 1048 | Configure NTP server. 1049 | Args: 1050 | ntp_server: link or IP server 1051 | 1052 | Returns: 1053 | Success (OK) or Failure (Error and description). 1054 | 1055 | """ 1056 | payload = { 1057 | 'action': 'update', 1058 | 'Time.NTP.Server': ntp_server, 1059 | } 1060 | 1061 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 1062 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1063 | params=payload) 1064 | 1065 | if resp.status_code == 200: 1066 | return resp.text 1067 | 1068 | text2 = str(resp) 1069 | text2 += str(resp.text) 1070 | return text2 1071 | 1072 | def set_pan_tilt_zoom_enable(self, *, pan_enable: str = None, tilt_enable: str = None, 1073 | zoom_enable: str = None): 1074 | """ 1075 | Turns PTZ control on and off. 1076 | 1077 | Args: 1078 | pan_enable: pan enabled value (true, false) 1079 | tilt_enable: tilt enabled value (true, false) 1080 | zoom_enable: zoom enabled value (true, false) 1081 | 1082 | Returns: 1083 | Success (OK) or Failure (Error and description). 1084 | 1085 | """ 1086 | payload = { 1087 | 'action': 'update', 1088 | 'PTZ.Various.V1.PanEnabled': pan_enable, 1089 | 'PTZ.Various.V1.TiltEnabled': tilt_enable, 1090 | 'PTZ.Various.V1.ZoomEnabled': zoom_enable 1091 | } 1092 | url = 'http://' + self.cam_ip + '/axis-cgi/param.cgi' 1093 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1094 | params=payload) 1095 | 1096 | if resp.status_code == 200: 1097 | return resp.text 1098 | 1099 | text2 = str(resp) 1100 | text2 += str(resp.text) 1101 | return text2 1102 | 1103 | def auto_focus(self, focus: str = None): # on or off 1104 | """ 1105 | Enable or disable automatic focus 1106 | 1107 | Args: 1108 | focus: focus value (on, off) 1109 | 1110 | Returns: 1111 | Success (OK) or Failure (Error and description). 1112 | 1113 | """ 1114 | payload = { 1115 | 'autofocus': focus 1116 | } 1117 | 1118 | url = 'http://' + self.cam_ip + '/axis-cgi/com/ptz.cgi' 1119 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1120 | params=payload) 1121 | 1122 | if resp.status_code == 200: 1123 | return resp.text 1124 | 1125 | text2 = str(resp) 1126 | text2 += str(resp.text) 1127 | return text2 1128 | 1129 | def auto_iris(self, iris: str = None): 1130 | """ 1131 | Enable or disable automatic iris control 1132 | 1133 | Args: 1134 | iris: iris value (on, off) 1135 | 1136 | Returns: 1137 | Success (OK) or Failure (Error and description). 1138 | 1139 | """ 1140 | payload = { 1141 | 'autoiris': iris 1142 | } 1143 | 1144 | url = 'http://' + self.cam_ip + '/axis-cgi/com/ptz.cgi' 1145 | resp = requests.get(url, auth=HTTPDigestAuth(self.cam_user, self.cam_password), 1146 | params=payload) 1147 | 1148 | if resp.status_code == 200: 1149 | return resp.text 1150 | 1151 | text2 = str(resp) 1152 | text2 += str(resp.text) 1153 | return text2 1154 | --------------------------------------------------------------------------------