+-> add complete js validation
-> group @config rules to top
--> notification: multiple email addresses and phone numbers
+-> notification: multiple email addresses
-> what do we do with working schedule
-> browser compatibility test
-> hint text next to section titles
# import config
# main_config = config.get_main()
-# #config.add_camera('v4l2:///dev/video0')
+# config.add_camera('v4l2:///dev/video0')
# #data = config.get_camera(1)
# #data['@enabled'] = True
# #config.set_camera(1, data)
raise
-def camera_ui_to_dict(camera_id, ui):
- video_device = ui.get('device', '')
- if video_device.count('://'):
- video_device = video_device.split('://')[-1]
-
- data = {
- # device
- '@name': ui.get('name', ''),
- '@enabled': ui.get('enabled', False),
- 'videodevice': video_device,
- 'lightswitch': int(ui.get('light_switch_detect', False) * 5),
- 'auto_brightness': ui.get('auto_brightness', False),
- 'brightness': int(int(ui.get('brightness', 0)) * 2.55),
- 'contrast': int(int(ui.get('contrast', 0)) * 2.55),
- 'saturation': int(int(ui.get('saturation', 0)) * 2.55),
- 'hue': int(int(ui.get('hue', 0))),
- 'width': int(ui.get('resolution', '352x288').split('x')[0]),
- 'height': int(ui.get('resolution', '352x288').split('x')[1]),
- 'framerate': int(ui.get('framerate', 1)),
-
- # file storage
- '@storage_device': ui.get('storage_device', 'local-disk'),
- '@network_server': ui.get('network_server', ''),
- '@network_share_name': ui.get('network_share_name', ''),
- '@network_username': ui.get('network_username', ''),
- '@network_password': ui.get('network_password', ''),
- 'target_dir': ui.get('root_directory', '/'),
-
- # text overlay
- 'text_left': '',
- 'text_right': '',
-
- # streaming
- 'webcam_localhost': not ui.get('video_streaming', True),
- 'webcam_port': int(ui.get('streaming_port', 8080)),
- 'webcam_maxrate': int(ui.get('streaming_framerate', 1)),
- 'webcam_quality': max(1, int(ui.get('streaming_quality', 50))),
-
- # still images
- 'output_normal': False,
- 'output_all': False,
- 'output_motion': False,
- 'snapshot_interval': 0,
- 'jpeg_filename': '',
- 'snapshot_filename': '',
- # TODO preserve images
-
- # movies
- 'ffmpeg_variable_bitrate': 0,
- 'ffmpeg_video_codec': 'mpeg4',
- 'ffmpeg_cap_new': True,
- 'movie_filename': '',
- # TODO preserve movies
-
- # motion detection
- 'text_changes': ui.get('show_frame_changes', False),
- 'locate': ui.get('show_frame_changes', False),
- 'threshold': ui.get('frame_change_threshold', 1500),
- 'noise_tune': ui.get('auto_noise_detect', True),
- 'noise_level': max(1, int(int(ui.get('noise_level', 8)) * 2.55)),
- 'gap': int(ui.get('gap', 60)),
- 'pre_capture': int(ui.get('pre_capture', 0)),
- 'post_capture': int(ui.get('post_capture', 0)),
-
- # TODO notifications
- }
-
- if ui.get('text_overlay', False):
- left_text = ui.get('left_text', 'camera-name')
- if left_text == 'camera-name':
- data['text_left'] = ui.get('name')
-
- elif left_text == 'timestamp':
- data['text_left'] = '%Y-%m-%d\n%T'
-
- else:
- data['text_left'] = ui.get('custom_left_text', '')
-
- right_text = ui.get('right_text', 'timestamp')
- if right_text == 'camera-name':
- data['text_right'] = ui.get('name')
-
- elif right_text == 'timestamp':
- data['text_right'] = '%Y-%m-%d\n%T'
-
- else:
- data['text_right'] = ui.get('custom_right_text', '')
-
- if ui.get('still_images', False):
- capture_mode = ui.get('capture_mode', 'motion-triggered')
- if capture_mode == 'motion-triggered':
- data['output_normal'] = True
- data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
-
- elif capture_mode == 'interval-snapshots':
- data['snapshot_interval'] = int(ui.get('snapshot_interval'), 300)
- data['snapshot_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
-
- elif capture_mode == 'all-frames':
- data['output_all'] = True
- data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S')
-
- data['quality'] = max(1, int(ui.get('image_quality', 75)))
-
- if ui.get('motion_movies', False):
- data['ffmpeg_variable_bitrate'] = 2 + int((100 - int(ui.get('movie_quality', 50))) * 0.29)
- data['movie_filename'] = ui.get('movie_file_name', '%Y-%m-%d-%H-%M-%S-%q')
-
- return data
-
-
-def camera_dict_to_ui(camera_id, data):
- # set the default options if not present
- _set_default_motion_camera(data)
-
- ui = {
- # device
- 'name': data['@name'],
- 'enabled': data['@enabled'],
- 'device': 'v4l2://' + data['videodevice'],
- 'light_switch_detect': data['lightswitch'] > 0,
- 'auto_brightness': data['auto_brightness'],
- 'brightness': int(int(data['brightness']) / 2.55),
- 'contrast': int(int(data['contrast']) / 2.55),
- 'saturation': int(int(data['saturation']) / 2.55),
- 'hue': int(int(data['hue'])),
- 'resolution': str(data['width']) + 'x' + str(data['height']),
- 'framerate': int(data['framerate']),
-
- # file storage
- 'storage_device': data['@storage_device'],
- 'network_server': data['@network_server'],
- 'network_share_name': data['@network_share_name'],
- 'network_username': data['@network_username'],
- 'network_password': data['@network_password'],
- 'root_directory': data['target_dir'],
-
- # text overlay
- 'text_overlay': False,
- 'left_text': 'camera-name',
- 'right_text': 'timestamp',
-
- # streaming
- 'vudeo_streaming': not data['webcam_localhost'],
- 'streaming_port': int(data['webcam_port']),
- 'streaming_framerate': int(data['webcam_maxrate']),
- 'streaming_quality': int(data['webcam_quality']),
-
- # still images
- 'still_images': False,
- 'capture_mode': 'motion-triggered',
- 'image_file_name': '%Y-%m-%d-%H-%M-%S',
- 'image_quality': 75,
- # TODO preserve images
-
- # motion movies
- 'motion_movies': False,
- 'movie_quality': 50,
- 'movie_file_name': '%Y-%m-%d-%H-%M-%S-%q',
- # TODO preserve movies
-
- # motion detection
- 'show_frame_changes': data.get('text_changes') or data.get('locate'),
- 'frame_change_threshold': data['threshold'],
- 'auto_noise_detect': data['noise_tune'],
- 'noise_level': int(int(data['noise_level']) / 2.55),
- 'gap': int(data['gap']),
- 'pre_capture': int(data['pre_capture']),
- 'post_capture': int(data['post_capture']),
-
- # TODO notifications
- }
-
- text_left = data['text_left']
- text_right = data['text_right']
- if text_left or text_right:
- ui['text_overlay'] = True
-
- if text_left == data['@name']:
- ui['left_text'] = 'camera-name'
-
- elif text_left == '%Y-%m-%d\n%T':
- ui['left_text'] = 'timestamp'
-
- else:
- ui['left_text'] = 'custom-text'
- ui['custom_left_text'] = text_left
-
- if text_right == data['@name']:
- ui['right_text'] = 'camera-name'
-
- elif text_right == '%Y-%m-%d\n%T':
- ui['right_text'] = 'timestamp'
-
- else:
- ui['right_text'] = 'custom-text'
- ui['custom_right_text'] = text_right
-
- output_all = data.get('output_all')
- output_normal = data.get('output_normal')
- jpeg_filename = data.get('jpeg_filename')
- snapshot_interval = data.get('snapshot_interval')
- snapshot_filename = data.get('snapshot_filename')
-
- if (((output_all or output_normal) and jpeg_filename) or
- (snapshot_interval and snapshot_filename)):
-
- ui['still_images'] = True
-
- if output_all:
- ui['capture_mode'] = 'all-frames'
- ui['image_file_name'] = jpeg_filename
-
- elif data.get('snapshot_interval'):
- ui['capture-mode'] = 'interval-snapshots'
- ui['image_file_name'] = snapshot_filename
-
- elif data.get('output_normal'):
- ui['capture-mode'] = 'motion-triggered'
- ui['image_file_name'] = jpeg_filename
-
- ui['image_quality'] = ui.get('quality', 75)
-
- movie_filename = data.get('movie_filename')
- if movie_filename:
- ui['motion_movies'] = True
- ui['movie_quality'] = int((max(2, data['ffmpeg_variable_bitrate']) - 2) / 0.29)
- ui['movie_file_name'] = movie_filename
-
- return data
-
-
def _value_to_python(value):
value_lower = value.lower()
if value_lower == 'off':
def _set_default_motion_camera(data):
data.setdefault('@name', '')
data.setdefault('@enabled', False)
+ data.setdefault('@proto', 'v4l2')
data.setdefault('videodevice', '')
data.setdefault('lightswitch', 0)
data.setdefault('auto_brightness', False)
data.setdefault('webcam_port', 8080)
data.setdefault('webcam_maxrate', 1)
data.setdefault('webcam_quality', 50)
+ data.setdefault('webcam_motion', False)
data.setdefault('text_left', '')
data.setdefault('text_right', '')
data.setdefault('snapshot_interval', 0)
data.setdefault('snapshot_filename', '')
data.setdefault('quality', 75)
+ data.setdefault('@preserve_images', 0)
data.setdefault('movie_filename', '')
data.setdefault('ffmpeg_variable_bitrate', 14)
+ data.setdefault('@preserve_movies', 0)
+
+ data.setdefault('@motion_notifications', False)
+ data.setdefault('@motion_notifications_emails', '')
+
+ data.setdefault('@working_schedule', '')
+
if camera_id not in camera_ids:
raise HTTPError(404, 'no such camera')
- self.finish_json(config.get_camera(camera_id))
+ ui_config = self._camera_dict_to_ui(config.get_camera(camera_id))
+ self.finish_json(ui_config)
else:
logging.debug('getting main config')
+ # TODO _main_dict_to_ui
self.finish_json(config.get_main())
def set_config(self, camera_id):
camera_ids = config.get_camera_ids()
if camera_id not in camera_ids:
raise HTTPError(404, 'no such camera')
-
+
+ data = self._camera_ui_to_dict(data)
config.set_camera(camera_id, data)
else:
raise
+ # TODO _main_ui_to_dict
config.set_main(data)
def list_cameras(self):
config.rem_camera(camera_id)
+ def _camera_ui_to_dict(self, ui):
+ video_device = ui.get('device', '')
+ if video_device.count('://'):
+ video_device = video_device.split('://')[-1]
+
+ if not ui.get('resolution'): # avoid errors for empty resolution setting
+ ui['resolution'] = '352x288'
+
+ data = {
+ # device
+ '@name': ui.get('name', ''),
+ '@enabled': ui.get('enabled', False),
+ 'videodevice': video_device,
+ 'lightswitch': int(ui.get('light_switch_detect', False) * 5),
+ 'auto_brightness': ui.get('auto_brightness', False),
+ 'brightness': int(int(ui.get('brightness', 0)) * 2.55),
+ 'contrast': int(int(ui.get('contrast', 0)) * 2.55),
+ 'saturation': int(int(ui.get('saturation', 0)) * 2.55),
+ 'hue': int(int(ui.get('hue', 0))),
+ 'width': int(ui['resolution'].split('x')[0]),
+ 'height': int(ui['resolution'].split('x')[1]),
+ 'framerate': int(ui.get('framerate', 1)),
+
+ # file storage
+ '@storage_device': ui.get('storage_device', 'local-disk'),
+ '@network_server': ui.get('network_server', ''),
+ '@network_share_name': ui.get('network_share_name', ''),
+ '@network_username': ui.get('network_username', ''),
+ '@network_password': ui.get('network_password', ''),
+ 'target_dir': ui.get('root_directory', '/'),
+
+ # text overlay
+ 'text_left': '',
+ 'text_right': '',
+
+ # streaming
+ 'webcam_localhost': not ui.get('video_streaming', True),
+ 'webcam_port': int(ui.get('streaming_port', 8080)),
+ 'webcam_maxrate': int(ui.get('streaming_framerate', 1)),
+ 'webcam_quality': max(1, int(ui.get('streaming_quality', 50))),
+ 'webcam_motion': ui.get('streaming_motion', False),
+
+ # still images
+ 'output_normal': False,
+ 'output_all': False,
+ 'output_motion': False,
+ 'snapshot_interval': 0,
+ 'jpeg_filename': '',
+ 'snapshot_filename': '',
+ '@preserve_images': int(ui.get('preserve_images', 0)),
+
+ # movies
+ 'ffmpeg_variable_bitrate': 0,
+ 'ffmpeg_video_codec': 'mpeg4',
+ 'ffmpeg_cap_new': True,
+ 'movie_filename': '',
+ '@preserve_movies': int(ui.get('preserve_movies', 0)),
+
+ # motion detection
+ 'text_changes': ui.get('show_frame_changes', False),
+ 'locate': ui.get('show_frame_changes', False),
+ 'threshold': ui.get('frame_change_threshold', 1500),
+ 'noise_tune': ui.get('auto_noise_detect', True),
+ 'noise_level': max(1, int(int(ui.get('noise_level', 8)) * 2.55)),
+ 'gap': int(ui.get('gap', 60)),
+ 'pre_capture': int(ui.get('pre_capture', 0)),
+ 'post_capture': int(ui.get('post_capture', 0)),
+
+ # motion notifications
+ '@motion_notifications': ui.get('motion_notifications', False),
+ '@motion_notifications_emails': ui.get('motion_notifications_emails', ''),
+
+ # working schedule
+ '@working_schedule': ''
+ }
+
+ if ui.get('text_overlay', False):
+ left_text = ui.get('left_text', 'camera-name')
+ if left_text == 'camera-name':
+ data['text_left'] = ui.get('name')
+
+ elif left_text == 'timestamp':
+ data['text_left'] = '%Y-%m-%d\n%T'
+
+ else:
+ data['text_left'] = ui.get('custom_left_text', '')
+
+ right_text = ui.get('right_text', 'timestamp')
+ if right_text == 'camera-name':
+ data['text_right'] = ui.get('name')
+
+ elif right_text == 'timestamp':
+ data['text_right'] = '%Y-%m-%d\n%T'
+
+ else:
+ data['text_right'] = ui.get('custom_right_text', '')
+
+ if ui.get('still_images', False):
+ capture_mode = ui.get('capture_mode', 'motion-triggered')
+ if capture_mode == 'motion-triggered':
+ data['output_normal'] = True
+ data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
+
+ elif capture_mode == 'interval-snapshots':
+ data['snapshot_interval'] = int(ui.get('snapshot_interval'), 300)
+ data['snapshot_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S-%q')
+
+ elif capture_mode == 'all-frames':
+ data['output_all'] = True
+ data['jpeg_filename'] = ui.get('image_file_name', '%Y-%m-%d-%H-%M-%S')
+
+ data['quality'] = max(1, int(ui.get('image_quality', 75)))
+
+ if ui.get('motion_movies', False):
+ data['ffmpeg_variable_bitrate'] = 2 + int((100 - int(ui.get('movie_quality', 50))) * 0.29)
+ data['movie_filename'] = ui.get('movie_file_name', '%Y-%m-%d-%H-%M-%S-%q')
+
+ if ui.get('working_schedule', False):
+ data['@working_schedule'] = (
+ ui.get('monday_from', '') + '-' + ui.get('monday_to') + '|' +
+ ui.get('tuesday_from', '') + '-' + ui.get('tuesday_to') + '|' +
+ ui.get('wednesday_from', '') + '-' + ui.get('wednesday_to') + '|' +
+ ui.get('thursday_from', '') + '-' + ui.get('thursday_to') + '|' +
+ ui.get('friday_from', '') + '-' + ui.get('friday_to') + '|' +
+ ui.get('saturday_from', '') + '-' + ui.get('saturday_to') + '|' +
+ ui.get('sunday_from', '') + '-' + ui.get('sunday_to'))
+
+ return data
+
+ def _camera_dict_to_ui(self, data):
+ ui = {
+ # device
+ 'name': data['@name'],
+ 'enabled': data['@enabled'],
+ 'device': data['@proto'] + '://' + data['videodevice'],
+ 'light_switch_detect': data['lightswitch'] > 0,
+ 'auto_brightness': data['auto_brightness'],
+ 'brightness': int(int(data['brightness']) / 2.55),
+ 'contrast': int(int(data['contrast']) / 2.55),
+ 'saturation': int(int(data['saturation']) / 2.55),
+ 'hue': int(int(data['hue'])),
+ 'resolution': str(data['width']) + 'x' + str(data['height']),
+ 'framerate': int(data['framerate']),
+
+ # file storage
+ 'storage_device': data['@storage_device'],
+ 'network_server': data['@network_server'],
+ 'network_share_name': data['@network_share_name'],
+ 'network_username': data['@network_username'],
+ 'network_password': data['@network_password'],
+ 'root_directory': data['target_dir'],
+
+ # text overlay
+ 'text_overlay': False,
+ 'left_text': 'camera-name',
+ 'right_text': 'timestamp',
+ 'custom_left_text': '',
+ 'custom_right_text': '',
+
+ # streaming
+ 'vudeo_streaming': not data['webcam_localhost'],
+ 'streaming_port': int(data['webcam_port']),
+ 'streaming_framerate': int(data['webcam_maxrate']),
+ 'streaming_quality': int(data['webcam_quality']),
+ 'streaming_motion': int(data['webcam_motion']),
+
+ # still images
+ 'still_images': False,
+ 'capture_mode': 'motion-triggered',
+ 'image_file_name': '%Y-%m-%d-%H-%M-%S',
+ 'image_quality': 75,
+ 'snapshot_interval': 0,
+ 'preserve_images': data['@preserve_images'],
+
+ # motion movies
+ 'motion_movies': False,
+ 'movie_quality': 50,
+ 'movie_file_name': '%Y-%m-%d-%H-%M-%S-%q',
+ 'preserve_movies': data['@preserve_movies'],
+
+ # motion detection
+ 'show_frame_changes': data.get('text_changes') or data.get('locate'),
+ 'frame_change_threshold': data['threshold'],
+ 'auto_noise_detect': data['noise_tune'],
+ 'noise_level': int(int(data['noise_level']) / 2.55),
+ 'gap': int(data['gap']),
+ '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',
+ 'tuesday_from': '09:00', 'tuesday_to': '17:00',
+ 'wednesday_from': '09:00', 'wednesday_to': '17:00',
+ 'thursday_from': '09:00', 'thursday_to': '17:00',
+ 'friday_from': '09:00', 'friday_to': '17:00',
+ 'saturday_from': '09:00', 'saturday_to': '17:00',
+ 'sunday_from': '09:00', 'sunday_to': '17:00'
+ }
+
+ text_left = data['text_left']
+ text_right = data['text_right']
+ if text_left or text_right:
+ ui['text_overlay'] = True
+
+ if text_left == data['@name']:
+ ui['left_text'] = 'camera-name'
+
+ elif text_left == '%Y-%m-%d\n%T':
+ ui['left_text'] = 'timestamp'
+
+ else:
+ ui['left_text'] = 'custom-text'
+ ui['custom_left_text'] = text_left
+
+ if text_right == data['@name']:
+ ui['right_text'] = 'camera-name'
+
+ elif text_right == '%Y-%m-%d\n%T':
+ ui['right_text'] = 'timestamp'
+
+ else:
+ ui['right_text'] = 'custom-text'
+ ui['custom_right_text'] = text_right
+
+ output_all = data.get('output_all')
+ output_normal = data.get('output_normal')
+ jpeg_filename = data.get('jpeg_filename')
+ snapshot_interval = data.get('snapshot_interval')
+ snapshot_filename = data.get('snapshot_filename')
+
+ if (((output_all or output_normal) and jpeg_filename) or
+ (snapshot_interval and snapshot_filename)):
+
+ ui['still_images'] = True
+
+ if output_all:
+ ui['capture_mode'] = 'all-frames'
+ ui['image_file_name'] = jpeg_filename
+
+ elif data.get('snapshot_interval'):
+ ui['capture-mode'] = 'interval-snapshots'
+ ui['image_file_name'] = snapshot_filename
+ ui['snapshot_interval'] = snapshot_interval
+
+ elif data.get('output_normal'):
+ ui['capture-mode'] = 'motion-triggered'
+ ui['image_file_name'] = jpeg_filename
+
+ ui['image_quality'] = ui.get('quality', 75)
+
+ movie_filename = data.get('movie_filename')
+ if movie_filename:
+ ui['motion_movies'] = True
+ ui['movie_quality'] = int((max(2, data['ffmpeg_variable_bitrate']) - 2) / 0.29)
+ ui['movie_file_name'] = movie_filename
+
+ working_schedule = data.get('@working_schedule')
+ if working_schedule:
+ days = working_schedule.split('|')
+ ui['monday_from'], ui['monday_to'] = days[0].split('-')
+ ui['tuesday_from'], ui['tuesday_to'] = days[1].split('-')
+ ui['wednesday_from'], ui['wednesday_to'] = days[2].split('-')
+ ui['thursday_from'], ui['thursday_to'] = days[3].split('-')
+ ui['friday_from'], ui['friday_to'] = days[4].split('-')
+ ui['saturday_from'], ui['saturday_to'] = days[5].split('-')
+ ui['sunday_from'], ui['sunday_to'] = days[6].split('-')
+ ui['working_schedule'] = True
+
+ return ui
+
class SnapshotHandler(BaseHandler):
def get(self, camera_id, op, filename=None):
/* Ajax */
-function ajax(method, url, data, success) {
+function ajax(method, url, data, callback) {
var options = {
type: method,
url: url,
data: data,
cache: false,
- success: success,
+ success: callback,
failure: function (request, options, error) {
alert('Request failed with code: ' + request.status);
+ if (callback) {
+ callback();
+ }
}
};
/* UI */
function initUI() {
+ /* checkboxes */
$('input[type=checkbox].styled').each(function () {
makeCheckBox($(this));
});
+ /* sliders */
makeSlider($('#brightnessSlider'), 0, 100, 0, null, 5, 0, '%');
makeSlider($('#contrastSlider'), 0, 100, 0, null, 5, 0, '%');
makeSlider($('#saturationSlider'), 0, 100, 0, null, 5, 0, '%');
makeSlider($('#frameChangeThresholdSlider'), 0, 10000, 0, null, 3, 0, 'px');
makeSlider($('#noiseLevelSlider'), 0, 100, 0, null, 5, 0, '%');
- makeNumberValidator($('#snapshotIntervalEntry'), 1, 86400, false, false);
- makeNumberValidator($('#gapEntry'), 1, 86400, false, false);
- makeNumberValidator($('#preCaptureEntry'), 0, 100, false, false);
- makeNumberValidator($('#postCaptureEntry'), 0, 100, false, false);
+ /* number validators */
+ makeNumberValidator($('#streamingPortEntry'), 1024, 65535, false, false, true);
+ makeNumberValidator($('#snapshotIntervalEntry'), 1, 86400, false, false, true);
+ makeNumberValidator($('#gapEntry'), 1, 86400, false, false, true);
+ makeNumberValidator($('#preCaptureEntry'), 0, 100, false, false, true);
+ makeNumberValidator($('#postCaptureEntry'), 0, 100, false, false, true);
+ /* time validators */
makeTimeValidator($('#mondayFrom'));
makeTimeValidator($('#mondayTo'));
makeTimeValidator($('#tuesdayFrom'));
makeTimeValidator($('#sundayFrom'));
makeTimeValidator($('#sundayTo'));
+ /* ui elements that enable/disable other ui elements */
$('#motionEyeSwitch').change(updateConfigUI);
$('#showAdvancedSwitch').change(updateConfigUI);
$('#storageDeviceSelect').change(updateConfigUI);
$('#motionNotificationsSwitch').change(updateConfigUI);
$('#workingScheduleSwitch').change(updateConfigUI);
- $('#videoDeviceSwitch').change(fetchCameraConfig);
+ /* fetch & push handlers */
+ $('#videoDeviceSelect').change(fetchCameraConfig);
$('input.general').change(pushMainConfig);
+ $('input.device, select.device, ' +
+ 'input.storage, select.storage, ' +
+ 'input.text-overlay, select.text-overlay, ' +
+ 'input.streaming, select.streaming, ' +
+ 'input.still-images, select.still-images, ' +
+ 'input.motion-movies, select.motion-movies, ' +
+ 'input.motion-detection, select.motion-detection, ' +
+ 'input.notifications, select.notifications, ' +
+ 'input.working-schedule, select.working-schedule').change(pushCameraConfig);
}
function updateConfigUI() {
}
});
+ /* re-validate all the input validators */
+ $('div.settings').find('input.number-validator, input.time-validator').each(function () {
+ this.validate();
+ });
+
+ /* update all checkboxes and sliders */
+ $('div.settings').find('input[type=checkbox], input.range').each(function () {
+ this.update();
+ });
+
+ /* select the first option for the selects with no current selection */
+ $('div.settings').find('select').each(function () {
+ if (this.selectedIndex === -1) {
+ this.selectedIndex = 0;
+ }
+ });
+
noPushLock--;
}
noPushLock++;
$('#motionEyeSwitch')[0].checked = dict['@enabled'];
- $('#motionEyeSwitch').change();
-
$('#showAdvancedSwitch')[0].checked = dict['@show_advanced'];
- $('#showAdvancedSwitch').change();
-
$('#adminUsernameEntry').val(dict['@admin_username']);
$('#adminPasswordEntry').val(dict['@admin_password']);
$('#normalUsernameEntry').val(dict['@normal_username']);
$('#normalPasswordEntry').val(dict['@normal_password']);
+ updateConfigUI();
+
noPushLock--;
}
function cameraUi2Dict() {
return {
+ /* video device */
+ 'enabled': $('#videoDeviceSwitch')[0].checked,
+ 'name': $('#deviceNameEntry').val(),
+ 'device': $('#deviceEntry').val(),
+ 'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked,
+ 'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
+ 'brightness': $('#brightnessSlider').val(),
+ 'contrast': $('#contrastSlider').val(),
+ 'saturation': $('#saturationSlider').val(),
+ 'hue': $('#hueSlider').val(),
+ 'resolution': $('#resolutionSelect').val(),
+ 'rotation': $('#rotationSelect').val(),
+ 'framerate': $('#framerateSlider').val(),
+
+ /* file storage */
+ 'storage_device': $('#storageDeviceSelect').val(),
+ 'network_server': $('#networkServerEntry').val(),
+ 'network_share_name': $('#networkShareNameEntry').val(),
+ 'network_username': $('#networkUsernameEntry').val(),
+ 'network_password': $('#networkPasswordEntry').val(),
+ 'root_directory': $('#rootDirectoryEntry').val(),
+
+ /* text overlay */
+ 'text_overlay': $('#textOverlaySwitch')[0].checked,
+ 'left_text': $('#leftTextSelect').val(),
+ 'custom_left_text': $('#leftTextEntry').val(),
+ 'right_text': $('#rightTextSelect').val(),
+ 'custom_right_text': $('#rightTextEntry').val(),
+
+ /* video streaming */
+ 'video_streaming': $('#videoStreamingSwitch')[0].checked,
+ 'streaming_port': $('#streamingPortEntry').val(),
+ 'streaming_framerate': $('#streamingFramerateSlider').val(),
+ 'streaming_quality': $('#streamingQualitySlider').val(),
+ 'streaming_motion': $('#streamingMotion')[0].checked,
+
+ /* still images */
+ 'still_images': $('#stillImagesSwitch')[0].checked,
+ 'image_file_name': $('#imageFileNameEntry').val(),
+ 'image_quality': $('#imageQualitySlider').val(),
+ 'capture_mode': $('#captureModeSelect').val(),
+ 'snapshot_interval': $('#snapshotIntervalEntry').val(),
+ 'preserve_images': $('#preserveImagesSelect').val(),
+
+ /* motion movies */
+ 'motion_movies': $('#motionMoviesSwitch')[0].checked,
+ 'movie_file_name': $('#movieFileNameEntry').val(),
+ 'movie_quality': $('#movieQualitySlider').val(),
+ 'preserve_movies': $('#preserveMoviesSelect').val(),
+
+ /* motion detection */
+ 'show_frame_changes': $('#showFrameChangesSwitch')[0].checked,
+ 'frame_change_threshold': $('#frameChangeThresholdSlider').val(),
+ 'auto_noise_detect': $('#autoNoiseDetectSwitch')[0].checked,
+ 'noise_level': $('#noiseLevelSlider').val(),
+ 'gap': $('#gapEntry').val(),
+ 'pre_capture': $('#preCaptureEntry').val(),
+ 'post_capture': $('#postCaptureEntry').val(),
+ /* motion notifications */
+ 'motion_notifications': $('#motionNotificationsSwitch')[0].checked,
+ 'motion_notifications_emails': $('#emailAddressesEntry').val(),
+
+ /* working schedule */
+ 'working_schedule': $('#workingScheduleSwitch')[0].checked,
+ 'monday_from': $('#mondayFrom').val(),
+ 'monday_to':$('#mondayTo').val(),
+ 'tuesday_from': $('#tuesdayFrom').val(),
+ 'tuesday_to': $('#tuesdayTo').val(),
+ 'wednesday_from': $('#wednesdayFrom').val(),
+ 'wednesday_to': $('#wednesdayTo').val(),
+ 'thursday_from': $('#thursdayFrom').val(),
+ 'thursday_to': $('#thursdayTo').val(),
+ 'friday_from':$('#fridayFrom').val(),
+ 'friday_to': $('#fridayTo').val(),
+ 'saturday_from':$('#saturdayFrom').val(),
+ 'saturday_to': $('#saturdayTo').val(),
+ 'sunday_from': $('#sundayFrom').val(),
+ 'sunday_to': $('#sundayTo').val(),
};
}
noPushLock++;
/* video device */
- $('#videoDeviceSwitch');
- $('#deviceNameEntry');
- $('#lightSwitchDetectSwitch');
- $('#autoBrightnessSwitch');
- $('#brightnessSlider');
- $('#constrastSlider');
- $('#saturationSlider');
- $('#hueSlider');
- $('#resolutionSelect');
- $('#rotationSelect');
- $('#framerateSlider');
+ $('#videoDeviceSwitch')[0].checked = dict['enabled'];
+ $('#deviceNameEntry').val(dict['name']);
+ $('#deviceEntry').val(dict['device']);
+ $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect'];
+ $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness'];
+ $('#brightnessSlider').val(dict['brightness']);
+ $('#contrastSlider').val(dict['contrast']);
+ $('#saturationSlider').val(dict['saturation']);
+ $('#hueSlider').val(dict['hue']);
+ $('#resolutionSelect').val(dict['resolution']);
+ $('#rotationSelect').val(dict['rotation']);
+ $('#framerateSlider').val(dict['framerate']);
/* file storage */
- $('#storageDeviceSelect');
- $('#networkServerEntry');
- $('#networkShareNameEntry');
- $('#networkUsernameEntry');
- $('#networkPasswordEntry');
- $('#rootDirectoryEntry');
+ $('#storageDeviceSelect').val(dict['storage_device']);
+ $('#networkServerEntry').val(dict['network_server']);
+ $('#networkShareNameEntry').val(dict['network_share_name']);
+ $('#networkUsernameEntry').val(dict['network_username']);
+ $('#networkPasswordEntry').val(dict['network_password']);
+ $('#rootDirectoryEntry').val(dict['root_directory']);
/* text overlay */
- $('#textOverlaySwitch');
- $('#leftTextSelect');
- $('#leftTextEntry');
- $('#rightTextSelect');
- $('#rightTextEntry');
+ $('#textOverlaySwitch')[0].checked = dict['text_overlay'];
+ $('#leftTextSelect').val(dict['left_text']);
+ $('#leftTextEntry').val(dict['custom_left_text']);
+ $('#rightTextSelect').val(dict['right_text']);
+ $('#rightTextEntry').val(dict['custom_right_text']);
/* video streaming */
- $('#videoStreamingSwitch');
- $('#streamingFramerateSlider');
- $('#streamingQualitySlider');
- $('#motionOptimizationSwitch');
+ $('#videoStreamingSwitch')[0].checked = dict['video_streaming'];
+ $('#streamingPortEntry').val(dict['streaming_port']);
+ $('#streamingFramerateSlider').val(dict['streaming_framerate']);
+ $('#streamingQualitySlider').val(dict['streaming_quality']);
+ $('#streamingMotion')[0].checked = dict['streaming_motion'];
/* still images */
- $('#stillImagesSwitch');
- $('#imageFileNameEntry');
- $('#imageQualitySlider');
- $('#captureModeSelect');
- $('#snapshotIntervalEntry');
- $('#preserveImagesSelect');
+ $('#stillImagesSwitch')[0].checked = dict['still_images'];
+ $('#imageFileNameEntry').val(dict['image_file_name']);
+ $('#imageQualitySlider').val(dict['image_quality']);
+ $('#captureModeSelect').val(dict['capture_mode']);
+ $('#snapshotIntervalEntry').val(dict['snapshot_interval']);
+ $('#preserveImagesSelect').val(dict['preserve_images']);
/* motion movies */
- $('#motionMoviesSwitch');
- $('#movieFileNameEntry');
- $('#movieQualitySlider');
- $('#preserveMoviesSelect');
+ $('#motionMoviesSwitch')[0].checked = dict['motion_movies'];
+ $('#movieFileNameEntry').val(dict['movie_file_name']);
+ $('#movieQualitySlider').val(dict['movie_quality']);
+ $('#preserveMoviesSelect').val(dict['preserve_movies']);
/* motion detection */
- $('#showFrameChangesSwitch');
- $('#frameChangeThresholdSlider');
- $('#autoNoiseDetectSwitch');
- $('#noiseLevelSlider');
- $('#gapEntry');
- $('#preCaptureEntry');
- $('#postCaptureEntry');
+ $('#showFrameChangesSwitch')[0].checked = dict['show_frame_changes'];
+ $('#frameChangeThresholdSlider').val(dict['frame_change_threshold']);
+ $('#autoNoiseDetectSwitch')[0].checked = dict['auto_noise_detect'];
+ $('#noiseLevelSlider').val(dict['noise_level']);
+ $('#gapEntry').val(dict['gap']);
+ $('#preCaptureEntry').val(dict['pre_capture']);
+ $('#postCaptureEntry').val(dict['post_capture']);
/* motion notifications */
- $('#motionNotificationsSwitch');
- $('#emailAddressEntry');
- $('#phoneNumberEntry');
+ $('#motionNotificationsSwitch')[0].checked = dict['motion_notifications'];
+ $('#emailAddressesEntry').val(dict['motion_notifications_emails']);
/* working schedule */
- $('#workingScheduleSwitch');
- $('#mondayFrom');
- $('#mondayTo');
- $('#tuesdayFrom');
- $('#tuesdayTo');
- $('#wednesdayFrom');
- $('#wednesdayTo');
- $('#thursdayFrom');
- $('#thursdayTo');
- $('#fridayFrom');
- $('#fridayTo');
- $('#saturdayFrom');
- $('#saturdayTo');
- $('#sundayFrom');
- $('#sundayTo');
+ $('#workingScheduleSwitch')[0].checked = dict['working_schedule'];
+ $('#mondayFrom').val(dict['monday_from']);
+ $('#mondayTo').val(dict['monday_to']);
+ $('#tuesdayFrom').val(dict['tuesday_from']);
+ $('#tuesdayTo').val(dict['tuesday_to']);
+ $('#wednesdayFrom').val(dict['wednesday_from']);
+ $('#wednesdayTo').val(dict['wednesday_to']);
+ $('#thursdayFrom').val(dict['thursday_from']);
+ $('#thursdayTo').val(dict['thursday_to']);
+ $('#fridayFrom').val(dict['friday_from']);
+ $('#fridayTo').val(dict['friday_to']);
+ $('#saturdayFrom').val(dict['saturday_from']);
+ $('#saturdayTo').val(dict['saturday_to']);
+ $('#sundayFrom').val(dict['sunday_from']);
+ $('#sundayTo').val(dict['sunday_to']);
+
+ updateConfigUI();
noPushLock--;
}
return;
}
+ noPushLock++;
+
var mainConfig = mainUi2Dict();
ajax('POST', '/config/main/set/', mainConfig, function () {
-
+ noPushLock--;
+ });
+}
+
+function pushCameraConfig() {
+ if (noPushLock) {
+ return;
+ }
+
+ noPushLock++;
+
+ var cameraConfig = cameraUi2Dict();
+ var cameraId = $('#videoDeviceSelect').val();
+
+ ajax('POST', '/config/' + cameraId + '/set/', cameraConfig, function () {
+ noPushLock--;
});
}
/* add the element */
$input.after(mainDiv);
- /* add event handers */
- $input.change(function () {
- if (this.checked) {
+ function update() {
+ if ($input[0].checked) {
setOn();
}
else {
setOff();
}
- }).change();
+ }
+
+ /* add event handers */
+ $input.change(update).change();
mainDiv.click(function () {
$input[0].checked = !$input[0].checked;
}
});
+ $input[0].update = update;
+
return mainDiv;
}
$('body').unbind('mousemove', bodyMouseMove);
$('body').unbind('mouseup', bodyMouseUp);
+
+ $input.change();
}
bar.mousedown(function (e) {
}
});
+ $input[0].update = input2slider;
+
return slider;
}
msg = 'enter a positive';
}
else {
- msg = 'enter a'
+ msg = 'enter a';
}
if (floating) {
msg += ' number';
$input.keyup(validate);
$input.change(validate).change();
+
+ $input.addClass('number-validator');
+ $input[0].validate = validate;
}
function makeTimeValidator($input) {
selectOnBlur: true,
timeFormat: 'H:i',
});
+
+ $input.addClass('time-validator');
+ $input[0].validate = validate;
}
<td class="settings-item-value"><input type="text" class="styled device" id="deviceNameEntry" placeholder="camera name..."></td>
<td><span class="help-mark" title="an alias for this camera device">?</span></td>
</tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Camera Device</span></td>
+ <td class="settings-item-value"><input type="text" class="styled device" id="deviceEntry" disabled="disabled"></td>
+ </tr>
<tr class="settings-item advanced-setting">
<td colspan="100"><div class="settings-item-separator"></div></td>
</tr>
<td class="settings-item-value"><input type="text" class="range styled streaming" id="streamingQualitySlider"></td>
<td><span class="help-mark" title="sets the live streaming quality (higher values yield a better video quality but require more bandwidth)">?</span></td>
</tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Port</span></td>
+ <td class="settings-item-value"><input type="text" class="styled streaming" id="streamingPortEntry"></td>
+ <td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
+ </tr>
<tr class="settings-item advanced-setting">
<td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled streaming" id="motionOptimizationSwitch"></td>
- <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming while no motion is detected">?</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled streaming" id="streamingMotion"></td>
+ <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
</tr>
</table>
<td class="settings-item-label"><span class="settings-item-label">Preserve Images</span></td>
<td class="settings-item-value">
<select class="styled still-images" id="preserveImagesSelect">
- <option>For One Day</option>
- <option>For One Week</option>
- <option>For One Month</option>
- <option>For One Year</option>
- <option>Forever</option>
+ <option value="1">For One Day</option>
+ <option value="7">For One Week</option>
+ <option value="30">For One Month</option>
+ <option value="365">For One Year</option>
+ <option value="0">Forever</option>
</select>
</td>
<td><span class="help-mark" title="images older than the specified duration are automatically deleted to free storage space">?</span></td>
<td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
<td class="settings-item-value">
<select class="styled motion-movies" id="preserveMoviesSelect">
- <option>For One Day</option>
- <option>For One Week</option>
- <option>For One Month</option>
- <option>For One Year</option>
- <option>Forever</option>
+ <option value="1">For One Day</option>
+ <option value="7">For One Week</option>
+ <option value="30">For One Month</option>
+ <option value="365">For One Year</option>
+ <option value="0">Forever</option>
</select>
</td>
<td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free storage space">?</span></td>
<div class="settings-section-title"><input type="checkbox" class="styled section notifications" id="motionNotificationsSwitch">Motion Notifications</div>
<table class="settings">
<tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Email Address</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications" id="emailAddressEntry" placeholder="email address..."></td>
+ <td class="settings-item-label"><span class="settings-item-label">Email Addresses</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications" id="emailAddressesEntry" placeholder="email addresses..."></td>
<td><span class="help-mark" title="email addresses (separated by comma) that are added here will receive notifications whenever a motion event is detected (leave empty to disable email notifications)">?</span></td>
</tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Phone Number</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications" id="phoneNumberEntry" placeholder="phone number..."></td>
- <td><span class="help-mark" title="phone numbers (separated by comma) that are added here will receive SMS notifications whenever a motion event is detected (leave empty to disable SMS notifications)">?</span></td>
- </tr>
</table>
<div class="settings-section-title"><input type="checkbox" class="styled section working-schedule" id="workingScheduleSwitch">Working Schedule</div>