// This file is part of Motion Watch. // Motion Watch 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. // Motion Watch 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. #include "mo_detect.h" #include "logger.h" void detectMoInFile(const string &detPath, const string &recPath, shared_t *share) { detLog("detect_mo_in_file() -- start", share); Mat thumbNail; if (moDetect(detPath, thumbNail, share)) { share->skipCmd = true; wrOut(recPath, thumbNail, share); } if (exists(detPath)) remove(detPath); if (exists(recPath)) remove(recPath); detLog("detect_mo_in_file() -- finished", share); } static void *runCmd(void *arg) { auto args = static_cast(arg); args->finished = false; args->ret = 0; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); args->ret = pclose(popen(args->cmd.c_str(), "r")); args->finished = true; return NULL; } bool allCmdsFinished(const vector &resList) { for (auto &&res : resList) { if (!res->finished) return false; } return true; } void cancelAllCmds(const vector &resList) { for (auto &&res : resList) { if (!res->finished) { pthread_cancel(res->thr); } } } bool allCmdsDidNotFail(const vector &resList) { for (auto &&res : resList) { if (res->ret != 0) return false; } return true; } void cleanupRes(vector &resList) { for (auto &&res : resList) { delete res; } } void runTOCmds(int timeOut, const vector &cmds, vector &resList, bool abs = true) { resList.clear(); for (auto &&cmd : cmds) { auto res = new struct cmdRes_t; res->cmd = cmd; res->finished = false; res->ret = pthread_create(&res->thr, NULL, &runCmd, res); resList.push_back(res); } if (abs) { sleep(timeOut); if (!allCmdsFinished(resList)) { sleep(2); } cancelAllCmds(resList); } else { for (auto i = 0; !allCmdsFinished(resList); ++i) { sleep(1); if (i >= timeOut) { cancelAllCmds(resList); break; } } } } void runTOCmd(int timeout, const string &cmd, bool abs = true) { vector cmds; vector rets; cmds.push_back(cmd); runTOCmds(timeout, cmds, rets, abs); cleanupRes(rets); } void recLoop(shared_t *share) { while (rdConf(share)) { enforceMaxLogSize(share->recLogPath, share); enforceMaxLogSize(share->detLogPath, share); initLogFile(share->recLogPath, share->recLogFile); initLogFile(share->detLogPath, share->detLogFile); initLogFrontPages(share); recLog("rec_loop() -- start", share); if (!exists("/tmp/mow-lock") && share->updateRoot) { system("touch /tmp/mow-lock"); genCSS(share); genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share); remove("/tmp/mow-lock"); recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); share->updateRoot = false; } genHTMLul(share->outDir, share->camName, share); recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); for (auto i = 0; i < share->numOfClips; ++i) { auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix; auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix; vector cmds; vector rets; if (share->recordUrl == share->detectUrl) { recPath = detPath; } else { cmds.push_back("ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + recPath); } cmds.push_back("ffmpeg -hide_banner -i " + share->detectUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + detPath); recLog("fetching camera footage -- ", share); runTOCmds(share->clipLen, cmds, rets); if (allCmdsDidNotFail(rets)) { new thread(detectMoInFile, detPath, recPath, share); } else { recLog("one or both fetch cmds failed.", share); } recLog("fetch results:", share); for (auto &&ret : rets) { recLog("cmd: " + ret->cmd + " return_code: " + to_string(ret->ret), share); } } if (!share->skipCmd) { recLog("no motion detected", share); if (share->postCmd.empty()) { recLog("post command not defined, skipping.", share); } else { recLog("running post command: " + share->postCmd, share); runTOCmd(14, share->postCmd, false); } } else { recLog("motion detected, skipping the post command.", share); } recLog("rec_loop() -- finished", share); if (share->retCode != 0) { break; } } } void sigHandler(int signal, siginfo_t *info, void *context) { cerr << "received signal details: " << endl; cerr << "-- si_pid: " << info->si_pid << endl; cerr << "-- si_signo: " << info->si_signo << endl; cerr << "-- si_code: " << info->si_code << endl; cerr << "-- si_errno: " << info->si_errno << endl; cerr << "-- si_uid: " << info->si_uid << endl; cerr << "-- si_addr: " << info->si_addr << endl; cerr << "-- si_status: " << info->si_status << endl; cerr << "-- si_band: " << info->si_band << endl; exit(EXIT_FAILURE); } int main(int argc, char** argv) { struct shared_t sharedRes; struct sigaction act = { 0 }; act.sa_flags = SA_SIGINFO; act.sa_sigaction = &sigHandler; sigaction(SIGTERM, &act, NULL); sigaction(SIGABRT, &act, NULL); sigaction(SIGSEGV, &act, NULL); sigaction(SIGILL, &act, NULL); sigaction(SIGFPE, &act, NULL); sigaction(SIGBUS, &act, NULL); sharedRes.conf = parseForParam("-c", argc, argv, false); if (parseForParam("-h", argc, argv, true) == "true") { cout << "Motion Watch " << APP_VER << endl << endl; cout << "Usage: mow " << endl << endl; cout << "-h : display usage information about this application." << endl; cout << "-c : path to the config file." << endl; cout << "-v : display the current version." << endl << endl; } else if (parseForParam("-v", argc, argv, true) == "true") { cout << APP_VER << endl; } else if (sharedRes.conf.empty()) { cerr << "err: config file not given in -c" << endl; } else { sharedRes.retCode = 0; sharedRes.updateRoot = true; sharedRes.skipCmd = false; sharedRes.init = true; recLoop(&sharedRes); return sharedRes.retCode; } return EINVAL; }