__init__.py (6336B)
1 # strix/__init__.py 2 # 3 # Copyright (C) 2017 Brian C. Lane 4 # 5 # This program is free software; you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation; either version 2 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 import multiprocessing as mp 18 import os 19 import re 20 import time 21 22 from . import api 23 from . import cmdline 24 from . import events 25 from . import queue 26 from . import logger 27 from . import motion 28 29 ## Check the motion args 30 ## Start the queue watcher thread 31 ## Start the bottle/API thread 32 ## Wait for a signal to shutdown 33 34 def check_motion_config(config_path): 35 """ Check the config file to make sure the settings match what Strix needs. 36 """ 37 picture_filename = "%Y-%m-%d/%v/%H-%M-%S-%q" 38 on_event_end_re = r".*touch (.*)/queue/Camera%t_%Y-%m-%d_%v" 39 target_dir_re = r"(.*)/Camera\d+" 40 41 errors = [] 42 base_target_dir = "" 43 base_queue_dir = "" 44 cameras = [] 45 found_pf = False 46 47 cfg = motion.config.MotionConfig(config_path) 48 49 # If there are threads, check their settings instead 50 for c in [cfg.config] + \ 51 [cfg.thread[cc] for cc in filter(lambda k: k.startswith("thread"), cfg.config.keys())]: 52 if c.get("picture_filename", "") == picture_filename: 53 found_pf = True 54 55 on_event_end = c.get("on_event_end", "") 56 if on_event_end: 57 em = re.match(on_event_end_re, on_event_end) 58 if not em or not em.groups(): 59 continue 60 # Above errors will be caught by not having base_queue_dir set. 61 if not base_queue_dir: 62 base_queue_dir = em.group(1) 63 elif base_queue_dir != em.group(1): 64 errors += ["All of the paths in on_event_end MUST match."] 65 66 target_dir = c.get("target_dir", "") 67 if target_dir: 68 tm = re.match(target_dir_re, target_dir) 69 if not tm or not tm.groups(): 70 continue 71 # Above errors will be caught by not having base_target_dir set. 72 if not base_target_dir: 73 base_target_dir = tm.group(1) 74 elif base_target_dir != tm.group(1): 75 errors += ["All of the base paths in target_dir MUST match."] 76 77 # Gather the info about the cameras and ports 78 camera_name = c.get("camera_name", "") 79 stream_port = c.get("stream_port", 8080) 80 if camera_name: 81 cameras.append((camera_name, stream_port)) 82 83 if not base_target_dir: 84 errors += ["Could not find a target_dir setting. The last directory must be /CameraX"] 85 if not base_queue_dir: 86 errors += ["Could not find an on_event_end setting. It must be set to /usr/bin/touch <TARGET_DIR>/queue/Camera%t_%Y-%m-%d_%v"] 87 if base_target_dir and base_queue_dir and base_target_dir != base_queue_dir: 88 errors += ["The target_dir and the base dir for on_event_end MUST match. eg. /var/lib/motion/"] 89 if not found_pf: 90 errors += ["picture_filename MUST be set to %s" % picture_filename] 91 92 # Cameras are sorted by their stream_port 93 cameras = sorted(cameras, key=lambda c: c[1]) 94 return (base_target_dir, cameras, errors) 95 96 97 def run(): 98 parser = cmdline.parser(queue.max_cores()) 99 opts = parser.parse_args() 100 101 try: 102 (base_dir, cameras, errors) = check_motion_config(opts.config) 103 except Exception as e: 104 errors = [str(e)] 105 106 if errors: 107 def p_e(e): 108 print("ERROR: %s" % e) 109 list(map(p_e, errors)) 110 return False 111 112 # Start logger thread 113 logger_queue = mp.JoinableQueue() 114 logger_quit = mp.Event() 115 logger_thread = mp.Process(name="logger-thread", 116 target=logger.listener, 117 args=(logger_queue, logger_quit, opts.log)) 118 logger_thread.start() 119 running_threads = [(logger_thread, logger_quit)] 120 121 # Setup a console logger for the startup messages 122 import logging 123 log = logging.getLogger("startup-logging") 124 log.setLevel(level=logging.DEBUG) 125 ch = logging.StreamHandler() 126 ch.setLevel(logging.DEBUG) 127 log.addHandler(ch) 128 129 # Initialize Event Cache settings 130 events.EventCache.logger(log) 131 events.EventCache.base_dir(base_dir) 132 events.EventCache.keep(opts.keep_days) 133 events.EventCache.check_cache(opts.check_cache) 134 events.EventCache.cleanup_dq() 135 events.preload_cache(log, base_dir) 136 137 # Start queue monitor and processing thread (starts its own Multiprocessing threads) 138 queue_path = os.path.abspath(os.path.join(base_dir, "queue/")) 139 if not os.path.exists(queue_path): 140 print("ERROR: %s does not exist. Is motion running?" % queue_path) 141 return False 142 queue_quit = mp.Event() 143 queue_rx, queue_tx = mp.Pipe(False) 144 queue_thread = mp.Process(name="queue-thread", 145 target=queue.monitor_queue, 146 args=(logger_queue, base_dir, queue_quit, opts.max_cores, queue_tx)) 147 queue_thread.start() 148 running_threads += [(queue_thread, queue_quit)] 149 150 # Start API thread (may start its own threads to handle requests) 151 api_quit = mp.Event() 152 api_thread = mp.Process(name="api-thread", 153 target=api.run_api, 154 args=(logger_queue, base_dir, cameras, opts.host, opts.port, opts.debug, queue_rx)) 155 api_thread.start() 156 running_threads += [(api_thread, api_quit)] 157 158 # Wait until it is told to exit 159 try: 160 while True: 161 time.sleep(10) 162 except Exception as e: 163 print("ERROR: %s" % e) 164 except KeyboardInterrupt: 165 print("Exiting due to ^C") 166 167 # Tell the threads to quit 168 for _thread, event in running_threads: 169 event.set() 170 171 # Wait until everything is done 172 print("Waiting for threads to quit") 173 for thread, _event in running_threads: 174 thread.join() 175 176 return True