]> www.vanbest.org Git - motioneye-debian/commitdiff
added support for monitoring commands
authorCalin Crisan <ccrisan@gmail.com>
Fri, 10 Jun 2016 18:28:41 +0000 (21:28 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Fri, 10 Jun 2016 18:28:41 +0000 (21:28 +0300)
motioneye/config.py
motioneye/handlers.py
motioneye/monitor.py [new file with mode: 0644]
motioneye/remote.py
motioneye/static/css/main.css
motioneye/static/js/main.js

index 92b8b209d082f235775559da206c89eaf5627b9d..d686a3652e1fc87df49da717888ff3d641f79a76 100644 (file)
@@ -47,6 +47,7 @@ _camera_ids_cache = None
 _additional_section_funcs = []
 _additional_config_funcs = []
 _additional_structure_cache = {}
+_monitor_command_cache = {}
 
 # starting with r490 motion config directives have changed a bit 
 _LAST_OLD_CONFIG_VERSIONS = (490, '3.2.12')
@@ -249,8 +250,6 @@ def get_network_shares():
 
 
 def get_camera(camera_id, as_lines=False):
-    global _camera_config_cache
-    
     if not as_lines and camera_id in _camera_config_cache:
         return _camera_config_cache[camera_id]
     
@@ -349,8 +348,6 @@ def get_camera(camera_id, as_lines=False):
 
 
 def set_camera(camera_id, camera_config):
-    global _camera_config_cache
-
     camera_config['@id'] = camera_id
     _camera_config_cache[camera_id] = camera_config
 
@@ -453,7 +450,6 @@ def set_camera(camera_id, camera_config):
 
 def add_camera(device_details):
     global _camera_ids_cache
-    global _camera_config_cache
     
     proto = device_details['proto']
     if proto in ['netcam', 'mjpeg']:
@@ -538,7 +534,7 @@ def add_camera(device_details):
     set_camera(camera_id, camera_config)
     
     _camera_ids_cache = None
-    _camera_config_cache = {}
+    _camera_config_cache.clear()
     
     camera_config = get_camera(camera_id)
     
@@ -547,7 +543,6 @@ def add_camera(device_details):
 
 def rem_camera(camera_id):
     global _camera_ids_cache
-    global _camera_config_cache
     
     camera_config_name = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id}
     camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
@@ -564,7 +559,7 @@ def rem_camera(camera_id):
     logging.info('removing camera config file %(path)s...' % {'path': camera_config_path})
     
     _camera_ids_cache = None
-    _camera_config_cache = {}
+    _camera_config_cache.clear()
     
     try:
         os.remove(camera_config_path)
@@ -1388,6 +1383,22 @@ def get_action_commands(camera_id):
     return action_commands
 
 
+def get_monitor_command(camera_id):
+    if camera_id not in _monitor_command_cache:
+        path = os.path.join(settings.CONF_PATH, 'monitor_%s' % camera_id)
+        if os.access(path, os.X_OK):
+            _monitor_command_cache[camera_id] = path
+        
+        else:
+            _monitor_command_cache[camera_id] = None
+
+    return _monitor_command_cache[camera_id]
+
+
+def invalidate_monitor_commands():
+    _monitor_command_cache.clear()
+
+
 def backup():
     logging.debug('generating config backup file')
 
index 63159a8528923d7c5675d6261d9c487a19361339..ac1644ec3df7ef7706c5eaee4548230f4658556e 100644 (file)
@@ -29,6 +29,7 @@ from tornado.web import RequestHandler, HTTPError, asynchronous
 import config
 import mediafiles
 import mjpgclient
+import monitor
 import motionctl
 import powerctl
 import prefs
@@ -212,18 +213,20 @@ class MainHandler(BaseHandler):
 class ConfigHandler(BaseHandler):
     @asynchronous
     def get(self, camera_id=None, op=None):
+        config.invalidate_monitor_commands()
+
         if camera_id is not None:
             camera_id = int(camera_id)
-        
+
         if op == 'get':
             self.get_config(camera_id)
-            
+
         elif op == 'list':
             self.list()
-        
+
         elif op == 'backup':
             self.backup()
-            
+
         elif op == 'authorize':
             self.authorize(camera_id)
 
@@ -864,23 +867,29 @@ class PictureHandler(BaseHandler):
         width = width and float(width)
         height = height and float(height)
         
+        camera_id_str = str(camera_id)
+        
         camera_config = config.get_camera(camera_id)
         if utils.local_motion_camera(camera_config):
             picture = mediafiles.get_current_picture(camera_config,
                     width=width,
                     height=height)
             
