]> www.vanbest.org Git - motioneye-debian/commitdiff
added support for execution custom commands on action buttons
authorCalin Crisan <ccrisan@gmail.com>
Tue, 1 Dec 2015 15:49:07 +0000 (17:49 +0200)
committerCalin Crisan <ccrisan@gmail.com>
Tue, 1 Dec 2015 15:49:07 +0000 (17:49 +0200)
motioneye/config.py
motioneye/handlers.py
motioneye/server.py
motioneye/static/css/main.css
motioneye/static/js/main.js

index de42747ac9ec01c57fd55ed52e78dc93ed7c5f6b..7b50fcc49eee430ed090ea28dc7f79da8dfbe108 100644 (file)
@@ -38,6 +38,7 @@ import v4l2ctl
 
 _CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf'
 _MAIN_CONFIG_FILE_NAME = 'motion.conf'
+_ACTIONS = ['lock', 'unlock', 'light_on', 'light_off', 'alarm_on', 'alarm_off']
 
 _main_config_cache = None
 _camera_config_cache = {}
@@ -1319,6 +1320,10 @@ def motion_camera_dict_to_ui(data):
             extra_options.append((name, value))
 
     ui['extra_options'] = extra_options
+    
+    # action commands
+    action_commands = get_action_commands(data['@id'])
+    ui['actions'] = action_commands.keys()
 
     return ui
 
@@ -1363,6 +1368,16 @@ def simple_mjpeg_camera_dict_to_ui(data):
     return ui
 
 
+def get_action_commands(camera_id):
+    action_commands = {}
+    for action in _ACTIONS:
+        path = os.path.join(settings.CONF_PATH, '%s_%s' % (action, camera_id))
+        if os.access(path, os.X_OK):
+            action_commands[action] = path
+    
+    return action_commands
+
+
 def backup():
     logging.debug('generating config backup file')
 
index 2ea300e94409af172699e4c0bdb69222039e937e..28dcef2380a42d7097886cb8aada96b03bb5d918 100644 (file)
@@ -1437,6 +1437,64 @@ class MovieHandler(BaseHandler):
             raise HTTPError(400, 'unknown operation')
 
 
+class ActionHandler(BaseHandler):
+    @asynchronous
+    def post(self, camera_id, action):
+        camera_id = int(camera_id)
+        if camera_id not in config.get_camera_ids():
+            raise HTTPError(404, 'no such camera')
+        
+        if action == 'snapshot':
+            logging.debug('executing snapshot action for camera with id %s' % camera_id)
+            return self.snapshot()
+        
+        elif action == 'record_start':
+            logging.debug('executing record_start action for camera with id %s' % camera_id)
+            return self.record_start()
+        
+        elif action == 'record_stop':
+            logging.debug('executing record_stop action for camera with id %s' % camera_id)
+            return self.record_stop()
+
+        action_commands = config.get_action_commands(camera_id)
+        command = action_commands.get(action)
+        if not command:
+            raise HTTPError(400, 'unknown action')
+
+        logging.debug('executing %s action for camera with id %s: "%s"' % (action, camera_id, command))
+        self.run_command_bg(command)
+    
+    def run_command_bg(self, command):
+        self.p = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE)
+        self.command = command
+        
+        self.io_loop = IOLoop.instance()
+        self.io_loop.add_timeout(datetime.timedelta(milliseconds=100), self.check_command)
+    
+    def check_command(self):
+        exit_status = self.p.poll()
+        if exit_status is not None:
+            output = self.p.stdout.read()
+            lines = output.split('\n')
+            if not lines[-1]:
+                lines = lines[:-1]
+            command = os.path.basename(self.command)
+            if exit_status:
+                logging.warn('%s: command has finished with non-zero exit status: %s' % (command, exit_status))
+                for line in lines:
+                    logging.warn('%s: %s' % (command, line))
+
+            else:
+                logging.debug('%s: command has finished' % command)
+                for line in lines:
+                    logging.debug('%s: %s' % (command, line))
+
+            self.finish_json({'status': exit_status})
+
+        else:
+            self.io_loop.add_timeout(datetime.timedelta(milliseconds=100), self.check_command)
+
+
 class PrefsHandler(BaseHandler):
     def get(self, key):
         self.finish_json(self.get_pref(key))
