From baeaabbd556a3c7f31c0da2623dc8d9e9efe24f7 Mon Sep 17 00:00:00 2001 From: Maurice ONeal Date: Sun, 11 Dec 2022 10:25:22 -0500 Subject: [PATCH] v1.5.t3 Fixed the default webroot directory to apache's correct webroot. Also renamed separated outDir from webRoot and made webRoot changeable on the config file. Added logging the recorder and detection loops to help with debugging and troubleshooting. Just like the video clips, max log lines were added to control the size of the data being saved to storage. --- CMakeLists.txt | 2 +- src/common.cpp | 30 +++++++++++----- src/common.h | 44 ++++++++++++++---------- src/logger.cpp | 73 +++++++++++++++++++++++++++++++++++++++ src/logger.h | 24 +++++++++++++ src/main.cpp | 50 +++++++++++++++++++++++---- src/mo_detect.cpp | 35 ++++++++++++++----- src/mo_detect.h | 3 +- src/web.cpp | 87 +++++++++++++++++++++++++++++++---------------- src/web.h | 3 +- 10 files changed, 277 insertions(+), 74 deletions(-) create mode 100644 src/logger.cpp create mode 100644 src/logger.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fa46d25..04cc324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,5 +3,5 @@ project( MotionWatch ) find_package( OpenCV REQUIRED ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pthread") include_directories( ${OpenCV_INCLUDE_DIRS} ) -add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp ) +add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp src/logger.cpp ) target_link_libraries( mow ${OpenCV_LIBS} ) diff --git a/src/common.cpp b/src/common.cpp index 5f30676..f590acd 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -114,7 +114,12 @@ vector lsDirsInDir(const string &path) { if (ent->d_type & DT_DIR) { - names.push_back(string(ent->d_name)); + auto name = string(ent->d_name); + + if ((name != "..") || (name != ".")) + { + names.push_back(name); + } } } @@ -210,17 +215,22 @@ bool rdConf(shared_t *share) share->postCmd.clear(); share->buffDir.clear(); + share->retCode = 0; share->frameGap = 10; share->pixThresh = 30; share->imgThresh = 512; share->secs = 60; share->maxDays = 15; share->maxClips = 30; + share->maxLogLines = 1000; share->camName = path(share->conf.c_str()).filename(); - share->outDir = "/var/www/hmtl"; + share->webRoot = "/var/www/html"; share->vidExt = "mp4"; share->recLoopWait = false; share->skipCmd = false; + share->webBg = "#485564"; + share->webTxt = "#dee5ee"; + share->webFont = "courier"; do { @@ -230,7 +240,10 @@ bool rdConf(shared_t *share) { rdLine("cam_name = ", line, &share->camName); rdLine("recording_stream = ", line, &share->recordUrl); - rdLine("output_dir = ", line, &share->outDir); + rdLine("web_root = ", line, &share->webRoot); + rdLine("web_text = ", line, &share->webTxt); + rdLine("web_bg = ", line, &share->webBg); + rdLine("web_font = ", line, &share->webFont); rdLine("post_cmd = ", line, &share->postCmd); rdLine("duration = ", line, &share->secs); rdLine("buff_dir = ", line, &share->buffDir); @@ -239,12 +252,16 @@ bool rdConf(shared_t *share) rdLine("img_thresh = ", line, &share->imgThresh); rdLine("max_days = ", line, &share->maxDays); rdLine("max_clips = ", line, &share->maxClips); + rdLine("max_log_lines = ", line, &share->maxLogLines); rdLine("vid_container = ", line, &share->vidExt); } } while(!line.empty()); - share->outDir = cleanDir(share->outDir) + "/" + share->camName; + share->outDir = cleanDir(share->webRoot) + "/" + share->camName; + + createDirTree(cleanDir(share->buffDir)); + createDirTree(share->outDir); if (share->init) { @@ -252,11 +269,6 @@ bool rdConf(shared_t *share) share->init = false; } - - createDirTree(cleanDir(share->buffDir)); - createDirTree(share->outDir); - - share->retCode = 0; } varFile.close(); diff --git a/src/common.h b/src/common.h index 675779c..729dab6 100644 --- a/src/common.h +++ b/src/common.h @@ -36,28 +36,36 @@ using namespace cv; using namespace std; using namespace std::filesystem; -#define APP_VER "1.5.t2" +#define APP_VER "1.5.t3" #define APP_NAME "Motion Watch" struct shared_t { - string recordUrl; - string outDir; - string postCmd; - string conf; - string buffDir; - string vidExt; - string camName; - bool init; - bool recLoopWait; - bool skipCmd; - int frameGap; - int pixThresh; - int imgThresh; - int secs; - int maxDays; - int maxClips; - int retCode; + vector recLogLines; + vector detLogLines; + string recordUrl; + string outDir; + string postCmd; + string conf; + string buffDir; + string vidExt; + string camName; + string webBg; + string webTxt; + string webFont; + string webRoot; + bool init; + bool recLoopWait; + bool logRun; + bool skipCmd; + int frameGap; + int pixThresh; + int imgThresh; + int secs; + int maxDays; + int maxClips; + int maxLogLines; + int retCode; }; string genDstFile(const string &dirOut, const char *fmt, const string &ext); diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000..152c898 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,73 @@ +// 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 "logger.h" + +void recLog(const string &line, shared_t *share) +{ + share->recLogLines.push_back(genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "
\n"); +} + +void detLog(const string &line, shared_t *share) +{ + share->detLogLines.push_back(genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "
\n"); +} + +void enforceMaxLogLines(vector &log, shared_t *share) +{ + while (log.size() > share->maxLogLines) + { + log.erase(log.begin()); + } +} + +string combine(const vector &log) +{ + string ret; + + for (auto &&line : log) ret += line; + + return ret; +} + +void logLoop(shared_t *share) +{ + string htmlText = "\n"; + + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += ""; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "

