From c06061c126f32660e765df77e870cd5272d031a6 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Wed, 13 Aug 2014 22:06:11 +0300 Subject: [PATCH] implemented email notifications --- sendmail.py | 87 +++++++++++++++++++++++++++++++++++---------- settings_default.py | 3 ++ src/config.py | 60 ++++++++++++++++++++++++------- src/tzctl.py | 5 +++ static/js/main.js | 18 ++++++++-- templates/main.html | 34 ++++++++++++++++-- 6 files changed, 171 insertions(+), 36 deletions(-) mode change 100644 => 100755 sendmail.py diff --git a/sendmail.py b/sendmail.py old mode 100644 new mode 100755 index b09acf0..3216867 --- a/sendmail.py +++ b/sendmail.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import datetime +import logging import os import smtplib import socket @@ -25,16 +27,33 @@ from email.mime.text import MIMEText import settings +from motioneye import _configure_settings, _configure_logging -def send_mail(host, port, username, password, tls, to, subject, message): - conn = smtplib.SMTP(host, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60)) +_configure_settings() +_configure_logging() + +import config +import tzctl + + +messages = { + 'motion_start': 'Motion has been detected by camera "%(camera)s/%(hostname)s" at %(moment)s (%(timezone)s).' +} + +subjects = { + 'motion_start': 'motionEye: motion detected by "%(camera)s"' +} + + +def send_mail(server, port, account, password, tls, to, subject, message): + conn = smtplib.SMTP(server, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60)) if tls: conn.starttls() - if username and password: - conn.login(username, password) + if account and password: + conn.login(account, password) - _from = username or 'motioneye@' + socket.gethostname() + _from = account or 'motioneye@' + socket.gethostname() email = MIMEText(message) email['Subject'] = subject @@ -45,25 +64,57 @@ def send_mail(host, port, username, password, tls, to, subject, message): conn.quit() +def format_message(subject, message, camera_id, moment): + format_dict = { + 'camera': config.get_camera(camera_id)['@name'], + 'hostname': socket.gethostname(), + 'moment': moment.strftime('%Y-%m-%d %H:%M:%S'), + } + + if settings.LOCAL_TIME_FILE: + format_dict['timezone'] = tzctl.get_time_zone() + + else: + format_dict['timezone'] = 'local time' + + message = message % format_dict + subject = subject % format_dict + subject = subject.replace('\n', ' ') + + message += '\n\n' + message += 'motionEye.' + + return (subject, message) + + def print_usage(): - print 'Usage: sendmail.py ' - print 'Environment: HOST, PORT, USERNAME, PASSWORD, TLD' + print 'Usage: sendmail.py ' if __name__ == '__main__': - if len(sys.argv) < 4: + if len(sys.argv) < 10: print_usage() sys.exit(-1) - host = os.environ.get('SMTP_HOST', 'localhost') - port = int(os.environ.get('SMTP_PORT', '465')) - username = os.environ.get('SMTP_USERNAME') - password = os.environ.get('SMTP_PASSWORD') - tls = os.environ.get('SMTP_TLS') == 'true' or True - to = sys.argv[1] - subject = sys.argv[2] - message = sys.argv[3] + server = sys.argv[1] + port = int(sys.argv[2]) + account = sys.argv[3] + password = sys.argv[4] + tls = sys.argv[5].lower() == 'true' + to = sys.argv[6] + msg_id = sys.argv[7] + camera_id = sys.argv[8] + moment = sys.argv[9] + + message = messages.get(msg_id) + subject = subjects.get(msg_id) + if not message or not subject: + logging.error('unknown message id') + sys.exit(-1) + + moment = datetime.datetime.strptime(moment, '%Y-%m-%dT%H:%M:%S') + subject, message = format_message(subject, message, camera_id, moment) - send_mail(host, port, username, password, tls, to, subject, message) + send_mail(server, port, account, password, tls, to, subject, message) - print('message sent.') + logging.info('message sent.') diff --git a/settings_default.py b/settings_default.py index 2faf74c..9617388 100644 --- a/settings_default.py +++ b/settings_default.py @@ -67,3 +67,6 @@ LOCAL_TIME_FILE = None # enables rebooting after changing system settings (such as wifi settings or system updates) ENABLE_REBOOT = False + +# the timeout in seconds to use when talking to a SMTP server +SMTP_TIMEOUT = 60 diff --git a/src/config.py b/src/config.py index 056b433..d27371a 100644 --- a/src/config.py +++ b/src/config.py @@ -593,14 +593,15 @@ def camera_ui_to_dict(ui): 'pre_capture': int(ui['pre_capture']), 'post_capture': int(ui['post_capture']), - # motion notifications - '@motion_notifications': ui['motion_notifications'], - '@motion_notifications_emails': ui['motion_notifications_emails'], - # working schedule - '@working_schedule': '' + '@working_schedule': '', + + # events + 'on_event_start': '' } + on_event_start = [] + if 'brightness' in ui: if int(ui['brightness']) == 50: data['brightness'] = 0 @@ -693,6 +694,21 @@ def camera_ui_to_dict(ui): max_val = min(max_val, 9999999) data['ffmpeg_bps'] = int(ui['movie_quality']) * max_val / 100 + + if ui['motion_notifications']: + send_mail_path = os.path.join(settings.PROJECT_PATH, 'sendmail.py') + send_mail_path = os.path.abspath(send_mail_path) + + emails = re.sub('\\s', '', ui['motion_notifications_emails']) + + on_event_start.append('%(script)s %(server)s %(port)s %(account)s %(password)s %(tls)s %(to)s motion_start %%t %%Y-%%m-%%dT%%H:%%M:%%S' % { + 'script': send_mail_path, + 'server': ui['smtp_server'], + 'port': ui['smtp_port'], + 'account': ui['smtp_account'], + 'password': ui['smtp_password'], + 'tls': ui['smtp_tls'], + 'to': emails}) if ui['working_schedule']: data['@working_schedule'] = ( @@ -703,6 +719,9 @@ def camera_ui_to_dict(ui): ui['friday_from'] + '-' + ui['friday_to'] + '|' + ui['saturday_from'] + '-' + ui['saturday_to'] + '|' + ui['sunday_from'] + '-' + ui['sunday_to']) + + if on_event_start: + data['on_event_start'] = '; '.join(on_event_start) return data @@ -785,10 +804,6 @@ def camera_dict_to_ui(data): 'pre_capture': int(data['pre_capture']), 'post_capture': int(data['post_capture']), - # motion notifications - 'motion_notifications': data['@motion_notifications'], - 'motion_notifications_emails': data['@motion_notifications_emails'], - # working schedule 'working_schedule': False, 'monday_from': '09:00', 'monday_to': '17:00', @@ -800,6 +815,10 @@ def camera_dict_to_ui(data): 'sunday_from': '09:00', 'sunday_to': '17:00' } + 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(';')] + # the brightness & co. keys in the ui dictionary # indicate the presence of these controls # we must call v4l2ctl functions to determine the available controls @@ -910,8 +929,24 @@ def camera_dict_to_ui(data): max_val = data['width'] * data['height'] * data['framerate'] / 3 max_val = min(max_val, 9999999) - ui['movie_quality'] = min(100, int(round(ffmpeg_bps * 100.0 / max_val))) + ui['movie_quality'] = min(100, int(round(ffmpeg_bps * 100.0 / max_val))) + for e in on_event_start: + if e.count('sendmail.py') and e.count('motion_start'): + e = e.split(' ') + if len(e) != 10: + continue + + ui['motion_notifications'] = True + ui['smtp_server'] = e[1] + ui['smtp_port'] = e[2] + ui['smtp_account'] = e[3] + ui['smtp_password'] = e[4] + ui['smtp_tls'] = e[5].lower() == 'true' + ui['motion_notifications_emails'] = e[6] + + break + working_schedule = data['@working_schedule'] if working_schedule: days = working_schedule.split('|') @@ -1210,11 +1245,10 @@ def _set_default_motion_camera(camera_id, data, old_motion): data.setdefault('ffmpeg_video_codec', 'msmpeg4') data.setdefault('@preserve_movies', 0) - data.setdefault('@motion_notifications', False) - data.setdefault('@motion_notifications_emails', '') - data.setdefault('@working_schedule', '') + data.setdefault('on_event_start', '') + def _get_wifi_settings(data): wifi_settings = wifictl.get_wifi_settings() diff --git a/src/tzctl.py b/src/tzctl.py index 6e19891..fbd04e0 100644 --- a/src/tzctl.py +++ b/src/tzctl.py @@ -24,6 +24,8 @@ import subprocess def _get_time_zone_symlink(): file = settings.LOCAL_TIME_FILE + if not file: + return None for i in xrange(8): # recursively follow the symlinks @UnusedVariable try: @@ -46,6 +48,9 @@ def _get_time_zone_symlink(): def _get_time_zone_md5(): + if settings.LOCAL_TIME_FILE: + return None + try: output = subprocess.check_output('cd /usr/share/zoneinfo; find * -type f | xargs md5sum', shell=True) diff --git a/static/js/main.js b/static/js/main.js index 4beaaf4..19c46c0 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -214,6 +214,9 @@ function initUI() { makeTextValidator($('#imageFileNameEntry'), true); makeTextValidator($('#movieFileNameEntry'), true); makeTextValidator($('#emailAddressesEntry'), true); + makeTextValidator($('#smtpServerEntry'), true); + makeTextValidator($('#smtpAccountEntry'), true); + makeTextValidator($('#smtpPasswordEntry'), true); /* number validators */ makeNumberValidator($('#streamingPortEntry'), 1024, 65535, false, false, true); @@ -223,6 +226,7 @@ function initUI() { makeNumberValidator($('#eventGapEntry'), 1, 86400, false, false, true); makeNumberValidator($('#preCaptureEntry'), 0, 100, false, false, true); makeNumberValidator($('#postCaptureEntry'), 0, 100, false, false, true); + makeNumberValidator($('#smtpPortEntry'), 1, 65535, false, false, true); /* time validators */ makeTimeValidator($('#mondayFrom')); @@ -243,6 +247,7 @@ function initUI() { /* ui elements that enable/disable other ui elements */ $('#motionEyeSwitch').change(updateConfigUi); $('#showAdvancedSwitch').change(updateConfigUi); + $('#wifiSwitch').change(updateConfigUi); $('#storageDeviceSelect').change(updateConfigUi); $('#autoBrightnessSwitch').change(updateConfigUi); $('#resolutionSelect').change(updateConfigUi); @@ -260,7 +265,6 @@ function initUI() { $('#preserveMoviesSelect').change(updateConfigUi); $('#motionNotificationsSwitch').change(updateConfigUi); $('#workingScheduleSwitch').change(updateConfigUi); - $('#wifiSwitch').change(updateConfigUi); $('#storageDeviceSelect').change(function () { $('#rootDirectoryEntry').val('/'); @@ -649,6 +653,11 @@ function cameraUi2Dict() { /* motion notifications */ 'motion_notifications': $('#motionNotificationsSwitch')[0].checked, 'motion_notifications_emails': $('#emailAddressesEntry').val(), + 'smtp_server': $('#smtpServerEntry').val(), + 'smtp_port': $('#smtpPortEntry').val(), + 'smtp_account': $('#smtpAccountEntry').val(), + 'smtp_password': $('#smtpPasswordEntry').val(), + 'smtp_tls': $('#smtpTlsSwitch')[0].checked, /* working schedule */ 'working_schedule': $('#workingScheduleSwitch')[0].checked, @@ -820,7 +829,12 @@ function dict2CameraUi(dict) { /* motion notifications */ $('#motionNotificationsSwitch')[0].checked = dict['motion_notifications']; $('#emailAddressesEntry').val(dict['motion_notifications_emails']); - + $('#smtpServerEntry').val(dict['smtp_server']); + $('#smtpPortEntry').val(dict['smtp_port']); + $('#smtpAccountEntry').val(dict['smtp_account']); + $('#smtpPasswordEntry').val(dict['smtp_password']); + $('#smtpTlsSwitch')[0].checked = dict['smtp_tls']; + /* working schedule */ $('#workingScheduleSwitch')[0].checked = dict['working_schedule']; $('#mondayFrom').val(dict['monday_from']); diff --git a/templates/main.html b/templates/main.html index 1798558..61f38f8 100644 --- a/templates/main.html +++ b/templates/main.html @@ -420,12 +420,40 @@ - +
Motion Notifications
- + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Email Addresses ??
SMTP Server?
SMTP Port?
SMTP Account?
SMTP Password?
Use TLS?
-- 2.39.5