index 0c8af20bbd142facef1a088b9750aac6b30e2a29..a9d006d25015b69bd6e95c43c74c25a489ffed9f 100644 (file)
@@ -176,6 +176,7 @@ handler_mapping = [
     (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
     (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
     (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.*?)/?$', handlers.MovieHandler),
+    (r'^/action/(?P<camera_id>\d+)/(?P<action>\w+)/?$', handlers.ActionHandler),
     (r'^/prefs/(?P<key>\w+)/?$', handlers.PrefsHandler),
     (r'^/_relay_event/?$', handlers.RelayEventHandler),
     (r'^/log/(?P<name>\w+)/?$', handlers.LogHandler),
index 9ac4edf600b69b45b563e93dc1aa2f9b3b62edb1..23ec58dc96e76ba78de6ad32a2f32bfc244e72c5 100644 (file)
@@ -902,7 +902,7 @@ div.camera-overlay-bottom {
 
 div.camera-frame:HOVER div.camera-overlay-top,
 div.camera-frame:HOVER div.camera-overlay-bottom {
-    background-color: rgba(65, 65, 65, 0.8);
+    background-color: rgba(40, 40, 40, 0.8);
 }
 
 div.camera-frame.motion-detected div.camera-overlay-top,
@@ -1012,6 +1012,10 @@ div.camera-action-button {
     vertical-align: top;
 }
 
+div.camera-action-button.pending {
+    opacity: 0.5 !important;
+}
+
 div.camera-action-button.lock {
     background-position: 0px 0px;
 }
index 1ca9238f370cff4415bc4c416b8b3898e469c91e..13c0a3a31eb8e80ff1700cc5dad260d9efca8ee4 100644 (file)
@@ -2421,6 +2421,18 @@ function doDeleteAllFiles(mediaType, cameraId, groupKey, callback) {
     }, {stack: true});
 }
 
+function doAction(cameraId, action, callback) {
+    ajax('POST', basePath + 'action/' + cameraId + '/' + action + '/', null, function (data) {
+        if (data == null || data.error) {
+            showErrorMessage(data && data.error);
+        }
+
+        if (callback) {
+            callback();
+        }
+    });
+}    
+
 
     /* fetch & push */
 
@@ -3723,7 +3735,7 @@ function addCameraFrameUi(cameraConfig) {
     var alarmOnButton = cameraFrameDiv.find('div.camera-action-button.alarm-on');
     var alarmOffButton = cameraFrameDiv.find('div.camera-action-button.alarm-off');
     var snapshotButton = cameraFrameDiv.find('div.camera-action-button.snapshot');
-    var recordButton = cameraFrameDiv.find('div.camera-action-button.record');
+    var recordButton = cameraFrameDiv.find('div.camera-action-button.record-start');
     
     var cameraOverlay = cameraFrameDiv.find('div.camera-overlay');
     var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
@@ -3787,7 +3799,7 @@ function addCameraFrameUi(cameraConfig) {
     /* fade in */
     cameraFrameDiv.animate({'opacity': 1}, 100);
     
-    /* add the top button handlers */
+    /* add the top buttons handlers */
     configureButton.click(function () {
         doConfigureCamera(cameraId);
     });
@@ -3811,12 +3823,53 @@ function addCameraFrameUi(cameraConfig) {
         };
     }(cameraId));
     
-    /* add the action button handlers */
-//    if (cameraConfig.at-most-4-buttons) { TODO
-//        cameraOverlay.find('div.camera-overlay-bottom').addClass('few-buttons');
-//    }
-    //TODO add handlers 
+    /* action buttons */
+
+    cameraFrameDiv.find('div.camera-action-button').css('display', 'none');
+    var actionButtonDict = {
+        'lock': lockButton,
+        'unlock': unlockButton,
+        'light_on': lightOnButton,
+        'light_off': lightOffButton,
+        'alarm_on': alarmOnButton,
+        'alarm_off': alarmOffButton,
+        'snapshpt': snapshotButton,
+        'record': recordButton
+    };
     
+    cameraConfig.actions.forEach(function (action) {
+        var button = actionButtonDict[action];
+        if (!button) {
+            return;
+        }
+        
+        button.css('display', '');
+        button.click(function () {
+            if (button.hasClass('pending')) {
+                return;
+            }
+            
+            button.addClass('pending');
+            
+            if (action == 'record') {
+                if (button.hasClass('record-start')) {
+                    action = 'record_start';
+                }
+                else {
+                    action = 'record_stop';
+                }
+            }
+
+            doAction(cameraId, action, function () {
+                button.removeClass('pending');
+            });
+        })
+    });
+    
+    if (cameraConfig.actions.length <= 4) {
+        cameraOverlay.find('div.camera-overlay-bottom').addClass('few-buttons');
+    }
+
     var FPS_LEN = 4;
     cameraImg[0].fpsTimes = [];
     
@@ -3862,6 +3915,13 @@ function addCameraFrameUi(cameraConfig) {
             else {
                 cameraFrameDiv.removeClass('motion-detected');
             }
+
+            if (getCookie('record_active_' + cameraId) == 'true') {
+                recordButton.removeClass('record-start').addClass('record-stop');
+            }
+            else {
+                recordButton.removeClass('record-stop').addClass('record-start');
+            }
             
             this.lastCookieTime = now;