]> www.vanbest.org Git - motioneye-debian/commitdiff
camera frames now indicate visually when motion is detected
authorCalin Crisan <ccrisan@gmail.com>
Sun, 7 Dec 2014 15:57:34 +0000 (17:57 +0200)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 7 Dec 2014 15:57:34 +0000 (17:57 +0200)
eventrelay.py [new file with mode: 0755]
src/config.py
src/handlers.py
src/motionctl.py
src/remote.py
src/server.py
static/css/frame.css
static/css/main.css
static/js/frame.js
static/js/main.js

diff --git a/eventrelay.py b/eventrelay.py
new file mode 100755 (executable)
index 0000000..69a7de4
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# 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 base64
+import logging
+import os.path
+import sys
+import urllib2
+
+sys.path.append(os.path.join(os.path.dirname(sys.argv[0]),'src'))
+
+import config
+import settings
+
+from motioneye import _configure_settings, _configure_logging
+
+
+_configure_settings()
+_configure_logging()
+
+
+def print_usage():
+    print 'Usage: eventrelay.py <event> <camera_id>'
+
+
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        print_usage()
+        sys.exit(-1)
+    
+    event = sys.argv[1] 
+    camera_id = sys.argv[2]
+
+    logging.debug('event = %s' % event)
+    logging.debug('camera_id = %s' % camera_id)
+
+    url = 'http://127.0.0.1:%(port)s/config/%(camera_id)s/_relay_event/?event=%(event)s' % {
+            'port': settings.PORT,
+            'camera_id': camera_id,
+            'event': event}
+
+    main_config = config.get_main()
+    
+    username = main_config.get('@admin_username', '')
+    password = main_config.get('@admin_password', '')
+    
+    request = urllib2.Request(url, '')
+    request.add_header('Authorization', 'Basic %s' % base64.encodestring('%s:%s' % (username, password)).replace('\n', ''))
+    
+    try:
+        urllib2.urlopen(request, timeout=settings.REMOTE_REQUEST_TIMEOUT)
+        logging.debug('event successfully relayed')
+    
+    except Exception as e:
+        logging.error('failed to relay event: %s' % e)
index 7dfc6ee76afa719b36a1a5d5605a151aa3216ec4..49659e2a5ac33edd4d79ef93bef5eedbdccfbbe9 100644 (file)
@@ -566,7 +566,8 @@ def camera_ui_to_dict(ui):
         '@working_schedule': '',
     
         # events
-        'on_event_start': ''
+        'on_event_start': '',
+        'on_event_end': ''
     }
     
     if ui['proto'] == 'v4l2':
@@ -713,8 +714,11 @@ def camera_ui_to_dict(ui):
         
         data['@working_schedule_type'] = ui['working_schedule_type']
     
-    # event start notifications
-    on_event_start = []
+    # event start
+    event_relay_path = os.path.join(settings.PROJECT_PATH, 'eventrelay.py')
+    event_relay_path = os.path.abspath(event_relay_path)
+        
+    on_event_start = ['%(script)s start %%t' % {'script': event_relay_path}]
     if ui['email_notifications_enabled']:
         send_mail_path = os.path.join(settings.PROJECT_PATH, 'sendmail.py')
         send_mail_path = os.path.abspath(send_mail_path)
@@ -743,8 +747,12 @@ def camera_ui_to_dict(ui):
         commands = ui['command_notifications_exec'].split(';')
         on_event_start += [c.strip() for c in commands]
 
-    if on_event_start:
-        data['on_event_start'] = '; '.join(on_event_start)
+    data['on_event_start'] = '; '.join(on_event_start)
+
+    # event end
+    on_event_end = ['%(script)s stop %%t' % {'script': event_relay_path}]
+    
+    data['on_event_end'] = '; '.join(on_event_end)
 
     return data
 
@@ -1009,7 +1017,7 @@ def camera_dict_to_ui(data):
         ui['sunday_from'], ui['sunday_to'] = days[6].split('-')
         ui['working_schedule_type'] = data['@working_schedule_type']
     
-    # event start notifications    
+    # event start    
     on_event_start = data.get('on_event_start') or []
     if on_event_start:
         on_event_start = [e.strip() for e in on_event_start.split(';')]
@@ -1037,6 +1045,9 @@ def camera_dict_to_ui(data):
             ui['web_hook_notifications_enabled'] = True 
             ui['web_hook_notifications_http_method'] = e[1]
             ui['web_hook_notifications_url'] = e[2]
+        
+        elif e.count('eventrelay.py'):
+            continue # ignore internal relay script
 
         else: # custom command
             command_notifications.append(e)
@@ -1345,6 +1356,7 @@ def _set_default_motion_camera(camera_id, data, old_motion=False):
     data.setdefault('@working_schedule_type', 'outside')
 
     data.setdefault('on_event_start', '')
+    data.setdefault('on_event_end', '')
 
 
 def _get_wifi_settings(data):
