--- /dev/null
+#!/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)
'@working_schedule': '',
# events
- 'on_event_start': ''
+ 'on_event_start': '',
+ 'on_event_end': ''
}
if ui['proto'] == 'v4l2':
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)
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
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(';')]
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)
data.setdefault('@working_schedule_type', 'outside')
data.setdefault('on_event_start', '')
+ data.setdefault('on_event_end', '')
def _get_wifi_settings(data):
elif op == 'rem':
self.rem_camera(camera_id)
+ elif op == '_relay_event':
+ self._relay_event(camera_id)
+
else:
raise HTTPError(400, 'unknown operation')
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
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,
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)
_started = False
_motion_binary_cache = None
+_motion_detected = {}
def find_motion():
except OSError as e:
if e.errno not in (errno.ESRCH, errno.ECHILD):
raise
-
+
def running():
pid = _get_pid()
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)
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,
'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)
[
(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),
vertical-align: top;
}
+div.camera-frame.motion-detected {
+ background-color: #712727;
+}
+
div.camera-container {
height: 100%;
text-align: center;
visibility: hidden;
}
+div.camera-frame.motion-detected {
+ background-color: #712727;
+}
+
div.modal-container div.camera-frame {
width: auto;
padding: 0px;
background-color: #414141;
}
+div.camera-frame.motion-detected:HOVER {
+ background-color: #8B3636;
+}
+
div.camera-top-bar {
padding: 3px 0px;
font-size: 20px;
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 */
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');
cameraImg.addClass('error').removeClass('loading');
cameraPlaceholder.css('opacity', 1);
cameraProgress.removeClass('visible');
+ cameraFrameDiv.removeClass('motion-detected');
});
cameraImg.load(function () {
if (refreshDisabled) {
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');
}
}
+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 */
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]) {
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();