diff --git a/README.rst b/README.rst index 5aaa701..982f603 100644 --- a/README.rst +++ b/README.rst @@ -136,11 +136,18 @@ Supportability Matrix +-------------------------+---------------+----------+-----------------+ | IP2M-841B/841W/842W | Yes | working | | +-------------------------+---------------+----------+-----------------+ +| IP3M-956B | Yes | working | | ++-------------------------+---------------+----------+-----------------+ | IP3M-956E | Yes | working | | +-------------------------+---------------+----------+-----------------+ +| IP3M-956W | Yes | working | | ++-------------------------+---------------+----------+-----------------+ | IPM-HX1B | Yes | working | | +-------------------------+---------------+----------+-----------------+ - +| IP3M-941 | Yes | working | | ++-------------------------+---------------+----------+-----------------+ +| IP3M-HX2 | Yes (partial) | working | | ++-------------------------+---------------+----------+-----------------+ If you have different model, feel fee to contribute and report your results. diff --git a/cli/amcrest-cli b/cli/amcrest-cli index b09986f..39ac761 100755 --- a/cli/amcrest-cli +++ b/cli/amcrest-cli @@ -493,7 +493,7 @@ def main(): if len(config.sections()) > 1 and not args.camera: print("More than 1 camera found at %s. " "Append the --camera option." % AMCREST_CONF) - return + sys.exit(-1) if args.camera: section = args.camera @@ -510,7 +510,7 @@ def main(): args.password = getpass.getpass() except (NoSectionError, NoOptionError) as e: print("ERROR! %s found at %s" % (e, AMCREST_CONF)) - return + sys.exit(-1) amcrest = AmcrestCamera( args.hostname, @@ -526,18 +526,15 @@ def main(): print(("Looks like amcrest device: {0}".format(ret))) else: print("No devices found, try again later!") - return if args.username and args.hostname and args.password: if args.telnet_config == -1: print((camera.telnet_config)) - return elif args.mjpg_stream: if not args.save: print("This option requires --save") - return timer_capture = float(args.mjpg_stream[0]) @@ -553,15 +550,12 @@ def main(): print((camera.mjpg_stream( channelno, typeno, path_file=args.save))) - return elif args.reboot: print((camera.reboot())) - return elif args.shutdown: print((camera.shutdown())) - return elif args.audio_send_stream: if len(args.audio_send_stream) <= 3: @@ -574,12 +568,10 @@ def main(): codec = args.audio_send_stream[3] camera.audio_send_stream(httptype, channel, filename, codec) - return elif args.audio_stream_capture: if not args.save: print("This option requires --save") - return if len(args.audio_stream_capture) <= 2: print("Requires arguments: httptype, channel, timer_capture!") @@ -598,36 +590,30 @@ def main(): camera.audio_stream_capture(httptype, channel) elif args.play_wav: - return camera.play_wav(path_file=args.play_wav) + camera.play_wav(path_file=args.play_wav) elif args.wlan_config: print((camera.wlan_config)) - return elif args.scan_wlan_devices: print((camera.scan_wlan_devices(args.scan_wlan_devices))) - return elif args.telnet_config: camera.telnet_config = args.telnet_config - return elif args.ptz_config: print((camera.ptz_config)) - return elif args.ptz_auto_movement: print((camera.ptz_auto_movement)) - return elif args.ptz_presets_list: print((camera.ptz_presets_list())) - return elif args.ptz_goto_preset: if len(args.ptz_goto_preset) < 2: print("Requires arguments: channel preset_point_number") - return + sys.exit(-1) action = 'start' channel = args.ptz_goto_preset[0] @@ -636,104 +622,73 @@ def main(): elif args.log_clear_all: print((camera.log_clear_all)) - return elif args.video_standard == -1: print((camera.video_standard)) - return elif args.video_standard: camera.video_standard = args.video_standard - return elif args.coordinates_current_window: print((camera.coordinates_current_window( args.coordinates_current_window))) - return elif args.video_widget_config: print((camera.video_widget_config)) - return elif args.video_input_capability: print((camera.video_input_capability)) - return elif args.video_in_options: print((camera.video_in_options)) - return elif args.video_out_options: print((camera.video_out_options)) - return elif args.video_max_remote_input_channels: print((camera.video_max_remote_input_channels)) - return elif args.video_output_channels_device_supported: print((camera.video_output_channels_device_supported)) - return elif args.video_input_channels_device_supported: print((camera.video_input_channels_device_supported)) - return elif args.video_channel_title: print((camera.video_channel_title)) - return elif args.encode_region_interested: print((camera.encode_region_interested)) - return elif args.encode_media: print((camera.encode_media)) - return elif args.encode_config_capability: print((camera.encode_config_capability)) - return elif args.encode_capability: print((camera.encode_capability)) - return elif args.video_max_extra_stream: print((camera.video_max_extra_stream)) - return elif args.video_color_config: print((camera.video_color_config)) - return elif args.factory_create: print((camera.factory_create)) - return elif args.record_capability: print((camera.record_capability)) - return - - elif args.record_mode == -1: - print((camera.record_mode)) - return - - elif args.record_mode is not None: - camera.record_mode = args.record_mode - return elif args.record_config == -1: print((camera.record_config)) - return elif args.record_config is not None: camera.record_config = args.record_config - return elif args.media_global_config: print((camera.media_global_config)) - return elif args.audio_input_channels_numbers: print((camera.audio_input_channels_numbers)) @@ -806,7 +761,7 @@ def main(): if len(args.add_user) <= 2: print("This option requires at least: " "username, password and group") - return + sys.exit(-1) user = args.add_user[0] pwd = args.add_user[1] @@ -829,33 +784,30 @@ def main(): print((camera.add_user(user, pwd, grp, sharable, reserved, memo))) - return elif args.modify_user: if len(args.modify_user) <= 2: print("This option requires at least: " "username, attribute and new value") - return + sys.exit(-1) user = args.modify_user[0] attr = args.modify_user[1] newvalue = args.modify_user[2] print((camera.modify_user(user, attr, newvalue))) - return elif args.modify_password: if len(args.modify_password) <= 2: print("This option requires at least: " "username, new password and old password") - return + sys.exit(-1) user = args.modify_password[0] newpwd = args.modify_password[1] oldpwd = args.modify_password[2] print((camera.modify_password(user, newpwd, oldpwd))) - return elif args.delete_user: print((camera.delete_user(args.delete_user))) @@ -904,23 +856,18 @@ def main(): elif args.upnp_config == -1: print((camera.upnp_config)) - return elif args.upnp_config is not None: camera.upnp_config = args.upnp_config - return elif args.upnp_status == -1: print((camera.upnp_status)) - return elif args.ntp_config == -1: print((camera.ntp_config)) - return elif args.ntp_config is not None: camera.ntp_config = args.ntp_config - return elif args.network_interfaces: print((camera.network_interfaces)) diff --git a/configure.ac b/configure.ac index a4ec309..5892989 100644 --- a/configure.ac +++ b/configure.ac @@ -10,7 +10,7 @@ # GNU General Public License for more details. define([VERSION_MAJOR], [1]) define([VERSION_MINOR], [2]) -define([VERSION_FIX], [1]) +define([VERSION_FIX], [2]) define([VERSION_NUMBER], VERSION_MAJOR[.]VERSION_MINOR[.]VERSION_FIX) define([VERSION_SUFFIX], [_master]) diff --git a/docs/index.rst b/docs/index.rst index f6d439d..d59e01d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,6 +130,8 @@ Supportability Matrix +-------------------------+---------------+----------+-----------------+ | IP2M-841B/841W/842W | Yes | working | | +-------------------------+---------------+----------+-----------------+ +| IP3M-956B | Yes | working | | ++-------------------------+---------------+----------+-----------------+ | IP3M-956E | Yes | working | | +-------------------------+---------------+----------+-----------------+ | IPM-HX1B | Yes | working | | diff --git a/examples/home-assitant/README b/examples/home-assitant/README new file mode 100644 index 0000000..d071566 --- /dev/null +++ b/examples/home-assitant/README @@ -0,0 +1,4 @@ +Example of configuration for Home Assitant (https://home-assistant.io) + +More info: +https://home-assistant.io/components/amcrest/ diff --git a/examples/home-assitant/example-configuration.yaml b/examples/home-assitant/example-configuration.yaml new file mode 100644 index 0000000..805a4f6 --- /dev/null +++ b/examples/home-assitant/example-configuration.yaml @@ -0,0 +1,12 @@ +# Example of configuration for Home Assistant +# ~/.homeassistant/configuration.yaml +amcrest: + - host: 192.168.1.5 + name: LivingRoom + username: admin + password: super-password + + - host: 192.168.1.6 + name: Garage + username: admin + password: super-password diff --git a/examples/zoom-in.py b/examples/zoom-in.py new file mode 100644 index 0000000..b55268a --- /dev/null +++ b/examples/zoom-in.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# vim:sw=4:ts=4:et +import time +from amcrest import AmcrestCamera + +amcrest = AmcrestCamera('192.168.1.5', 80, 'admin', 'super-password') + +amcrest.camera.zoom_in("start") +time.sleep(0.5) +amcrest.camera.zoom_in("stop") + +# Snapshot +amcrest.camera.snapshot(path_file="/tmp/snapshot.jpg") diff --git a/examples/zoom-out.py b/examples/zoom-out.py new file mode 100644 index 0000000..9c02369 --- /dev/null +++ b/examples/zoom-out.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# vim:sw=4:ts=4:et +import time +from amcrest import AmcrestCamera + +amcrest = AmcrestCamera('192.168.1.6', 80, 'admin', 'super-password') + +amcrest.camera.zoom_out("start") +time.sleep(0.5) +amcrest.camera.zoom_out("stop") + +# Snapshot +amcrest.camera.snapshot(path_file="/tmp/snapshot.jpg") diff --git a/presentations/python-amcrest-2017-12-18.odp b/presentations/python-amcrest-2017-12-18.odp new file mode 100644 index 0000000..be4ddd0 Binary files /dev/null and b/presentations/python-amcrest-2017-12-18.odp differ diff --git a/setup.py b/setup.py index b631426..6ed61f6 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup(name='amcrest', - version='1.2.1', + version='1.2.2', description='Python wrapper implementation for Amcrest cameras.', author='Douglas Schilling Landgraf, Marcelo Moreira de Mello', author_email='dougsland@gmail.com, tchello.mello@gmail.com', diff --git a/src/amcrest/Makefile.am b/src/amcrest/Makefile.am index 4ed1a54..e51407e 100644 --- a/src/amcrest/Makefile.am +++ b/src/amcrest/Makefile.am @@ -34,4 +34,7 @@ pycrest_PYTHON = \ log.py \ ptz.py \ special.py \ + utils.py \ + nas.py \ + storage.py \ $(NULL) diff --git a/src/amcrest/http.py b/src/amcrest/http.py index 8d8a7cc..141c87e 100644 --- a/src/amcrest/http.py +++ b/src/amcrest/http.py @@ -81,7 +81,7 @@ def _generate_token(self): req = requests.get(url, auth=auth) req.raise_for_status() - except requests.exceptions.HTTPError: + except requests.HTTPError: # if 401, then try new digest method self._authentication = 'digest' auth = requests.auth.HTTPDigestAuth(self._user, self._password) @@ -111,7 +111,9 @@ def __repr__(self): def as_dict(self): """Callback for __dict__.""" cdict = self.__dict__.copy() - cdict['_token'] = '_redacted' + redacted = '**********' + cdict['_token'] = redacted + cdict['_password'] = redacted return cdict # Base methods @@ -154,7 +156,7 @@ def command(self, cmd, retries=None, timeout_cmd=None): ) resp.raise_for_status() break - except requests.exceptions.HTTPError as error: + except requests.HTTPError as error: _LOGGER.debug("Trying again due error %s", error) continue diff --git a/src/amcrest/motion_detection.py b/src/amcrest/motion_detection.py index b5207a5..76e4022 100644 --- a/src/amcrest/motion_detection.py +++ b/src/amcrest/motion_detection.py @@ -47,3 +47,16 @@ def motion_detection(self, opt): return True return False + + @motion_detection.setter + def motion_recording(self, opt): + if opt.lower() == "true" or opt.lower() == "false": + ret = self.command( + 'configManager.cgi?action=' + 'setConfig&MotionDetect[0].EventHandler.RecordEnable={0}' + .format(opt.lower()) + ) + if "ok" in ret.content.decode('utf-8').lower(): + return True + + return False diff --git a/src/amcrest/system.py b/src/amcrest/system.py index 103d9d1..85495f7 100644 --- a/src/amcrest/system.py +++ b/src/amcrest/system.py @@ -127,10 +127,13 @@ def config_backup(self, filename=None): 'Config.backup?action=All' ) + if not ret: + return None + if filename: with open(filename, "w+") as cfg: cfg.write(ret.content.decode('utf-8')) - return + return None return ret.content.decode('utf-8') diff --git a/src/amcrest/utils.py b/src/amcrest/utils.py index 5ed930f..1378cb5 100644 --- a/src/amcrest/utils.py +++ b/src/amcrest/utils.py @@ -65,3 +65,5 @@ def to_unit(value, unit='B'): if unit in byte_array: result = value / 1024**byte_array.index(unit) return (float('{:.{prec}f}'.format(result, prec=PRECISION)), unit) + + return value diff --git a/tox.ini b/tox.ini index 75e2411..5940145 100644 --- a/tox.ini +++ b/tox.ini @@ -32,4 +32,8 @@ commands = ignore = D102, E121, + E722 exclude = docs,.tox,*.egg,*.pyc,.git,__pycache + +[pycodestyle] +ignore = E722