index e8833c0186b7ac4333800866af3f8f42babb9b6a..4b1d2e91ddbdde40a5fb1ad89b25b9a1232e7bd6 100644 (file)
@@ -197,6 +197,9 @@ class ConfigHandler(BaseHandler):
         elif op == 'rem':
             self.rem_camera(camera_id)
         
+        elif op == '_relay_event':
+            self._relay_event(camera_id)
+        
         else:
             raise HTTPError(400, 'unknown operation')
     
@@ -622,6 +625,22 @@ class ConfigHandler(BaseHandler):
             
         self.finish_json()
 
+    @BaseHandler.auth(admin=True)
+    def _relay_event(self, camera_id):
+        event = self.get_argument('event')
+        logging.debug('event %(event)s relayed for camera %(id)s' % {'event': event, 'id': camera_id})
+        
+        if event == 'start':
+            motionctl._motion_detected[camera_id] = True
+            
+        elif event == 'stop':
+            motionctl._motion_detected[camera_id] = False
+            
+        else:
+            logging.warn('unknown event %s' % event)
+
+        self.finish_json()
+
 
 class PictureHandler(BaseHandler):
     @asynchronous
@@ -680,10 +699,10 @@ class PictureHandler(BaseHandler):
         height = self.get_argument('height', None)
         
         picture = sequence and mediafiles.get_picture_cache(camera_id, sequence, width) or None
-        
+
         if picture is not None:
             return self.try_finish(picture)
-        
+
         camera_config = config.get_camera(camera_id)
         if utils.local_camera(camera_config):
             picture = mediafiles.get_current_picture(camera_config,
@@ -693,13 +712,15 @@ class PictureHandler(BaseHandler):
             if sequence and picture:
                 mediafiles.set_picture_cache(camera_id, sequence, width, picture)
 
+            self.set_cookie('motion_detected_' + str(camera_id), str(motionctl.is_motion_detected(camera_id)).lower())
             self.try_finish(picture)
                 
         else: # remote camera
-            def on_response(picture=None, error=None):
+            def on_response(motion_detected=False, picture=None, error=None):
                 if sequence and picture:
                     mediafiles.set_picture_cache(camera_id, sequence, width, picture)
                 
+                self.set_cookie('motion_detected_' + str(camera_id), str(motion_detected).lower())
                 self.try_finish(picture)
             
             remote.get_current_picture(camera_config, on_response, width=width, height=height)
index 8fa4f3203fb7efdddb5c040c83df01c6f7dd360c..506b5f9016fd80b590bd2a21f128b5dcb662bb7d 100644 (file)
@@ -34,6 +34,7 @@ import utils
 
 _started = False
 _motion_binary_cache = None
+_motion_detected = {}
 
 
 def find_motion():
@@ -162,7 +163,7 @@ def stop():
         except OSError as e:
             if e.errno not in (errno.ESRCH, errno.ECHILD):
                 raise
-    
+
 
 def running():
     pid = _get_pid()
@@ -247,6 +248,10 @@ def set_motion_detection(camera_id, enabled):
     http_client.fetch(request, on_response)
 
 
+def is_motion_detected(camera_id):
+    return _motion_detected.get(camera_id, False)
+
+
 def _get_thread_id(camera_id):
     # find the corresponding thread_id
     # (which can be different from camera_id)
index 8988bcb2da259c961daa7ef04c169a1280305dfa..45f51670bb78638ea63d36e288a97820119aedf3 100644 (file)
@@ -237,6 +237,15 @@ def get_current_picture(local_config, callback, width, height):
     request = _make_request(host, port, username, password, uri + '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
     
     def on_response(response):
+        motion_detected = False
+        
+        cookies = response.headers.get('Set-Cookie')
+        if cookies:
+            cookies = cookies.split(';')
+            cookies = [[i.strip() for i in c.split('=')] for c in cookies]
+            cookies = dict([c for c in cookies if len(c) == 2])
+            motion_detected = cookies.get('motion_detected_' + camera_id) == 'true'
+        
         if response.error:
             logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
                     'id': camera_id,
@@ -244,8 +253,8 @@ def get_current_picture(local_config, callback, width, height):
                     'msg': unicode(response.error)})
             
             return callback(error=unicode(response.error))
-        
-        callback(response.body)
+
+        callback(motion_detected, response.body)
     
     http_client = AsyncHTTPClient()
     http_client.fetch(request, on_response)