-            self.set_cookie('motion_detected_' + str(camera_id), str(motionctl.is_motion_detected(camera_id)).lower())
-            self.set_cookie('capture_fps_' + str(camera_id), '%.1f' % mjpgclient.get_fps(camera_id))
+            self.set_cookie('motion_detected_' + camera_id_str, str(motionctl.is_motion_detected(camera_id)).lower())
+            self.set_cookie('capture_fps_' + camera_id_str, '%.1f' % mjpgclient.get_fps(camera_id))
+            self.set_cookie('monitor_info_' + camera_id_str, monitor.get_monitor_info(camera_id))
+
             self.try_finish(picture)
 
         elif utils.remote_camera(camera_config):
-            def on_response(motion_detected=False, fps=None, picture=None, error=None):
+            def on_response(motion_detected=False, capture_fps=None, monitor_info=None, picture=None, error=None):
                 if error:
                     return self.try_finish(None)
 
-                self.set_cookie('motion_detected_' + str(camera_id), str(motion_detected).lower())
-                self.set_cookie('capture_fps_' + str(camera_id), '%.1f' % fps)
+                self.set_cookie('motion_detected_' + camera_id_str, str(motion_detected).lower())
+                self.set_cookie('capture_fps_' + camera_id_str, '%.1f' % capture_fps)
+                self.set_cookie('monitor_info_' + camera_id_str, monitor_info or '')
+
                 self.try_finish(picture)
             
             remote.get_current_picture(camera_config, width=width, height=height, callback=on_response)
diff --git a/motioneye/monitor.py b/motioneye/monitor.py
new file mode 100644 (file)
index 0000000..6612507
--- /dev/null
@@ -0,0 +1,67 @@
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye 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, either version 3 of the License, or
+# (at your option) any later version.
+# 
+# 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.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>. 
+
+import logging
+import re
+import subprocess
+import time
+
+import config
+
+
+DEFAULT_INTERVAL = 1 # seconds
+
+_monior_info_cache_by_camera_id = {}
+_last_call_time_by_camera_id = {}
+_interval_by_camera_id = {}
+
+
+def get_monitor_info(camera_id):
+    global _command_cache_time
+
+    now = time.time()
+    command = config.get_monitor_command(camera_id)
+    if command is None:
+        return ''
+
+    monitor_info = _monior_info_cache_by_camera_id.get(camera_id)
+    last_call_time = _last_call_time_by_camera_id.get(camera_id, 0)
+    interval = _interval_by_camera_id.get(camera_id, DEFAULT_INTERVAL)
+    if monitor_info is None or now - last_call_time > interval:
+        monitor_info, interval = _exec_monitor_command(command)
+        monitor_info = re.sub('[\x00-\x20]', '&nbsp', monitor_info)
+        _interval_by_camera_id[camera_id] = interval
+        _monior_info_cache_by_camera_id[camera_id] = monitor_info
+        _last_call_time_by_camera_id[camera_id] = now
+    
+    return monitor_info
+
+
+def _exec_monitor_command(command):
+    process = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = process.communicate()
+
+    try:
+        interval = int(err)
+    
+    except:
+        interval = DEFAULT_INTERVAL
+    
+    out = out.strip()
+    logging.debug('monitoring command "%s" returned "%s"' % (command, out))
+
+    return out, interval
index d2ddb786e2ce9cb09dbd97556c340efd08829ae9..23323c70bc22b6fde5662252f231fbee00e35b3a 100644 (file)
@@ -320,8 +320,9 @@ def get_current_picture(local_config, width, height, callback):
     def on_response(response):
         cookies = utils.parse_cookies(response.headers.get_list('Set-Cookie'))
         motion_detected = cookies.get('motion_detected_' + str(camera_id)) == 'true'
-        fps = cookies.get('capture_fps_' + str(camera_id))
-        fps = float(fps) if fps else 0
+        capture_fps = cookies.get('capture_fps_' + str(camera_id))
+        capture_fps = float(capture_fps) if capture_fps else 0
+        monitor_info = cookies.get('monitor_info_' + str(camera_id))
 
         if response.error:
             logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
@@ -331,7 +332,7 @@ def get_current_picture(local_config, width, height, callback):
             
             return callback(error=utils.pretty_http_error(response))
 
-        callback(motion_detected, fps, response.body)
+        callback(motion_detected, capture_fps, monitor_info, response.body)
     
     http_client = AsyncHTTPClient()
     http_client.fetch(request, _callback_wrapper(on_response))
