From 410d399e057c6e3096008e6e6626cee791e04675 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sun, 6 Oct 2013 11:05:05 +0300 Subject: [PATCH] remote config fixes --- doc/todo.txt | 1 + src/config.py | 42 ++++++----- src/handlers.py | 184 ++++++++++++++++++++++++---------------------- src/motionctl.py | 9 +++ static/js/main.js | 4 +- 5 files changed, 132 insertions(+), 108 deletions(-) diff --git a/doc/todo.txt b/doc/todo.txt index 46666f8..fd41383 100644 --- a/doc/todo.txt +++ b/doc/todo.txt @@ -2,6 +2,7 @@ -> bug: adding a remote device does not provide the available resolutions -> bug: if updating a remote camera config, local motion will get restarted +-> bug: when renaming the camera, the left/right texts set "Camera" should also change -> make camera frames positions configurable -> hide horrible 404 image on cameras -> prevent Request closed errors by stopping mjpg clients before stopping motion diff --git a/src/config.py b/src/config.py index 89f7f09..3dae27d 100644 --- a/src/config.py +++ b/src/config.py @@ -7,7 +7,6 @@ import re from collections import OrderedDict import settings -import v4l2ctl _CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf' @@ -143,7 +142,7 @@ def has_enabled_cameras(): camera_ids = get_camera_ids() cameras = [get_camera(camera_id) for camera_id in camera_ids] - return bool([c for c in cameras if c['@enabled']]) + return bool([c for c in cameras if c['@enabled'] and c['@proto'] == 'v4l2']) def get_camera(camera_id, as_lines=False): @@ -193,7 +192,7 @@ def get_camera(camera_id, as_lines=False): def set_camera(camera_id, data): # TODO use a cache - if data.get('@proto') == 'v4l2': + if data['@proto'] == 'v4l2': _set_default_motion_camera(data) # set the enabled status in main config @@ -208,6 +207,10 @@ def set_camera(camera_id, data): main_config['thread'] = threads + del data['@enabled'] + if '@id' in data: + del data['@id'] + set_main(main_config) # read the actual configuration from file @@ -251,9 +254,6 @@ def set_camera(camera_id, data): def add_camera(device_details): # TODO use a cache - device = device_details.get('device') - proto = device_details.get('proto') - # determine the last camera id camera_ids = get_camera_ids() @@ -264,23 +264,27 @@ def add_camera(device_details): logging.info('adding new camera with id %(id)s...' % {'id': camera_id}) # add the default camera config + proto = device_details['proto'] + data = OrderedDict() - data['@name'] = 'Camera' + str(camera_id) data['@proto'] = proto - data['@enabled'] = True - - for k, v in device_details.items(): - data['@' + k] = v + data['@enabled'] = device_details.get('enabled', True) if proto == 'v4l2': - data['videodevice'] = device - # find a suitable resolution - for (w, h) in v4l2ctl.list_resolutions(device): # TODO move/copy this code to handler/get_config - if w > 300: - data['width'] = w - data['height'] = h - break - + data['@name'] = 'Camera' + str(camera_id) + data['videodevice'] = device_details['device'] + if 'width' in device_details: + data['width'] = device_details['width'] + data['height'] = device_details['height'] + + else: + data['@host'] = device_details['host'] + data['@port'] = device_details['port'] + data['@username'] = device_details['username'] + data['@password'] = device_details['password'] + data['@remote_camera_id'] = device_details['remote_camera_id'] + data['@enabled'] = device_details.get('enabled', True) + # write the configuration to file set_camera(camera_id, data) diff --git a/src/handlers.py b/src/handlers.py index a650469..b81e84d 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -86,14 +86,13 @@ class ConfigHandler(BaseHandler): if camera_id: logging.debug('getting config for camera %(id)s' % {'id': camera_id}) - camera_ids = config.get_camera_ids() - if camera_id not in camera_ids: + if camera_id not in config.get_camera_ids(): raise HTTPError(404, 'no such camera') camera_config = config.get_camera(camera_id) if camera_config['@proto'] != 'v4l2': try: - remote_data = remote.get_config( + remote_ui_config = remote.get_config( camera_config.get('@host'), camera_config.get('@port'), camera_config.get('@username'), @@ -102,17 +101,21 @@ class ConfigHandler(BaseHandler): except Exception as e: return self.finish_json({'error': unicode(e)}) - - remote_data = self._camera_ui_to_dict(remote_data) + + local_data = camera_config + camera_config = self._camera_ui_to_dict(remote_ui_config) + camera_config['@proto'] = local_data['@proto'] + camera_config['@enabled'] = local_data['@enabled'] - camera_config.update(remote_data) - ui_config = self._camera_dict_to_ui(camera_config) if camera_config['@proto'] == 'v4l2': resolutions = v4l2ctl.list_resolutions(camera_config['videodevice']) resolutions = [(str(w) + 'x' + str(h)) for (w, h) in resolutions] ui_config['available_resolutions'] = resolutions + + else: + ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] self.finish_json(ui_config) @@ -124,60 +127,43 @@ class ConfigHandler(BaseHandler): def set_config(self, camera_id): try: - data = json.loads(self.request.body) + ui_config = json.loads(self.request.body) except Exception as e: logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)}) raise - restart = bool(data.get('last')) - if restart and motionctl.running(): - motionctl.stop() - - try: - if camera_id: - logging.debug('setting config for camera %(id)s' % {'id': camera_id}) - - camera_ids = config.get_camera_ids() - if camera_id not in camera_ids: - raise HTTPError(404, 'no such camera') + if camera_id: + logging.debug('setting config for camera %(id)s' % {'id': camera_id}) + + camera_ids = config.get_camera_ids() + if camera_id not in camera_ids: + raise HTTPError(404, 'no such camera') + + camera_config = config.get_camera(camera_id) + if camera_config['@proto'] == 'v4l2': + camera_config = self._camera_ui_to_dict(ui_config) + camera_config['@proto'] = 'v4l2' + config.set_camera(camera_id, camera_config) - camera_config = config.get_camera(camera_id) - if camera_config['@proto'] == 'v4l2': - data = self._camera_ui_to_dict(data) - config.set_camera(camera_id, data) - - else: - remote.set_config( - camera_config.get('@host'), - camera_config.get('@port'), - camera_config.get('@username'), - camera_config.get('@password'), - camera_config.get('@remote_camera_id'), - data) - else: - logging.debug('setting main config') - - try: - data = json.loads(self.request.body) - - except Exception as e: - logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)}) - - raise - - data = self._main_ui_to_dict(data) - config.set_main(data) + remote.set_config( + camera_config.get('@host'), + camera_config.get('@port'), + camera_config.get('@username'), + camera_config.get('@password'), + camera_config.get('@remote_camera_id'), + ui_config) - except: - raise - - finally: - if restart: - if config.has_enabled_cameras(): - motionctl.start() + else: + logging.debug('setting main config') + + main_config = self._main_ui_to_dict(ui_config) + config.set_main(main_config) + + if not ui_config.get('norestart'): + motionctl.restart() def set_preview(self, camera_id): try: @@ -223,7 +209,7 @@ class ConfigHandler(BaseHandler): username = self.get_argument('username', None) password = self.get_argument('password', None) - if host: # remote + if host: # remote listing try: cameras = remote.list_cameras(host, port, username, password) @@ -233,17 +219,25 @@ class ConfigHandler(BaseHandler): else: cameras = [] for camera_id in config.get_camera_ids(): - data = config.get_camera(camera_id) - if data['@proto'] == 'v4l2': - data = self._camera_dict_to_ui(data) + camera_config = config.get_camera(camera_id) + if camera_config['@proto'] == 'v4l2': + name = camera_config['@name'] - else: - data = { - 'name': data['@name'] - } + else: # remote camera + try: + remote_camera_config = remote.get_config( + camera_config.get('@host'), + camera_config.get('@port'), + camera_config.get('@username'), + camera_config.get('@password'), + camera_config.get('@remote_camera_id')) + + except: + continue - data['id'] = camera_id - cameras.append(data) + name = remote_camera_config['name'] + + cameras.append({'name': name, 'id': camera_id}) self.finish_json({'cameras': cameras}) @@ -271,31 +265,50 @@ class ConfigHandler(BaseHandler): raise - camera_id, data = config.add_camera(device_details) + proto = device_details['proto'] + if proto == 'v4l2': + # find a suitable resolution + for (w, h) in v4l2ctl.list_resolutions(device_details['device']): + if w > 300: + device_details['width'] = w + device_details['height'] = h + break + + camera_id, camera_config = config.add_camera(device_details) + + if proto == 'v4l2': + motionctl.restart() + + else: + try: + remote_ui_config = remote.get_config( + device_details.get('host'), + device_details.get('port'), + device_details.get('username'), + device_details.get('password'), + device_details.get('remote_camera_id')) + + except Exception as e: + return self.finish_json({'error': unicode(e)}) + + local_data = camera_config + camera_config = self._camera_ui_to_dict(remote_ui_config) + camera_config['@enabled'] = local_data['@enabled'] + camera_config['@proto'] = local_data['@proto'] - data['@id'] = camera_id + camera_config['@id'] = camera_id - if motionctl.running() and data['@proto'] == 'v4l2': - motionctl.stop() + ui_config = self._camera_dict_to_ui(camera_config) - if config.has_enabled_cameras() and data['@proto'] == 'v4l2': - motionctl.start() - - try: - remote_data = remote.get_config( - device_details.get('host'), - device_details.get('port'), - device_details.get('username'), - device_details.get('password'), - device_details.get('remote_camera_id')) + if camera_config['@proto'] == 'v4l2': + resolutions = v4l2ctl.list_resolutions(camera_config['videodevice']) + resolutions = [(str(w) + 'x' + str(h)) for (w, h) in resolutions] + ui_config['available_resolutions'] = resolutions - except Exception as e: - return self.finish_json({'error': unicode(e)}) - - remote_data = self._camera_ui_to_dict(remote_data) - remote_data.update(data) + else: + ui_config['available_resolutions'] = remote_ui_config['available_resolutions'] - self.finish_json(self._camera_dict_to_ui(remote_data)) + self.finish_json(ui_config) def rem_camera(self, camera_id): logging.debug('removing camera %(id)s' % {'id': camera_id}) @@ -303,11 +316,8 @@ class ConfigHandler(BaseHandler): local = config.get_camera(camera_id).get('@proto') == 'v4l2' config.rem_camera(camera_id) - if motionctl.running() and local: - motionctl.stop() - - if config.has_enabled_cameras() and local: - motionctl.start() + if local: + motionctl.restart() def _main_ui_to_dict(self, ui): return { diff --git a/src/motionctl.py b/src/motionctl.py index 62b0e98..548330f 100644 --- a/src/motionctl.py +++ b/src/motionctl.py @@ -5,6 +5,7 @@ import signal import subprocess import time +import config import settings @@ -100,6 +101,14 @@ def running(): return False +def restart(): + if running(): + stop() + + if config.has_enabled_cameras(): + start() + + def _get_pid(): motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid') diff --git a/static/js/main.js b/static/js/main.js index 989c341..b4856f7 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -746,7 +746,7 @@ function doApply() { for (var i = 0; i < configs.length; i++) { var config = configs[i]; if (i === configs.length - 1) { - config.config['last'] = true; + config.config['last'] = true; // TODO not used, to be replaced by norestart } ajax('POST', '/config/' + config.key + '/set/', config.config, function (data) { if (data == null || data.error) { @@ -1218,7 +1218,7 @@ function doCloseCamera(cameraId) { } data['enabled'] = false; - data['last'] = true; + data['last'] = true;// TODO not used, to be replaced by norestart ajax('POST', '/config/' + cameraId + '/set/', data, function (data) { if (data == null || data.error) { return; // TODO handle error -- 2.39.5