index 5a00a365028ef88c12b1729900dab21566f19eaf..de82013a970aa007ef8b6abfc3fb2173d4a1e6cc 100644 (file)
@@ -42,7 +42,7 @@ application = Application(
     [
         (r'^/$', handlers.MainHandler),
         (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
-        (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
+        (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview|_relay_event)/?$', handlers.ConfigHandler),
         (r'^/config/(?P<op>add|list|list_devices)/?$', handlers.ConfigHandler),
         (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
         (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
index 18b04dab3e98fb626477445a4e6a358b8f9f8326..db630c6f40ca734d6d524992d1fa5bc3c6860415 100644 (file)
@@ -47,6 +47,10 @@ div.camera-frame {
     vertical-align: top;
 }
 
+div.camera-frame.motion-detected {
+    background-color: #712727;
+}
+
 div.camera-container {
     height: 100%;
     text-align: center;
index 27b59a2f5213634e9428fd8ef52a78324471b91a..60e43767d80d4613b74ff394ce6776fec71d9ae9 100644 (file)
@@ -728,6 +728,10 @@ div.camera-frame-place-holder {
     visibility: hidden;
 }
 
+div.camera-frame.motion-detected {
+    background-color: #712727;
+}
+
 div.modal-container div.camera-frame {
     width: auto;
     padding: 0px;
@@ -739,6 +743,10 @@ div.camera-frame:HOVER {
     background-color: #414141;
 }
 
+div.camera-frame.motion-detected:HOVER {
+    background-color: #8B3636;
+}
+
 div.camera-top-bar {
     padding: 3px 0px;
     font-size: 20px;
index 89e2c6770f95bf24b8cb0cd7442bf9a6d0a5ce41..ce07f24a07a8994e3f3de9566ca459298c6cd540 100644 (file)
@@ -3,6 +3,28 @@ var refreshDisabled = false;
 var inProgress = false;
 var refreshInterval = 50; /* milliseconds */
 
+
+    /* utils */
+
+function getCookie(name) {
+    if (document.cookie.length <= 0) {
+        return null;
+    }
+
+    var start = document.cookie.indexOf(name + '=');
+    if (start == -1) {
+        return null;
+    }
+     
+    var start = start + name.length + 1;
+    var end = document.cookie.indexOf(';', start);
+    if (end == -1) {
+        end = document.cookie.length;
+    }
+    
+    return unescape(document.cookie.substring(start, end));
+}
+
     
     /* progress */
 
@@ -36,6 +58,7 @@ function setupCameraFrame() {
     var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
     var cameraProgress = cameraFrameDiv.find('div.camera-progress');
     var cameraImg = cameraFrameDiv.find('img.camera');
+    var cameraId = cameraFrameDiv.attr('id').substring(6);
     var progressImg = cameraFrameDiv.find('img.camera-progress');
     var body = $('body');
     
@@ -58,6 +81,7 @@ function setupCameraFrame() {
         cameraImg.addClass('error').removeClass('loading');
         cameraPlaceholder.css('opacity', 1);
         cameraProgress.removeClass('visible');
+        cameraFrameDiv.removeClass('motion-detected');
     });
     cameraImg.load(function () {
         if (refreshDisabled) {
@@ -71,6 +95,13 @@ function setupCameraFrame() {
         cameraPlaceholder.css('opacity', 0);
         cameraProgress.removeClass('visible');
         
+        if (getCookie('motion_detected_' + cameraId) == 'true') {
+            cameraFrameDiv.addClass('motion-detected');
+        }
+        else {
+            cameraFrameDiv.removeClass('motion-detected');
+        }
+
         if (this.naturalWidth / this.naturalHeight > body.width() / body.height()) {
             cameraImg.css('width', '100%');
             cameraImg.css('height', 'auto');
index 793d7c5e8fd95edf36083fa907b697ef327299d4..632f1902f1c0f01f31a7cd81d1319629dcb735fc 100644 (file)
@@ -194,8 +194,27 @@ function makeDeviceUrl(dict) {
     }
 }
 
+function getCookie(name) {
+    if (document.cookie.length <= 0) {
+        return null;
+    }
 
-    /* UI initialization */
+    var start = document.cookie.indexOf(name + '=');
+    if (start == -1) {
+        return null;
+    }
+     
+    var start = start + name.length + 1;
+    var end = document.cookie.indexOf(';', start);
+    if (end == -1) {
+        end = document.cookie.length;
+    }
+    
+    return unescape(document.cookie.substring(start, end));
+}
+
+/* UI initialization */
 
 function initUI() {
     /* checkboxes */
@@ -2392,6 +2411,7 @@ function addCameraFrameUi(cameraConfig) {
         cameraImg.height(Math.round(cameraImg.width() * 0.75));
         cameraPlaceholder.css('opacity', 1);
         cameraProgress.removeClass('visible');
+        cameraFrameDiv.removeClass('motion-detected');
     });
     cameraImg.load(function () {
         if (refreshDisabled[cameraId]) {
@@ -2406,6 +2426,13 @@ function addCameraFrameUi(cameraConfig) {
         cameraPlaceholder.css('opacity', 0);
         cameraProgress.removeClass('visible');
         
+        if (getCookie('motion_detected_' + cameraId) == 'true') {
+            cameraFrameDiv.addClass('motion-detected');
+        }
+        else {
+            cameraFrameDiv.removeClass('motion-detected');
+        }
+
         if (fullScreenCameraId) {
             /* update the modal dialog position when image is loaded */
             updateModalDialogPosition();