index 2ee7404fa561d8f32b165d8cbc0394013bf66d33..7922d0f77b9fd730d6d931d045b89445b4912c70 100644 (file)
@@ -981,6 +981,9 @@ div.camera-overlay-bottom.few-buttons {
 div.camera-info {
     display: inline-block;
     white-space: nowrap;
+    font-family: monospace;
+    font-size: 1em;
+    font-weight: bold;
     width: 40%;
     overflow: hidden;
     text-overflow: ellipsis;
@@ -989,12 +992,32 @@ div.camera-info {
     line-height: 5em;
 }
 
+div.camera-info.two-lines {
+    line-height: 1.2em;
+}
+
 div.camera-overlay-bottom.few-buttons div.camera-info {
     line-height: 2.5em;
 }
 
+div.camera-overlay-bottom.few-buttons div.camera-info.two-lines {
+    line-height: 1.2em;
+    font-size: 0.8em;
+}
+
 span.camera-info {
-    padding-left: 0.5em;
+    display: inline-block;
+    margin-left: 1em;
+}
+
+div.camera-info.two-lines > span.camera-info {
+    margin-left: 1em;
+    margin-top: 1.5em;
+}
+
+div.camera-overlay-bottom.few-buttons div.camera-info.two-lines > span.camera-info {
+    margin-left: 0.5em;
+    margin-top: 0.5em;
 }
 
 div.camera-action-buttons {
index 0a92b009642eb2ceaee08d7334cc6691fefb1f06..b273a244faec4a8a706d553fca805d06cdeb2bf0 100644 (file)
@@ -3878,7 +3878,7 @@ function addCameraFrameUi(cameraConfig) {
                     '</div>' +
                     '<div class="camera-overlay-bottom">' +
                         '<div class="camera-info">' +
-                            '<span class="camera-info fps" title="streaming/capture frame rate"></span>' +
+                            '<span class="camera-info" title="streaming/capture frame rate"></span>' +
                         '</div>' +
                         '<div class="camera-action-buttons">' +
                         '<div class="camera-action-buttons-wrapper">' +
@@ -3902,8 +3902,9 @@ function addCameraFrameUi(cameraConfig) {
     var picturesButton = cameraFrameDiv.find('div.camera-top-button.media-pictures');
     var moviesButton = cameraFrameDiv.find('div.camera-top-button.media-movies');
     var fullScreenButton = cameraFrameDiv.find('div.camera-top-button.full-screen');
-    
-    var fpsSpan = cameraFrameDiv.find('span.camera-info.fps');
+
+    var cameraInfoDiv = cameraFrameDiv.find('div.camera-info');
+    var cameraInfoSpan = cameraFrameDiv.find('span.camera-info');
     
     var lockButton = cameraFrameDiv.find('div.camera-action-button.lock');
     var unlockButton = cameraFrameDiv.find('div.camera-action-button.unlock');
@@ -4067,7 +4068,7 @@ function addCameraFrameUi(cameraConfig) {
         cameraPlaceholder.css('opacity', 1);
         cameraProgress.removeClass('visible');
         cameraFrameDiv.removeClass('motion-detected');
-        fpsSpan.html('');
+        cameraInfoSpan.html('');
     };
     cameraImg[0].onload = function () {
         if (this.error) {
@@ -4106,6 +4107,7 @@ function addCameraFrameUi(cameraConfig) {
             }
             
             var captureFps = getCookie('capture_fps_' + cameraId);
+            var monitorInfo = getCookie('monitor_info_' + cameraId);
             
             this.lastCookieTime = now;
 
@@ -4113,14 +4115,25 @@ function addCameraFrameUi(cameraConfig) {
                 var streamingFps = this.fpsTimes.length * 1000 / (this.fpsTimes[this.fpsTimes.length - 1] - this.fpsTimes[0]);
                 streamingFps = streamingFps.toFixed(1);
                 
-                var fps = streamingFps;
+                var info = streamingFps;
                 if (captureFps) {
-                    fps += '/' + captureFps;
+                    info += '/' + captureFps;
                 }
                 
-                fps += ' fps';
+                info += ' fps';
+                
+                if (monitorInfo) {
+                    if (monitorInfo.charAt(0) == monitorInfo.charAt(monitorInfo.length - 1)) {
+                        monitorInfo = monitorInfo.substring(1, monitorInfo.length - 1);
+                    }
+                    info += '<br>' + monitorInfo;
+                    cameraInfoDiv.addClass('two-lines');
+                }
+                else {
+                    cameraInfoDiv.removeClass('two-lines')
+                }
 
-                fpsSpan.html(fps);
+                cameraInfoSpan.html(info);
             }
         }