\n"; + + while (share->logRun && (share->retCode == 0)) + { + sleep(10); + + enforceMaxLogLines(share->recLogLines, share); + enforceMaxLogLines(share->detLogLines, share); + + auto recLogFilePath = cleanDir(share->outDir) + "/recording_log.html"; + auto detLogFilePath = cleanDir(share->outDir) + "/detection_log.html"; + + ofstream recFile(recLogFilePath.c_str()); + ofstream detFile(detLogFilePath.c_str()); + + recFile << htmlText << combine(share->recLogLines) << "

\n\n"; + detFile << htmlText << combine(share->detLogLines) << "

\n\n"; + + recFile.close(); + detFile.close(); + } +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..d4be579 --- /dev/null +++ b/src/logger.h @@ -0,0 +1,24 @@ +#ifndef lOGGER_H +#define lOGGER_H + +// 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 "common.h" + +void recLog(const string &line, shared_t *share); +void detLog(const string &line, shared_t *share); +void logLoop(shared_t *share); +void enforceMaxLogLines(vector &log, shared_t *share); +string combine(const vector &log); + +#endif // lOGGER_H diff --git a/src/main.cpp b/src/main.cpp index 2870b6c..f5965a8 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,9 +11,12 @@ // GNU General Public License for more details. #include "mo_detect.h" +#include "logger.h" void detectLoop(shared_t *share) { + detLog("detectLoop() -- start", share); + vector bufFiles; do @@ -35,10 +38,12 @@ void detectLoop(shared_t *share) { share->skipCmd = true; + detLog("motion detected in file: " + fullPath, share); wrOut(fullPath, thumbNail, share); } else { + detLog("no motion detected in file: " + fullPath + " removing it.", share); remove(fullPath.c_str()); } } @@ -48,23 +53,29 @@ void detectLoop(shared_t *share) } } while (!bufFiles.empty()); + + detLog("detectLoop() -- finished", share); } void recLoop(shared_t *share) { while (rdConf(share)) { + recLog("recLoop() -- start", share); + if (!fileExists("/tmp/mow-lock")) { + recLog("/tmp/mow-lock not found, assuming it is safe to update the webroot page.", share); + recLog("webroot page = " + cleanDir(share->webRoot) + "/index.html", share); system("touch /tmp/mow-lock"); - - auto webRoot = path(share->outDir).parent_path().string(); - - genHTMLul(webRoot, string(APP_NAME) + " " + string(APP_VER)); + genCSS(share); + genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share); system("rm /tmp/mow-lock"); } - - createDirTree(share->buffDir); + else + { + recLog("/tmp/mow-lock pesent, skipping update of the webroot page.", share); + } auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt; auto secs = to_string(share->secs); @@ -73,14 +84,24 @@ void recLoop(shared_t *share) thread th2(detectLoop, share); - if (system(cmd.c_str()) != 0) + recLog("detect_loop started in a seperate thread.", share); + recLog("ffmpeg = " + cmd, share); + + auto ret = system(cmd.c_str()); + + if (ret == 0) { + recLog("ffmpeg_return_code = " + to_string(ret), share); + share->recLoopWait = true; th2.join(); } else { + recLog("ffmpeg failed, cooling down for " + to_string(share->secs) + "secs.", share); + recLog("ffmpeg_return_code = " + to_string(ret), share); + // simulate that ffmpeg is running even after it has failed. sleep(share->secs); @@ -89,9 +110,19 @@ void recLoop(shared_t *share) if (!share->skipCmd) { + recLog("motion not detected by detect loop.", share); + recLog("running post command = " + share->postCmd, share); system(share->postCmd.c_str()); } + else + { + recLog("motion detected by detect loop, skpping the post command.", share); + } + + recLog("recLoop() -- finished", share); } + + share->logRun = false; } int main(int argc, char** argv) @@ -122,9 +153,14 @@ int main(int argc, char** argv) sharedRes.recLoopWait = false; sharedRes.skipCmd = false; sharedRes.init = true; + sharedRes.logRun = true; + + thread th3(logLoop, &sharedRes); recLoop(&sharedRes); + th3.join(); + return sharedRes.retCode; } diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp index 9d23270..15a5434 100644 --- a/src/mo_detect.cpp +++ b/src/mo_detect.cpp @@ -12,14 +12,16 @@ #include "mo_detect.h" -bool imgDiff(const Mat &prev, const Mat &next, shared_t *share) +bool imgDiff(const Mat &prev, const Mat &next, int &diffScore, shared_t *share) { Mat diff; absdiff(prev, next, diff); threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY); - return countNonZero(diff) >= share->imgThresh; + diffScore = countNonZero(diff); + + return diffScore >= share->imgThresh; } Mat frameFF(VideoCapture *cap, int gap) @@ -57,6 +59,10 @@ void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share) auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt); auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg"); + detLog("write_out_vid: " + vidOut, share); + detLog("write_out_img: " + imgOut, share); + detLog("remove_file: " + buffFile, share); + enforceMaxClips(outDir, share); copy_file(buffFile.c_str(), vidOut.c_str()); @@ -65,8 +71,8 @@ void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share) imwrite(imgOut.c_str(), vidThumb); genHTMLvid(vidOut, share); - genHTMLul(outDir, share->camName + ": " + dayStr); - genHTMLul(share->outDir, share->camName); + genHTMLul(outDir, share->camName + ": " + dayStr, share); + genHTMLul(share->outDir, share->camName, share); } bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) @@ -79,6 +85,9 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) { Mat prev; Mat next; + int diff = 0; + int maxDiff = 0; + int frameGaps = 0; do { @@ -87,19 +96,29 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) next = frameFF(&capture, share->frameGap); - if (imgDiff(prev, next, share)) + if (imgDiff(prev, next, diff, share)) { - resize(next, vidThumb, Size(360, 202), INTER_LINEAR); + resize(next, vidThumb, Size(1280, 720), INTER_LINEAR); + + if (diff > maxDiff) + { + maxDiff = diff; + } mod = true; break; } + + frameGaps++; } while (!prev.empty() && !next.empty()); + + detLog("scanned_buff_file = " + buffFile + " max_score = " + to_string(maxDiff) + " frame_gaps = " + to_string(frameGaps), share); } else { - cerr << "err: Could not open buff file: " << buffFile << " for reading. check formatting/permissions." << endl; - cerr << " Also check if opencv was compiled with FFMPEG encoding enabled." << endl; + detLog("failed to open buff file: " + buffFile + " for reading.", share); + detLog("check formatting/permissions.", share); + detLog("also check if opencv was compiled with FFMPEG encoding enabled.", share); } return mod; diff --git a/src/mo_detect.h b/src/mo_detect.h index 8799101..459f26c 100644 --- a/src/mo_detect.h +++ b/src/mo_detect.h @@ -15,8 +15,9 @@ #include "common.h" #include "web.h" +#include "logger.h" -bool imgDiff(const Mat &prev, const Mat &next, shared_t *share); +bool imgDiff(const Mat &prev, const Mat &next, int &diffScore, shared_t *share); bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share); void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share); Mat frameFF(VideoCapture *cap, int gap); diff --git a/src/web.cpp b/src/web.cpp index 7208781..f6c165e 100644 --- a/src/web.cpp +++ b/src/web.cpp @@ -12,54 +12,64 @@ #include "web.h" -void genHTMLul(const string &outputDir, const string &title) +void genHTMLul(const string &outputDir, const string &title, shared_t *share) { DIR *dir; struct dirent *ent; - vector regNames; - vector dirNames; + vector logNames; + vector regNames = lsFilesInDir(outputDir); + vector dirNames = lsDirsInDir(outputDir); string htmlText = "\n"; htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; htmlText += "\n"; - htmlText += "

" + title + "

\n"; - htmlText += "\n"; + if (!logNames.empty()) + { + htmlText += "

Logs

\n"; + htmlText += "
\n"; + } + htmlText += "\n"; htmlText += ""; @@ -79,8 +89,11 @@ void genHTMLvid(const string &outputVid, shared_t *share) filename = replaceAll(filename, share->vidExt, "html"); htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; htmlText += "\n"; - htmlText += "