├── .gitignore ├── LICENSE ├── README.md ├── move.py └── ptz_control.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 RichardoMrMu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-onvif 2 | test code to control hikcamera using onvif 3 | # Requirement 4 | 1. python-onvif-zeep 5 | 2. opencv-python 6 | ```shell 7 | pip install onvif_zeep 8 | pip install opencv-python 9 | ``` 10 | # Introduction 11 | The [move.py](https://github.com/RichardoMrMu/python-onvif/blob/main/move.py) can achieve move function like absolute move and relate move (including up,down,left and right four axises), meanwhile, it uses functional language. And the [ptz_control.py](https://github.com/RichardoMrMu/python-onvif/blob/main/move.py) can achieve get configurations, move, goto presets, etc. It uses oop(Object Oriented Programming). You can chooses the style you like. 12 | # Using 13 | For [move.py](https://github.com/RichardoMrMu/python-onvif/blob/main/move.py), you need to change [this line](https://github.com/RichardoMrMu/python-onvif/blob/a4277a9957ec30a96ce359404948710d6cb9421d/move.py#L77) as your ip, passwd and username. Then you can run 14 | ```shell 15 | python move.py 16 | ``` 17 | and see camera move successfully if connecting. And for [ptz_control.py](https://github.com/RichardoMrMu/python-onvif/blob/main/move.py), your should replace [this line](https://github.com/RichardoMrMu/python-onvif/blob/a4277a9957ec30a96ce359404948710d6cb9421d/ptz_control.py#L11) 's ip, username, passwd ans port as yours, and you can write your own main function like this : 18 | ```python 19 | if __name__ == '__main__': 20 | ptz = ptzControl() 21 | ptz.goto_preset() 22 | ptz.zoom_relative(0.5,0.4) 23 | ``` 24 | Then run like this : 25 | ```shell 26 | pyhton ptz_control.py 27 | ``` 28 | enjoy it! 29 | -------------------------------------------------------------------------------- /move.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2021/4/20 10:53 3 | # @Author : RichardoMu 4 | # @File : move.py 5 | # @Software: PyCharm 6 | 7 | from time import sleep 8 | 9 | from onvif import ONVIFCamera 10 | import zeep 11 | 12 | XMAX = 1 13 | XMIN = -1 14 | YMAX = 1 15 | YMIN = -1 16 | 17 | 18 | def zeep_pythonvalue(self, xmlvalue): 19 | return xmlvalue 20 | 21 | 22 | def perform_move(ptz, request, timeout): 23 | # Start continuous move 24 | ptz.ContinuousMove(request) 25 | # Wait a certain time 26 | sleep(timeout) 27 | # Stop continuous move 28 | ptz.Stop({'ProfileToken': request.ProfileToken}) 29 | 30 | 31 | def move_up(ptz, request, timeout=1): 32 | print('move up...') 33 | request.Velocity.PanTilt.x = 0 34 | request.Velocity.PanTilt.y = YMAX 35 | perform_move(ptz, request, timeout) 36 | 37 | 38 | def move_down(ptz, request, timeout=1): 39 | print('move down...') 40 | request.Velocity.PanTilt.x = 0 41 | request.Velocity.PanTilt.y = YMIN 42 | perform_move(ptz, request, timeout) 43 | 44 | 45 | def move_right(ptz, request, timeout=1): 46 | print('move right...') 47 | request.Velocity.PanTilt.x = XMAX 48 | request.Velocity.PanTilt.y = 0 49 | 50 | perform_move(ptz, request, timeout) 51 | 52 | 53 | def move_left(ptz, request, timeout=1): 54 | print('move left...') 55 | request.Velocity.PanTilt.x = XMIN 56 | request.Velocity.PanTilt.y = 0 57 | perform_move(ptz, request, timeout) 58 | 59 | 60 | def zoom_up(ptz,request,timeout=1): 61 | print('zoom up') 62 | request.Velocity.Zoom.x = 1 63 | request.Velocity.PanTilt.x = 0 64 | request.Velocity.PanTilt.y = 0 65 | perform_move(ptz,request,timeout) 66 | 67 | 68 | def zoom_dowm(ptz,request,timeout=1): 69 | print('zoom down') 70 | request.Velocity.Zoom.x = -1 71 | request.Velocity.PanTilt.x = 0 72 | request.Velocity.PanTilt.y = 0 73 | perform_move(ptz, request, timeout) 74 | 75 | 76 | def continuous_move(): 77 | mycam = ONVIFCamera('192.168.66.64', 80, 'admin', 'wst123456') 78 | # Create media service object 79 | media = mycam.create_media_service() 80 | # Create ptz service object 81 | ptz = mycam.create_ptz_service() 82 | 83 | # Get target profile 84 | zeep.xsd.simple.AnySimpleType.pythonvalue = zeep_pythonvalue 85 | media_profile = media.GetProfiles()[0] 86 | 87 | # Get PTZ configuration options for getting continuous move range 88 | request = ptz.create_type('GetConfigurationOptions') 89 | request.ConfigurationToken = media_profile.PTZConfiguration.token 90 | ptz_configuration_options = ptz.GetConfigurationOptions(request) 91 | 92 | request = ptz.create_type('ContinuousMove') 93 | request.ProfileToken = media_profile.token 94 | ptz.Stop({'ProfileToken': media_profile.token}) 95 | 96 | if request.Velocity is None: 97 | request.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position 98 | request.Velocity = ptz.GetStatus({'ProfileToken': media_profile.token}).Position 99 | request.Velocity.PanTilt.space = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].URI 100 | request.Velocity.Zoom.space = ptz_configuration_options.Spaces.ContinuousZoomVelocitySpace[0].URI 101 | 102 | # Get range of pan and tilt 103 | # NOTE: X and Y are velocity vector 104 | global XMAX, XMIN, YMAX, YMIN 105 | XMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Max 106 | XMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].XRange.Min 107 | YMAX = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Max 108 | YMIN = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].YRange.Min 109 | 110 | 111 | for i in range(10): 112 | zoom_up(ptz,request) 113 | 114 | for i in range(10): 115 | zoom_dowm(ptz,request) 116 | # move right 117 | for i in range(10): 118 | move_right(ptz, request) 119 | 120 | # move left 121 | for i in range(10): 122 | 123 | move_left(ptz, request) 124 | 125 | # Move up 126 | for i in range(10): 127 | move_up(ptz, request) 128 | 129 | # move down 130 | for i in range(10): 131 | move_down(ptz, request) 132 | 133 | 134 | if __name__ == '__main__': 135 | continuous_move() 136 | -------------------------------------------------------------------------------- /ptz_control.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2021/4/20 14:57 3 | # @Author : RichardoMu 4 | # @File : ptz_control.py 5 | # @Software: PyCharm 6 | 7 | import sys 8 | from onvif import ONVIFCamera 9 | from time import sleep 10 | 11 | IP = "192.168.66.64" # Camera IP address 12 | PORT = 80 # Port 13 | USER = "admin" # Username 14 | PASS = "wst123456" # Password 15 | 16 | class ptzControl(object): 17 | def __init__(self): 18 | super(ptzControl, self).__init__() 19 | self.mycam = ONVIFCamera(IP,PORT,USER,PASS) 20 | # create media service object 21 | self.media = self.mycam.create_media_service() 22 | # Get target profile 23 | self.media_profile = self.media.GetProfiles()[0] 24 | # Use the first profile and Profiles have at least one 25 | token = self.media_profile.token 26 | # PTZ controls ------------------------------------------------------------- 27 | self.ptz = self.mycam.create_ptz_service() 28 | # Get available PTZ services 29 | request = self.ptz.create_type('GetServiceCapabilities') 30 | Service_Capabilities = self.ptz.GetServiceCapabilities(request) 31 | # Get PTZ status 32 | status = self.ptz.GetStatus({'ProfileToken': token}) 33 | 34 | # Get PTZ configuration options for getting option ranges 35 | request = self.ptz.create_type('GetConfigurationOptions') 36 | request.ConfigurationToken = self.media_profile.PTZConfiguration.token 37 | ptz_configuration_options = self.ptz.GetConfigurationOptions(request) 38 | 39 | # get continuousMove request -- requestc 40 | self.requestc = self.ptz.create_type('ContinuousMove') 41 | self.requestc.ProfileToken = self.media_profile.token 42 | if self.requestc.Velocity is None: 43 | self.requestc.Velocity = self.ptz.GetStatus({'ProfileToken': self.media_profile.token}).Position 44 | self.requestc.Velocity.PanTilt.space = ptz_configuration_options.Spaces.ContinuousPanTiltVelocitySpace[0].URI 45 | self.requestc.Velocity.Zoom.space = ptz_configuration_options.Spaces.ContinuousZoomVelocitySpace[0].URI 46 | 47 | # get absoluteMove request -- requesta 48 | self.requesta = self.ptz.create_type('AbsoluteMove') 49 | self.requesta.ProfileToken = self.media_profile.token 50 | if self.requesta.Position is None: 51 | self.requesta.Position = self.ptz.GetStatus( 52 | {'ProfileToken': self.media_profile.token}).Position 53 | if self.requesta.Speed is None: 54 | self.requesta.Speed = self.ptz.GetStatus( 55 | {'ProfileToken': self.media_profile.token}).Position 56 | 57 | # get relativeMove request -- requestr 58 | self.requestr = self.ptz.create_type('RelativeMove') 59 | self.requestr.ProfileToken = self.media_profile.token 60 | if self.requestr.Translation is None: 61 | self.requestr.Translation = self.ptz.GetStatus( 62 | {'ProfileToken': self.media_profile.token}).Position 63 | self.requestr.Translation.PanTilt.space = ptz_configuration_options.Spaces.RelativePanTiltTranslationSpace[0].URI 64 | self.requestr.Translation.Zoom.space = ptz_configuration_options.Spaces.RelativeZoomTranslationSpace[0].URI 65 | if self.requestr.Speed is None: 66 | self.requestr.Speed = self.ptz.GetStatus( 67 | {'ProfileToken': self.media_profile.token}).Position 68 | 69 | self.requests = self.ptz.create_type('Stop') 70 | self.requests.ProfileToken = self.media_profile.token 71 | self.requestp = self.ptz.create_type('SetPreset') 72 | self.requestp.ProfileToken = self.media_profile.token 73 | self.requestg = self.ptz.create_type('GotoPreset') 74 | self.requestg.ProfileToken = self.media_profile.token 75 | self.stop() 76 | 77 | # Stop pan, tilt and zoom 78 | def stop(self): 79 | self.requests.PanTilt = True 80 | self.requests.Zoom = True 81 | print(f"self.request:{self.requests}") 82 | self.ptz.Stop(self.requests) 83 | 84 | # Continuous move functions 85 | def perform_move(self, requestc): 86 | # Start continuous move 87 | ret = self.ptz.ContinuousMove(requestc) 88 | 89 | def move_tilt(self, velocity): 90 | self.requestc.Velocity.PanTilt.x = 0.0 91 | self.requestc.Velocity.PanTilt.y = velocity 92 | self.perform_move(self.requestc) 93 | 94 | def move_pan(self, velocity): 95 | self.requestc.Velocity.PanTilt.x = velocity 96 | self.requestc.Velocity.PanTilt.y = 0.0 97 | self.perform_move(self.requestc) 98 | 99 | def move_continuous(self, pan, tilt): 100 | self.requestc.Velocity.PanTilt.x = pan 101 | self.requestc.Velocity.PanTilt.y = tilt 102 | self.perform_move(self.requestc) 103 | 104 | def zoom(self, velocity): 105 | self.requestc.Velocity.Zoom.x = velocity 106 | self.perform_move(self.requestc) 107 | 108 | 109 | # Absolute move functions --NO ERRORS BUT CAMERA DOES NOT MOVE 110 | def move_abspantilt(self, pan, tilt, velocity): 111 | self.requesta.Position.PanTilt.x = pan 112 | self.requesta.Position.PanTilt.y = tilt 113 | self.requesta.Speed.PanTilt.x = velocity 114 | self.requesta.Speed.PanTilt.y = velocity 115 | ret = self.ptz.AbsoluteMove(self.requesta) 116 | 117 | # Relative move functions --NO ERRORS BUT CAMERA DOES NOT MOVE 118 | def move_relative(self, pan, tilt, velocity): 119 | self.requestr.Translation.PanTilt.x = pan 120 | self.requestr.Translation.PanTilt.y = tilt 121 | self.requestr.Speed.PanTilt = [velocity,velocity] 122 | # self.requestr.Speed.PanTilt.x = velocity 123 | # self.requestr.Speed.PanTilt.y = velocity 124 | self.requestr.Speed.Zoom = 0 125 | ret = self.ptz.RelativeMove(self.requestr) 126 | 127 | def zoom_relative(self, zoom, velocity): 128 | self.requestr.Translation.PanTilt.x = 0 129 | self.requestr.Translation.PanTilt.y = 0 130 | self.requestr.Translation.Zoom.x = zoom 131 | self.requestr.Speed.PanTilt.x = 0 132 | self.requestr.Speed.PanTilt.y = 0 133 | self.requestr.Speed.Zoom.x = velocity 134 | ret = self.ptz.RelativeMove(self.requestr) 135 | 136 | # Sets preset set, query and and go to 137 | 138 | def set_preset(self, name): 139 | self.requestp.PresetName = name 140 | self.requestp.PresetToken = '1' 141 | self.preset = self.ptz.SetPreset(self.requestp) # returns the PresetToken 142 | 143 | def get_preset(self): 144 | self.ptzPresetsList = self.ptz.GetPresets(self.requestc) 145 | 146 | def goto_preset(self): 147 | self.requestg.PresetToken = '1' 148 | self.ptz.GotoPreset(self.requestg) --------------------------------------------------------------------------------