25#include <opencv2/opencv.hpp>
29 static std::string dir;
35 o.
connect(
"/Logger/History/IMAGE");
37 if (o[
"History dir"] != std::string(
""))
38 dir = o[
"History dir"];
42 dir = l[
"History dir"];
52 if (dir.back() !=
'/')
61static size_t write_data(
void *ptr,
size_t size,
size_t nmemb,
void *stream)
63 size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
67static std::vector<std::thread> _image_threads;
70int mkpath(std::string dir, mode_t mode)
76 if (!stat(dir.c_str(), &sb))
82 mkpath(p.c_str(), mode);
85 return mkdir(dir.c_str(), mode);
89void image_thread(std::string
name) {
90 DWORD last_check_delete = 0;
93 cv::VideoCapture* cap =
new cv::VideoCapture();
95 int failed_connections = 0;
100 std::this_thread::sleep_for(std::chrono::milliseconds(20));
106 if (
ss_time() > last_check_delete + 60 && o[
"Storage hours"] > 0) {
115 for (
unsigned i=0 ;
i<flist.size() ;
i++) {
116 std::string filename = flist[
i];
118 sscanf(filename.c_str(),
"%2d%2d%2d_%2d%2d%2d", &ti.tm_year, &ti.tm_mon,
119 &ti.tm_mday, &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
124 double age = (
ss_time() - ft)/3600.0;
125 if (age >= o[
"Storage hours"]) {
126 std::string pathname = (path+
"/"+filename);
127 int error =
remove(pathname.c_str());
129 if (error && errno != ENOENT)
130 cm_msg(
MERROR,
"image_thread",
"Cannot remove file \"%s\", remove() errno %d (%s)", pathname.c_str(), errno, strerror(errno));
141 if (
ss_time() >= o[
"Last fetch"] + o[
"Period"]) {
143 std::string url = o[
"URL"];
144 bool is_rtsp =
false;
146 if (url.compare(0,7,
"rtsp://") == 0) {
149 if (!cap->isOpened() ) {
151 if (!cap->open(url.c_str())) {
152 std::cout <<
"Unable to open video capture\n";
153 failed_connections++;
154 std::string error =
"Cannot connect to camera \"" +
name +
"\" at " + url +
", please check camera power and URL";
156 if (failed_connections > 10)
157 cm_msg(
MERROR,
"image_history_rtsp",
"More than 10 failed connections, I will stop reporting this error");
161 failed_connections = 0;
166 std::string error =
"Cannot connect to camera \"" +
name +
"\". mlogger not build with rtsp support (OpenCV)";
171 std::string dotname = filename;
172 int status = mkpath(filename, 0755);
174 cm_msg(
MERROR,
"image_thread",
"Cannot create directory \"%s\": mkpath() errno %d (%s)", filename.c_str(), errno, strerror(errno));
178 time_t now = time(
nullptr);
180 localtime_r(&now, <m);
183 std::setfill(
'0') << std::setw(2) << ltm.tm_year - 100 <<
184 std::setfill(
'0') << std::setw(2) << ltm.tm_mon + 1 <<
185 std::setfill(
'0') << std::setw(2) << ltm.tm_mday <<
187 std::setfill(
'0') << std::setw(2) << ltm.tm_hour <<
188 std::setfill(
'0') << std::setw(2) << ltm.tm_min <<
189 std::setfill(
'0') << std::setw(2) << ltm.tm_sec;
190 filename +=
"/" + s.str();
191 dotname +=
"/." + s.str();
193 if (o[
"Extension"] == std::string(
"")) {
194 filename += url.substr(url.find_last_of(
'.'));
195 dotname += url.substr(url.find_last_of(
'.'));
197 filename += o[
"Extension"];
198 dotname += o[
"Extension"];
204 bool OK = cap->grab();
206 std::string error =
"Cannot grab from camera \"" +
name +
"\" at " + url +
", please check camera power and URL";
207 cm_msg(
MERROR,
"log_image_history",
"%s", error.c_str());
210 cap =
new cv::VideoCapture();
216 std::string error =
"Recieved empty frame from camera \"" +
name;
217 cm_msg(
MERROR,
"log_image_history",
"%s", error.c_str());
220 cap =
new cv::VideoCapture();
224 cv::imwrite(filename, frame);
227 CURL *conn = curl_easy_init();
228 curl_easy_setopt(conn, CURLOPT_URL, url.c_str());
229 curl_easy_setopt(conn, CURLOPT_WRITEFUNCTION, write_data);
230 curl_easy_setopt(conn, CURLOPT_VERBOSE, 0L);
231 auto f = fopen(dotname.c_str(),
"wb");
233 curl_easy_setopt(conn, CURLOPT_WRITEDATA, f);
234 curl_easy_setopt(conn, CURLOPT_TIMEOUT, 60L);
235 int status = curl_easy_perform(conn);
238 if (
status == CURLE_COULDNT_CONNECT) {
239 error =
"Cannot connect to camera \"" +
name +
"\" at " + url +
", please check camera power and URL";
240 }
else if (
status != CURLE_OK) {
241 error =
"Error fetching image from camera \"" +
name +
"\", curl status " + std::to_string(
status);
244 curl_easy_getinfo(conn, CURLINFO_RESPONSE_CODE, &http_code);
245 if (http_code != 200)
246 error =
"Error fetching image from camera \"" +
name +
"\", http error status " +
247 std::to_string(http_code);
249 if (!error.empty()) {
250 if (
ss_time() > o[
"Last error"] + o[
"Error interval (s)"]) {
251 cm_msg(
MERROR,
"log_image_history",
"%s", error.c_str());
258 rename(dotname.c_str(), filename.c_str());
260 curl_easy_cleanup(conn);
269 for (
auto &t : _image_threads)
271 curl_global_cleanup();
275 return _image_threads.size();
279 curl_global_init(CURL_GLOBAL_DEFAULT);
293 {
"Name",
"Demo Camera"},
295 {
"URL",
"https://localhost:8000/image.jpg"},
296 {
"Extension",
".jpg"},
299 {
"Storage hours", 0},
300 {
"Error interval (s)", 60},
304 c.
connect(ic.get_odb().get_full_path());
306 std::string
name = ic.get_odb().get_name();
307 if (
name !=
"Demo" ||
c[
"Enabled"] ==
true)
308 _image_threads.push_back(std::thread(image_thread,
name));
333 std::vector<time_t> &vtime, std::vector<std::string> &vfilename)
342 localtime_r(&start_time, <m);
345 std::setfill(
'0') << std::setw(2) << ltm.tm_year - 100 <<
346 std::setfill(
'0') << std::setw(2) << ltm.tm_mon + 1 <<
347 std::setfill(
'0') << std::setw(2) << ltm.tm_mday <<
352 struct tm ltStart, ltStop;
353 localtime_r(&start_time, <Start);
355 std::stringstream sStart, sStop;
356 std::string mStart, mStop;
357 mask =
"??????_??????.*";
360 std::setfill(
'0') << std::setw(2) << ltStart.tm_year - 100 <<
361 std::setfill(
'0') << std::setw(2) << ltStart.tm_mon + 1 <<
362 std::setfill(
'0') << std::setw(2) << ltStart.tm_mday <<
364 std::setfill(
'0') << std::setw(2) << ltStart.tm_hour <<
365 std::setfill(
'0') << std::setw(2) << ltStart.tm_min <<
366 std::setfill(
'0') << std::setw(2) << ltStart.tm_sec;
367 mStart = sStart.str();
369 std::setfill(
'0') << std::setw(2) << ltStop.tm_year - 100 <<
370 std::setfill(
'0') << std::setw(2) << ltStop.tm_mon + 1 <<
371 std::setfill(
'0') << std::setw(2) << ltStop.tm_mday <<
373 std::setfill(
'0') << std::setw(2) << ltStop.tm_hour <<
374 std::setfill(
'0') << std::setw(2) << ltStop.tm_min <<
375 std::setfill(
'0') << std::setw(2) << ltStop.tm_sec;
377 for (
int i=0 ;
i<13 ;
i++) {
378 if (mStart[
i] == mStop[
i])
392 std::sort(vfn.begin(), vfn.end());
394 time_t minDiff = 1E7;
398 for (
unsigned i=0 ;
i<vfn.size() ;
i++) {
400 sscanf(vfn[
i].c_str(),
"%2d%2d%2d_%2d%2d%2d", &ti.tm_year, &ti.tm_mon,
401 &ti.tm_mday, &ti.tm_hour, &ti.tm_min, &ti.tm_sec);
409 if (abs(ft - start_time) < minDiff) {
410 minDiff = abs(ft - start_time);
417 vfilename.push_back(vfn[
i]);
422 if (start_time ==
stop_time && vfn.size() > 0) {
423 vtime.push_back(minTime);
424 vfilename.push_back(vfn[minIndex]);
static bool exists(const std::string &name)
static int create(const char *name, int type=TID_KEY)
bool is_subkey(std::string str)
void connect(const std::string &path, const std::string &name, bool write_defaults, bool delete_keys_not_in_defaults=false)
std::string cm_get_path()
time_t ss_mktime(struct tm *tms)
INT ss_file_find(const char *path, const char *pattern, char **plist)
INT cm_msg(INT message_type, const char *filename, INT line, const char *routine, const char *format,...)
std::string history_dir()
void stop_image_history()
int hs_image_retrieve(std::string image_name, time_t start_time, time_t stop_time, std::vector< time_t > &vtime, std::vector< std::string > &vfilename)
int get_number_image_history_threads()
void start_image_history()
volatile int stop_all_threads
static std::string join(const char *sep, const std::vector< std::string > &v)
std::vector< std::string > STRING_LIST
static std::string remove(const std::string s, char c)