MIDAS
Loading...
Searching...
No Matches
history_image.cxx
Go to the documentation of this file.
1/********************************************************************\
2
3 Name: history_image.cxx
4 Created by: Stefan Ritt
5
6 Contents: Logger module saving images from webcams through
7 network HTTP link into subdirectories. These images
8 can then be retrived in the history page.
9
10\********************************************************************/
11
12#include <string>
13#include <exception>
14#include <sstream>
15#include <iomanip>
16#include <thread>
17#include <algorithm>
18
19#include "midas.h"
20#include "msystem.h"
21#include "odbxx.h"
22
23
24#ifdef HAVE_OPENCV
25#include <opencv2/opencv.hpp>
26#endif
27
28std::string history_dir() {
29 static std::string dir;
30
31 if (dir.empty()) {
32 midas::odb o = {
33 {"History dir", ""}
34 };
35 o.connect("/Logger/History/IMAGE");
36
37 if (o["History dir"] != std::string(""))
38 dir = o["History dir"];
39 else {
40 midas::odb l("/Logger");
41 if (l.is_subkey("History dir")) {
42 dir = l["History dir"];
43 if (dir == "")
44 dir = l["Data dir"];
45 } else
46 dir = l["Data dir"];
47 }
48
49 if (dir.empty())
50 dir = cm_get_path();
51
52 if (dir.back() != '/')
53 dir += "/";
54 }
55 return dir;
56}
57
58#ifdef HAVE_CURL
59#include <curl/curl.h>
60
61static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
62{
63 size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
64 return written;
65}
66
67static std::vector<std::thread> _image_threads;
68static bool stop_all_threads = false;
69
70int mkpath(std::string dir, mode_t mode)
71{
72 if (dir.back() == DIR_SEPARATOR)
73 dir.pop_back();
74
75 struct stat sb;
76 if (!stat(dir.c_str(), &sb))
77 return 0;
78
79 std::string p = dir;
80 if (p.find_last_of(DIR_SEPARATOR) != std::string::npos) {
81 p = p.substr(0, p.find_last_of(DIR_SEPARATOR));
82 mkpath(p.c_str(), mode);
83 }
84
85 return mkdir(dir.c_str(), mode);
86}
87
88
89void image_thread(std::string name) {
91 midas::odb o(("/History/Images/"+name).c_str());
92#ifdef HAVE_OPENCV
93 cv::VideoCapture* cap = new cv::VideoCapture();
94 //Track the number of times we have failed to connect to prevent error spam
95 int failed_connections = 0;
96 cv::Mat frame;
97#endif
98
99 do {
100 std::this_thread::sleep_for(std::chrono::milliseconds(20));
101
103 break;
104
105 // check for old files
106 if (ss_time() > last_check_delete + 60 && o["Storage hours"] > 0) {
107
108 std::string path = history_dir();
109 path += name;
110
112 //printf("scan [%s]\n", path.c_str());
113 ss_file_find(path.c_str(), "??????_??????.*", &flist);
114
115 for (unsigned i=0 ; i<flist.size() ; i++) {
116 std::string filename = flist[i];
117 struct tm ti{};
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);
120 ti.tm_year += 100;
121 ti.tm_mon -= 1;
122 ti.tm_isdst = -1;
123 time_t ft = ss_mktime(&ti);
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());
128 // suppress cases with ENOENT happening on systems with very many files
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));
131 }
132
133 }
134
136 }
137
138 if (!o["Enabled"])
139 continue;
140
141 if (ss_time() >= o["Last fetch"] + o["Period"]) {
142 o["Last fetch"] = ss_time();
143 std::string url = o["URL"];
144 bool is_rtsp = false;
145 // Check if the URL is rtsp protocol
146 if (url.compare(0,7,"rtsp://") == 0) {
147 is_rtsp = true;
148#ifdef HAVE_OPENCV
149 if (!cap->isOpened() ) {
150 cm_msg(MINFO,"image_history_rtsp","Opening camera %s",name.c_str());
151 if (!cap->open(url.c_str())) {
152 std::cout << "Unable to open video capture\n";
154 std::string error = "Cannot connect to camera \"" + name + "\" at " + url + ", please check camera power and URL";
155 cm_msg(MERROR,"image_history_rtsp","%s",error.c_str());
156 if (failed_connections > 10)
157 cm_msg(MERROR,"image_history_rtsp","More than 10 failed connections, I will stop reporting this error");
158 continue;
159 } else {
160 //Connection success! Reset failure counter
162 }
163 }
164
165#else // We have not built with OpenCV and tried to connect to a rtsp camera
166 std::string error = "Cannot connect to camera \"" + name + "\". mlogger not build with rtsp support (OpenCV)";
167 cm_msg(MERROR,"image_history_rtsp","%s",error.c_str());
168#endif
169 }
170 std::string filename = history_dir() + name;
171 std::string dotname = filename;
172 int status = mkpath(filename, 0755);
173 if (status)
174 cm_msg(MERROR, "image_thread", "Cannot create directory \"%s\": mkpath() errno %d (%s)", filename.c_str(), errno, strerror(errno));
175
176 ss_tzset(); // required by localtime_r()
177
178 time_t now = time(nullptr);
179 struct tm ltm;
180 localtime_r(&now, &ltm);
181 std::stringstream s;
182 s <<
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 <<
186 "_" <<
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();
192
193 if (o["Extension"] == std::string("")) {
194 filename += url.substr(url.find_last_of('.'));
195 dotname += url.substr(url.find_last_of('.'));
196 } else {
197 filename += o["Extension"];
198 dotname += o["Extension"];
199 }
200 if (is_rtsp)
201 {
202#ifdef HAVE_OPENCV
203 // If the system has OpenCV but not the full gstreamer install, or the system is missing video codecs, the mlogger can hang here.
204 bool OK = cap->grab();
205 if (OK == false) {
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());
208 cap->release();
209 delete cap;
210 cap = new cv::VideoCapture();
211 continue;
212 }
213
214 *cap >> frame;
215 if (frame.empty()) {
216 std::string error = "Recieved empty frame from camera \"" + name;
217 cm_msg(MERROR, "log_image_history", "%s", error.c_str());
218 cap->release();
219 delete cap;
220 cap = new cv::VideoCapture();
221 continue;
222 // End of video stream
223 }
224 cv::imwrite(filename, frame);
225#endif
226 } else {
227 CURL *conn = curl_easy_init();
228 curl_easy_setopt(conn, CURLOPT_URL, url.c_str());
231 auto f = fopen(dotname.c_str(), "wb");
232 if (f) {
235 int status = curl_easy_perform(conn);
236 fclose(f);
237 std::string error;
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);
242 } else {
243 long http_code = 0;
245 if (http_code != 200)
246 error = "Error fetching image from camera \"" + name + "\", http error status " +
247 std::to_string(http_code);
248 }
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());
252 o["Last error"] = ss_time();
253 }
254 remove(dotname.c_str());
255 }
256
257 // rename dotfile to filename to make it visible
258 rename(dotname.c_str(), filename.c_str());
259 }
260 curl_easy_cleanup(conn);
261 }
262 }
263
264 } while (!stop_all_threads);
265}
266
267void stop_image_history() {
268 stop_all_threads = true;
269 for (auto &t : _image_threads)
270 t.join();
272}
273
275 return _image_threads.size();
276}
277
278void start_image_history() {
280
281 // create default "Demo" image if ODB tree does not exist
282 if (!midas::odb::exists("/History/Images"))
283 midas::odb::create("/History/Images/Demo", TID_KEY);
284
285 static midas::odb h;
286 h.connect("/History/Images");
287
288 // loop over all cameras
289 for (auto &ic: h) {
290
291 // write default values if not present (ODB has precedence)
292 midas::odb c = {
293 {"Name", "Demo Camera"},
294 {"Enabled", false},
295 {"URL", "https://localhost:8000/image.jpg"},
296 {"Extension", ".jpg"},
297 {"Period", 60},
298 {"Last fetch", 0},
299 {"Storage hours", 0},
300 {"Error interval (s)", 60},
301 {"Last error", 0},
302 {"Timescale", "8h"}
303 };
304 c.connect(ic.get_odb().get_full_path());
305
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));
309 }
310}
311
312#else // HAVE_CURL
313
314// no history image logging wihtout CURL library
318
319#endif
320
321//std::chrono::time_point<std::chrono::high_resolution_clock> usStart()
322//{
323// return std::chrono::high_resolution_clock::now();
324//}
325//
326//unsigned int usSince(std::chrono::time_point<std::chrono::high_resolution_clock> start) {
327// auto elapsed = std::chrono::high_resolution_clock::now() - start;
328// return std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
329//}
330
331// retrieve image history
333 std::vector<time_t> &vtime, std::vector<std::string> &vfilename)
334{
335 // auto start = usStart();
336 std::string path = history_dir() + image_name;
337
338 std::string mask;
339 if (start_time == stop_time) {
340 ss_tzset(); // required by localtime_r()
341 struct tm ltm;
342 localtime_r(&start_time, &ltm);
343 std::stringstream s;
344 s <<
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 <<
348 "_" << "??????.*";
349 mask = s.str();
350 } else {
351 ss_tzset(); // required by localtime_r()
352 struct tm ltStart, ltStop;
353 localtime_r(&start_time, &ltStart);
355 std::stringstream sStart, sStop;
356 std::string mStart, mStop;
357 mask = "??????_??????.*";
358
359 sStart <<
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 <<
363 "_" <<
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();
368 sStop <<
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 <<
372 "_" <<
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;
376 mStop = sStop.str();
377 for (int i=0 ; i<13 ; i++) {
378 if (mStart[i] == mStop[i])
379 mask[i] = mStart[i];
380 else
381 break;
382 }
383 }
384
386
387 ss_file_find(path.c_str(), mask.c_str(), &vfn);
388
389 if (vfn.size() == 0)
390 ss_file_find(path.c_str(), "??????_??????.*", &vfn);
391
392 std::sort(vfn.begin(), vfn.end());
393
394 time_t minDiff = 1E7;
395 time_t minTime{};
396 int minIndex{};
397
398 for (unsigned i=0 ; i<vfn.size() ; i++) {
399 struct tm ti{};
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);
402 ti.tm_year += 100;
403 ti.tm_mon -= 1;
404 ti.tm_isdst = -1;
405 time_t ft = ss_mktime(&ti);
406 time_t now;
407 time(&now);
408
409 if (abs(ft - start_time) < minDiff) {
410 minDiff = abs(ft - start_time);
411 minTime = ft;
412 minIndex = i;
413 }
414
415 if (start_time != stop_time && ft >= start_time && ft <= stop_time) {
416 vtime.push_back(ft);
417 vfilename.push_back(vfn[i]);
418 }
419 }
420
421 // start == stop means return single image closest to them
422 if (start_time == stop_time && vfn.size() > 0) {
423 vtime.push_back(minTime);
424 vfilename.push_back(vfn[minIndex]);
425 }
426
427 //std::cout << "mask = " << mask << ", n = " << n << ", t = " << usSince(start)/1000.0 << " ms" << std::endl;
428
429 return HS_SUCCESS;
430}
static bool exists(const std::string &name)
Definition odbxx.cxx:66
static int create(const char *name, int type=TID_KEY)
Definition odbxx.cxx:130
bool is_subkey(std::string str)
Definition odbxx.cxx:458
void connect(const std::string &path, const std::string &name, bool write_defaults, bool delete_keys_not_in_defaults=false)
Definition odbxx.cxx:1291
std::string cm_get_path()
Definition midas.cxx:1537
#define HS_SUCCESS
Definition midas.h:727
unsigned int DWORD
Definition mcstd.h:51
#define TID_KEY
Definition midas.h:349
#define MINFO
Definition midas.h:560
#define MERROR
Definition midas.h:559
time_t ss_mktime(struct tm *tms)
Definition system.cxx:3365
void ss_tzset()
Definition system.cxx:3355
DWORD ss_time()
Definition system.cxx:3462
INT ss_file_find(const char *path, const char *pattern, char **plist)
Definition system.cxx:6713
INT cm_msg(INT message_type, const char *filename, INT line, const char *routine, const char *format,...)
Definition midas.cxx:915
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()
INT i
Definition mdump.cxx:32
DWORD stop_time
Definition mevb.cxx:55
volatile int stop_all_threads
static std::string join(const char *sep, const std::vector< std::string > &v)
Definition midas.cxx:389
#define DIR_SEPARATOR
Definition midas.h:193
std::vector< std::string > STRING_LIST
Definition midas.h:246
#define mask(slot)
Definition midas_macro.h:54
#define name(x)
Definition midas_macro.h:24
static std::string remove(const std::string s, char c)
Definition mjsonrpc.cxx:253
MUTEX_T * tm
Definition odbedit.cxx:39
DWORD status
Definition odbhist.cxx:39
TH1X EXPRT * h1_book(const char *name, const char *title, int bins, double min, double max)
Definition rmidas.h:24
char c
Definition system.cxx:1310