JeVois  1.0
JeVois Smart Embedded Machine Vision Toolkit
Engine.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 #include <jevois/Core/Engine.H>
19 
20 #include <jevois/Core/Camera.H>
21 #include <jevois/Core/MovieInput.H>
22 
23 #include <jevois/Core/Gadget.H>
27 
28 #include <jevois/Core/Serial.H>
30 
31 #include <jevois/Core/Module.H>
33 
34 #include <jevois/Debug/Log.H>
35 #include <jevois/Util/Utils.H>
36 #include <jevois/Debug/SysInfo.H>
37 
38 #include <cmath> // for fabs
39 #include <fstream>
40 #include <algorithm>
41 
42 // On the older platform kernel, detect class is not defined:
43 #ifndef V4L2_CTRL_CLASS_DETECT
44 #define V4L2_CTRL_CLASS_DETECT 0x00a30000
45 #endif
46 
47 namespace
48 {
49  // Assign a short name to every V4L2 control, for use by getcam and setcam commands
50  struct shortcontrol { int id; char const * const shortname; };
51 
52  // All V4L2 controls
53  // From this: grep V4L2_CID v4l2-controls.h | awk '{ print " { " $2 ", \"\" }," }'
54  // then fill-in the short names.
55  static shortcontrol camcontrols[] = {
56  // In V4L2_CID_BASE class:
57  { V4L2_CID_BRIGHTNESS, "brightness" },
58  { V4L2_CID_CONTRAST, "contrast" },
59  { V4L2_CID_SATURATION, "saturation" },
60  { V4L2_CID_HUE, "hue" },
61  { V4L2_CID_AUDIO_VOLUME, "audiovol" },
62  { V4L2_CID_AUDIO_BALANCE, "audiobal" },
63  { V4L2_CID_AUDIO_BASS, "audiobass" },
64  { V4L2_CID_AUDIO_TREBLE, "audiotreble" },
65  { V4L2_CID_AUDIO_MUTE, "audiomute" },
66  { V4L2_CID_AUDIO_LOUDNESS, "audioloudness" },
67  { V4L2_CID_BLACK_LEVEL, "blacklevel" },
68  { V4L2_CID_AUTO_WHITE_BALANCE, "autowb" },
69  { V4L2_CID_DO_WHITE_BALANCE, "dowb" },
70  { V4L2_CID_RED_BALANCE, "redbal" },
71  { V4L2_CID_BLUE_BALANCE, "bluebal" },
72  { V4L2_CID_GAMMA, "gamma" },
73  { V4L2_CID_WHITENESS, "whiteness" },
74  { V4L2_CID_EXPOSURE, "exposure" },
75  { V4L2_CID_AUTOGAIN, "autogain" },
76  { V4L2_CID_GAIN, "gain" },
77  { V4L2_CID_HFLIP, "hflip" },
78  { V4L2_CID_VFLIP, "vflip" },
79  { V4L2_CID_POWER_LINE_FREQUENCY, "powerfreq" },
80  { V4L2_CID_HUE_AUTO, "autohue" },
81  { V4L2_CID_WHITE_BALANCE_TEMPERATURE, "wbtemp" },
82  { V4L2_CID_SHARPNESS, "sharpness" },
83  { V4L2_CID_BACKLIGHT_COMPENSATION, "backlight" },
84  { V4L2_CID_CHROMA_AGC, "chromaagc" },
85  { V4L2_CID_COLOR_KILLER, "colorkiller" },
86  { V4L2_CID_COLORFX, "colorfx" },
87  { V4L2_CID_AUTOBRIGHTNESS, "autobrightness" },
88  { V4L2_CID_BAND_STOP_FILTER, "bandfilter" },
89  { V4L2_CID_ROTATE, "rotate" },
90  { V4L2_CID_BG_COLOR, "bgcolor" },
91  { V4L2_CID_CHROMA_GAIN, "chromagain" },
92  { V4L2_CID_ILLUMINATORS_1, "illum1" },
93  { V4L2_CID_ILLUMINATORS_2, "illum2" },
94  { V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, "mincapbuf" },
95  { V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, "minoutbuf" },
96  { V4L2_CID_ALPHA_COMPONENT, "alphacompo" },
97  // This one is not defined in our older platform kernel:
98  //{ V4L2_CID_COLORFX_CBCR, "colorfxcbcr" },
99 
100  // In V4L2_CID_CAMERA_CLASS_BASE class
101  { V4L2_CID_EXPOSURE_AUTO, "autoexp" },
102  { V4L2_CID_EXPOSURE_ABSOLUTE, "absexp" },
103  { V4L2_CID_EXPOSURE_AUTO_PRIORITY, "exppri" },
104  { V4L2_CID_PAN_RELATIVE, "panrel" },
105  { V4L2_CID_TILT_RELATIVE, "tiltrel" },
106  { V4L2_CID_PAN_RESET, "panreset" },
107  { V4L2_CID_TILT_RESET, "tiltreset" },
108  { V4L2_CID_PAN_ABSOLUTE, "panabs" },
109  { V4L2_CID_TILT_ABSOLUTE, "tiltabs" },
110  { V4L2_CID_FOCUS_ABSOLUTE, "focusabs" },
111  { V4L2_CID_FOCUS_RELATIVE, "focusrel" },
112  { V4L2_CID_FOCUS_AUTO, "focusauto" },
113  { V4L2_CID_ZOOM_ABSOLUTE, "zoomabs" },
114  { V4L2_CID_ZOOM_RELATIVE, "zoomrel" },
115  { V4L2_CID_ZOOM_CONTINUOUS, "zoomcontinuous" },
116  { V4L2_CID_PRIVACY, "privacy" },
117  { V4L2_CID_IRIS_ABSOLUTE, "irisabs" },
118  { V4L2_CID_IRIS_RELATIVE, "irisrel" },
119 
120  // definition for this one seems to be in the kernel but missing somehow here:
121 #ifndef V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
122 #define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE (V4L2_CID_CAMERA_CLASS_BASE+20)
123 #endif
124  { V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, "presetwb" },
125 
126  // Those are not defined in our older platform kernel:
127  //{ V4L2_CID_AUTO_EXPOSURE_BIAS, "expbias" },
128  //{ V4L2_CID_WIDE_DYNAMIC_RANGE, "wdr" },
129  //{ V4L2_CID_IMAGE_STABILIZATION, "stabilization" },
130  //{ V4L2_CID_ISO_SENSITIVITY, "isosens" },
131  //{ V4L2_CID_ISO_SENSITIVITY_AUTO, "isosensauto" },
132  //{ V4L2_CID_EXPOSURE_METERING, "expmetering" },
133  //{ V4L2_CID_SCENE_MODE, "scene" },
134  //{ V4L2_CID_3A_LOCK, "3alock" },
135  //{ V4L2_CID_AUTO_FOCUS_START, "autofocusstart" },
136  //{ V4L2_CID_AUTO_FOCUS_STOP, "autofocusstop" },
137  //{ V4L2_CID_AUTO_FOCUS_STATUS, "autofocusstatus" },
138  //{ V4L2_CID_AUTO_FOCUS_RANGE, "autofocusrange" },
139  //{ V4L2_CID_PAN_SPEED, "panspeed" },
140  //{ V4L2_CID_TILT_SPEED, "tiltspeed" },
141 
142  // In V4L2_CID_FLASH_CLASS_BASE:
143  { V4L2_CID_FLASH_LED_MODE, "flashled" },
144  { V4L2_CID_FLASH_STROBE_SOURCE, "flashstrobesrc" },
145  { V4L2_CID_FLASH_STROBE, "flashstrobe" },
146  { V4L2_CID_FLASH_STROBE_STOP, "flashstrobestop" },
147  { V4L2_CID_FLASH_STROBE_STATUS, "flashstrovestat" },
148  { V4L2_CID_FLASH_TIMEOUT, "flashtimeout" },
149  { V4L2_CID_FLASH_INTENSITY, "flashintens" },
150  { V4L2_CID_FLASH_TORCH_INTENSITY, "flashtorch" },
151  { V4L2_CID_FLASH_INDICATOR_INTENSITY, "flashindintens" },
152  { V4L2_CID_FLASH_FAULT, "flashfault" },
153  { V4L2_CID_FLASH_CHARGE, "flashcharge" },
154  { V4L2_CID_FLASH_READY, "flashready" },
155 
156  // In V4L2_CID_JPEG_CLASS_BASE:
157  { V4L2_CID_JPEG_CHROMA_SUBSAMPLING, "jpegchroma" },
158  { V4L2_CID_JPEG_RESTART_INTERVAL, "jpegrestartint" },
159  { V4L2_CID_JPEG_COMPRESSION_QUALITY, "jpegcompression" },
160  { V4L2_CID_JPEG_ACTIVE_MARKER, "jpegmarker" },
161 
162  // In V4L2_CID_IMAGE_SOURCE_CLASS_BASE:
163  // Those are not defined in our older platform kernel:
164  //{ V4L2_CID_VBLANK, "vblank" },
165  //{ V4L2_CID_HBLANK, "hblank" },
166  //{ V4L2_CID_ANALOGUE_GAIN, "again" },
167  //{ V4L2_CID_TEST_PATTERN_RED, "testpatred" },
168  //{ V4L2_CID_TEST_PATTERN_GREENR, "testpatgreenr" },
169  //{ V4L2_CID_TEST_PATTERN_BLUE, "testpatblue" },
170  //{ V4L2_CID_TEST_PATTERN_GREENB, "testpatbreenb" },
171 
172  // In V4L2_CID_IMAGE_PROC_CLASS_BASE:
173  //{ V4L2_CID_LINK_FREQ, "linkfreq" },
174  //{ V4L2_CID_PIXEL_RATE, "pixrate" },
175  //{ V4L2_CID_TEST_PATTERN, "testpat" },
176 
177  // In V4L2_CID_DETECT_CLASS_BASE:
178  //{ V4L2_CID_DETECT_MD_MODE, "detectmode" },
179  //{ V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, "detectthresh" },
180  //{ V4L2_CID_DETECT_MD_THRESHOLD_GRID, "detectthreshgrid" },
181  //{ V4L2_CID_DETECT_MD_REGION_GRID, "detectregiongrid" },
182  };
183 
184  // Convert a long name to a short name:
185  std::string abbreviate(std::string const & longname)
186  {
187  std::string name(longname);
188  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
189  name.erase(std::remove_if(name.begin(), name.end(), [](int c) { return !std::isalnum(c); }), name.end());
190  return name;
191  }
192 } // anonymous namespace
193 
194 
195 // ####################################################################################################
196 jevois::Engine::Engine(std::string const & instance) :
197  jevois::Manager(instance), itsMappings(jevois::loadVideoMappings(itsDefaultMappingIdx)),
198  itsUSBout(false), itsRunning(false), itsStreaming(false), itsStopMainLoop(false), itsTurbo(false),
199  itsManualStreamon(false)
200 {
201  JEVOIS_TRACE(1);
202 
203  LINFO("Loaded " << itsMappings.size() << " vision processing modes.");
204 }
205 
206 // ####################################################################################################
207 jevois::Engine::Engine(int argc, char const* argv[], std::string const & instance) :
208  jevois::Manager(argc, argv, instance), itsMappings(jevois::loadVideoMappings(itsDefaultMappingIdx)),
209  itsRunning(false), itsStreaming(false), itsStopMainLoop(false)
210 {
211  JEVOIS_TRACE(1);
212 
213  LINFO("Loaded " << itsMappings.size() << " vision processing modes.");
214 }
215 
216 // ####################################################################################################
217 void jevois::Engine::onParamChange(jevois::engine::serialdev const & JEVOIS_UNUSED_PARAM(param),
218  std::string const & newval)
219 {
220  JEVOIS_TIMED_LOCK(itsMtx);
221 
222  // If we have a serial already, nuke it:
223  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
224  if ((*itr)->instanceName() == "serial") itr = itsSerials.erase(itr);
225  removeComponent("serial", false);
226 
227  // Open the usb hardware (4-pin connector) serial port, if any:
228  if (newval.empty() == false)
229  try
230  {
231  std::shared_ptr<jevois::UserInterface> s;
232  if (newval == "stdio")
233  s = addComponent<jevois::StdioInterface>("serial");
234  else
235  {
236  s = addComponent<jevois::Serial>("serial", jevois::UserInterface::Type::Hard);
237  s->setParamVal("devname", newval);
238  }
239 
240  itsSerials.push_back(s);
241  LINFO("Using [" << newval << "] hardware (4-pin connector) serial port");
242  }
243  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start hardware (4-pin connector) serial port"); }
244  else LINFO("No hardware (4-pin connector) serial port used");
245 }
246 
247 // ####################################################################################################
248 void jevois::Engine::onParamChange(jevois::engine::usbserialdev const & JEVOIS_UNUSED_PARAM(param),
249  std::string const & newval)
250 {
251  JEVOIS_TIMED_LOCK(itsMtx);
252 
253  // If we have a usbserial already, nuke it:
254  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
255  if ((*itr)->instanceName() == "usbserial") itr = itsSerials.erase(itr);
256  removeComponent("usbserial", false);
257 
258  // Open the USB serial port, if any:
259  if (newval.empty() == false)
260  try
261  {
262  std::shared_ptr<jevois::UserInterface> s =
263  addComponent<jevois::Serial>("usbserial", jevois::UserInterface::Type::USB);
264  s->setParamVal("devname", newval);
265  itsSerials.push_back(s);
266  LINFO("Using [" << newval << "] USB serial port");
267  }
268  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start USB serial port"); }
269  else LINFO("No USB serial port used");
270 }
271 
272 // ####################################################################################################
273 void jevois::Engine::onParamChange(jevois::engine::cpumode const & JEVOIS_UNUSED_PARAM(param),
274  jevois::engine::CPUmode const & newval)
275 {
276  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");
277  if (ofs.is_open() == false) { LERROR("Cannot set cpu frequency governor mode -- IGNORED"); return; }
278 
279  switch (newval)
280  {
281  case engine::CPUmode::PowerSave: ofs << "powersave" << std::endl; break;
282  case engine::CPUmode::Conservative: ofs << "conservative" << std::endl; break;
283  case engine::CPUmode::OnDemand: ofs << "ondemand" << std::endl; break;
284  case engine::CPUmode::Interactive: ofs << "interactive" << std::endl; break;
285  case engine::CPUmode::Performance: ofs << "performance" << std::endl; break;
286  }
287 }
288 
289 // ####################################################################################################
290 void jevois::Engine::onParamChange(jevois::engine::cpumax const & JEVOIS_UNUSED_PARAM(param),
291  unsigned int const & newval)
292 {
293  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
294  if (ofs.is_open() == false) { LERROR("Cannot set cpu max frequency -- IGNORED"); return; }
295 
296  ofs << newval * 1000U << std::endl;
297 }
298 
299 // ####################################################################################################
301 {
302  // Set any initial parameters from global config file:
303  std::string const paramcfg = std::string(JEVOIS_CONFIG_PATH) + '/' + JEVOIS_MODULE_PARAMS_FILENAME;
304  std::ifstream ifs(paramcfg); if (ifs.is_open()) setParamsFromStream(ifs, paramcfg);
305 
306  // Run the Manager version. This parses the command line:
308 }
309 
310 // ####################################################################################################
312 {
313  // First make sure the manager gets to run this:
315 
316  // Freeze the serial port device names, their params, and camera and gadget too:
317  serialdev::freeze();
318  usbserialdev::freeze();
319  for (auto & s : itsSerials) s->freezeAllParams();
320  cameradev::freeze();
321  cameranbuf::freeze();
322  camturbo::freeze();
323  gadgetdev::freeze();
324  gadgetnbuf::freeze();
325  itsTurbo = camturbo::get();
326 
327  // Grab the log messages, itsSerials is not going to change anymore now that the serial params are frozen:
328  jevois::logSetEngine(this);
329 
330  // Instantiate a camera: If device names starts with "/dev/v", assume a hardware camera, otherwise a movie file:
331  std::string const camdev = cameradev::get();
332  if (jevois::stringStartsWith(camdev, "/dev/v"))
333  {
334  LINFO("Starting camera device " << camdev);
335 
336  // Set turbo mode or not:
337  std::ofstream ofs("/sys/module/vfe_v4l2/parameters/turbo");
338  if (ofs.is_open())
339  {
340  if (itsTurbo) ofs << "1" << std::endl; else ofs << "0" << std::endl;
341  ofs.close();
342  }
343  else LERROR("Could not access VFE turbo parameter -- IGNORED");
344 
345  // Now instantiate the camera:
346  itsCamera.reset(new jevois::Camera(camdev, cameranbuf::get()));
347 
348 #ifndef JEVOIS_PLATFORM
349  // No need to confuse people with a non-working camreg param:
350  camreg::set(false);
351  camreg::freeze();
352 #endif
353  }
354  else
355  {
356  LINFO("Using movie input " << camdev << " -- issue a 'streamon' to start processing.");
357  itsCamera.reset(new jevois::MovieInput(camdev, cameranbuf::get()));
358 
359  // No need to confuse people with a non-working camreg param:
360  camreg::set(false);
361  camreg::freeze();
362  }
363 
364  // Instantiate a USB gadget: Note: it will want to access the mappings. If the user-selected video mapping has no usb
365  // out, do not instantiate a gadget:
366  int midx = videomapping::get();
367 
368  // The videomapping parameter is now disabled, users should use the 'setmapping' command once running:
369  videomapping::freeze();
370 
371  if (midx >= int(itsMappings.size()))
372  { LERROR("Mapping index " << midx << " out of range -- USING DEFAULT"); midx = -1; }
373 
374  if (midx < 0) midx = itsDefaultMappingIdx;
375 
376  // Always instantiate a gadget even if not used right now, may be used later:
377  std::string const gd = gadgetdev::get();
378  if (gd == "None")
379  {
380  LINFO("Using no USB video output.");
381  // No USB output and no display, useful for benchmarking only:
382  itsGadget.reset(new jevois::VideoOutputNone());
383  itsManualStreamon = true;
384  }
385  else if (jevois::stringStartsWith(gd, "/dev/"))
386  {
387  LINFO("Loading USB video driver " << gd);
388  // USB gadget driver:
389  itsGadget.reset(new jevois::Gadget(gd, itsCamera.get(), this, gadgetnbuf::get()));
390  }
391  else if (gd.empty() == false)
392  {
393  LINFO("Saving output video to file " << gd);
394  // Non-empty filename, save to file:
395  itsGadget.reset(new jevois::MovieOutput(gd));
396  itsManualStreamon = true;
397  }
398  else
399  {
400  LINFO("Using display for video output");
401  // Local video display, for use on a host desktop:
402  itsGadget.reset(new jevois::VideoDisplay("jevois", gadgetnbuf::get()));
403  itsManualStreamon = true;
404  }
405 
406  // We are ready to run:
407  itsRunning.store(true);
408 
409  // Set initial format:
410  setFormat(midx);
411 
412  // Run init script:
413  JEVOIS_TIMED_LOCK(itsMtx);
414  runScriptFromFile(JEVOIS_ENGINE_INIT_SCRIPT, nullptr, false);
415 }
416 
417 // ####################################################################################################
419 {
420  JEVOIS_TRACE(1);
421 
422  // Turn off stream if it is on:
423  streamOff();
424 
425  // Tell our run() thread to finish up:
426  itsRunning.store(false);
427 
428  // Nuke our module as soon as we can, hopefully soon now that we turned off streaming and running:
429  {
430  JEVOIS_TIMED_LOCK(itsMtx);
431  removeComponent(itsModule);
432  itsModule.reset();
433 
434  // Gone, nuke the loader now:
435  itsLoader.reset();
436  }
437 
438  // Because we passed the camera as a raw pointer to the gadget, nuke the gadget first and then the camera:
439  itsGadget.reset();
440  itsCamera.reset();
441 
442  // Things should be quiet now, unhook from the logger (this call is not strictly thread safe):
443  jevois::logSetEngine(nullptr);
444 }
445 
446 // ####################################################################################################
448 {
449  JEVOIS_TRACE(2);
450 
451  JEVOIS_TIMED_LOCK(itsMtx);
452  itsCamera->streamOn();
453  itsGadget->streamOn();
454  itsStreaming.store(true);
455 }
456 
457 // ####################################################################################################
459 {
460  JEVOIS_TRACE(2);
461 
462  // First, tell both ethe camera and gadget to abort streaming, this will make get()/done()/send() to throw:
463  itsGadget->abortStream();
464  itsCamera->abortStream();
465 
466  // Stop the main loop, which will flit itsStreaming to false and will make it easier for us to lock itsMtx:
467  LDEBUG("Stopping main loop...");
468  itsStopMainLoop.store(true);
469  while (itsStopMainLoop.load() && itsRunning.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
470  LDEBUG("Main loop stopped.");
471 
472  // Lock up and stream off:
473  JEVOIS_TIMED_LOCK(itsMtx);
474  itsGadget->streamOff();
475  itsCamera->streamOff();
476 }
477 
478 // ####################################################################################################
480 {
481  JEVOIS_TRACE(2);
482 
483  LDEBUG("Set format number " << idx << " start...");
484 
485  if (idx >= itsMappings.size())
486  LFATAL("Requested mapping index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
487 
488  JEVOIS_TIMED_LOCK(itsMtx);
489  setFormatInternal(idx);
490  LDEBUG("Set format number " << idx << " done");
491 }
492 
493 // ####################################################################################################
494 void jevois::Engine::setFormatInternal(size_t idx)
495 {
496  // itsMtx should be locked by caller, idx should be valid:
497  JEVOIS_TRACE(2);
498 
499  jevois::VideoMapping const & m = itsMappings[idx];
500  setFormatInternal(m);
501 }
502 
503 // ####################################################################################################
504 void jevois::Engine::setFormatInternal(jevois::VideoMapping const & m)
505 {
506  // itsMtx should be locked by caller, idx should be valid:
507  JEVOIS_TRACE(2);
508 
509  LINFO(m.str());
510 
511  // Now that the module is nuked, we won't have any get()/done()/send() requests on the camera or gadget, thus it is
512  // safe to change the formats on both:
513  itsCamera->setFormat(m);
514  if (m.ofmt == 0) itsUSBout = false; else { itsGadget->setFormat(m); itsUSBout = true;}
515 
516  // Nuke the processing module, if any, so we can also safely nuke the loader. We always nuke the module instance so we
517  // won't have any issues with latent state even if we re-use the same module but possibly with different input
518  // image resolution, etc:
519  if (itsModule) { removeComponent(itsModule); itsModule.reset(); }
520 
521  // We can however re-use the same loader and avoid closing the .so if we will use the same module:
522  std::string const sopath = m.sopath();
523  if (itsLoader.get() == nullptr || itsLoader->sopath() != sopath)
524  {
525  // Nuke our previous loader and free its resources if needed, then start a new loader:
526  LINFO("Instantiating dynamic loader for " << sopath);
527  itsLoader.reset(new jevois::DynamicLoader(sopath, true));
528  }
529 
530  // Check version match:
531  auto version_major = itsLoader->load<int()>(m.modulename + "_version_major");
532  auto version_minor = itsLoader->load<int()>(m.modulename + "_version_minor");
533  if (version_major() != JEVOIS_VERSION_MAJOR || version_minor() != JEVOIS_VERSION_MINOR)
534  LERROR("Module " << m.modulename << " in file " << sopath << " was build for JeVois v" << version_major() << '.'
535  << version_minor() << ", but running framework is v" << JEVOIS_VERSION_STRING << " -- TRYING ANYWAY");
536 
537  // Instantiate the new module:
538  auto create = itsLoader->load<std::shared_ptr<jevois::Module>(std::string const &)>(m.modulename + "_create");
539  itsModule = create(m.modulename); // Here we just use the class name as instance name
540 
541  // Add it as a component to us. Keep this code in sync with Manager::addComponent():
542  {
543  // Lock up so we guarantee the instance name does not get robbed as we add the sub:
544  boost::unique_lock<boost::shared_mutex> ulck(itsSubMtx);
545 
546  // Then add it as a sub-component to us, if there is not instance name clash with our other sub-components:
547  itsSubComponents.push_back(itsModule);
548  itsModule->itsParent = this;
549  itsModule->setPath(sopath.substr(0, sopath.rfind('/')));
550  }
551 
552  // Bring it to our runstate and load any extra params. NOTE: Keep this in sync with Component::init():
553  if (itsInitialized) itsModule->runPreInit();
554 
555  std::string const paramcfg = itsModule->absolutePath(JEVOIS_MODULE_PARAMS_FILENAME);
556  std::ifstream ifs(paramcfg); if (ifs.is_open()) itsModule->setParamsFromStream(ifs, paramcfg);
557 
558  if (itsInitialized) { itsModule->setInitialized(); itsModule->runPostInit(); }
559 
560  // And finally run any config script:
561  runScriptFromFile(itsModule->absolutePath(JEVOIS_MODULE_SCRIPT_FILENAME), nullptr, false);
562 
563  LINFO("Module [" << m.modulename << "] loaded, initialized, and ready.");
564 }
565 
566 // ####################################################################################################
568 {
569  JEVOIS_TRACE(2);
570 
571  // Announce that we are ready to the hardware serial port, if any. Do not use sendSerial() here so we always issue
572  // this message irrespectively of the user serial preferences:
573  for (auto & s : itsSerials)
574  if (s->instanceName() == "serial")
575  try { s->writeString("INF READY JEVOIS " JEVOIS_VERSION_STRING); }
576  catch (...) { jevois::warnAndIgnoreException(); }
577 
578  while (itsRunning.load())
579  {
580  bool dosleep = true;
581 
582  if (itsStreaming.load())
583  {
584  JEVOIS_TIMED_LOCK(itsMtx);
585 
586  if (itsModule)
587  try
588  {
589  if (itsUSBout) itsModule->process(jevois::InputFrame(itsCamera, itsTurbo), jevois::OutputFrame(itsGadget));
590  else itsModule->process(jevois::InputFrame(itsCamera, itsTurbo));
591  dosleep = false;
592  }
593  catch (...) { jevois::warnAndIgnoreException(); }
594  }
595 
596  if (itsStopMainLoop.load())
597  {
598  itsStreaming.store(false);
599  LDEBUG("-- Main loop stopped --");
600  itsStopMainLoop.store(false);
601  }
602 
603  if (dosleep)
604  {
605  LDEBUG("No processing module loaded or not streaming... Sleeping...");
606  std::this_thread::sleep_for(std::chrono::milliseconds(50));
607  }
608 
609  // Serial input handling. Note that readSome() and writeString() on the serial could throw. The code below is
610  // organized to catch all other exceptions, except for those, which are caught here at the first try level:
611  for (auto & s : itsSerials)
612  {
613  try
614  {
615  std::string str; bool parsed = false; bool success = false;
616 
617  if (s->readSome(str))
618  {
619  JEVOIS_TIMED_LOCK(itsMtx);
620 
621  // Try to execute this command. If the command is for us (e.g., set a parameter) and is correct,
622  // parseCommand() will return true; if it is for us but buggy, it will throw. If it is not recognized by us,
623  // it will return false and we should try sending it to the Module:
624  try { parsed = parseCommand(str, s); success = parsed; }
625  catch (std::exception const & e) { s->writeString(std::string("ERR ") + e.what()); parsed = true; }
626  catch (...) { s->writeString("ERR Unknown error"); parsed = true; }
627 
628  if (parsed == false)
629  {
630  if (itsModule)
631  {
632  try { itsModule->parseSerial(str, s); success = true; }
633  catch (std::exception const & me) { s->writeString(std::string("ERR ") + me.what()); }
634  catch (...) { s->writeString("ERR Command not recognized by Engine or Module"); }
635  }
636  else s->writeString("ERR Unsupported command and no module");
637  }
638 
639  // If success, let user know:
640  if (success) s->writeString("OK");
641  }
642  }
643  catch (...) { jevois::warnAndIgnoreException(); }
644  }
645  }
646 }
647 
648 // ####################################################################################################
649 void jevois::Engine::sendSerial(std::string const & str, bool islog)
650 {
651  // Decide where to send this message based on the value of islog:
652  jevois::engine::SerPort p = islog ? serlog::get() : serout::get();
653 
654  switch (p)
655  {
656  case jevois::engine::SerPort::None:
657  break; // Nothing to send
658 
659  case jevois::engine::SerPort::All:
660  for (auto & s : itsSerials)
661  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
662  break;
663 
664  case jevois::engine::SerPort::Hard:
665  for (auto & s : itsSerials)
666  if (s->type() == jevois::UserInterface::Type::Hard)
667  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
668  break;
669 
670  case jevois::engine::SerPort::USB:
671  for (auto & s : itsSerials)
672  if (s->type() == jevois::UserInterface::Type::USB)
673  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
674  break;
675  }
676 }
677 
678 // ####################################################################################################
680 {
681  return itsMappings.size();
682 }
683 
684 // ####################################################################################################
686 {
687  if (idx >= itsMappings.size())
688  LFATAL("Index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
689 
690  return itsMappings[idx];
691 }
692 
693 // ####################################################################################################
694 size_t jevois::Engine::getVideoMappingIdx(unsigned int iformat, unsigned int iframe, unsigned int interval) const
695 {
696  // If the iformat or iframe is zero, that's probably a probe for the default mode, so return it:
697  if (iformat == 0 || iframe == 0) return itsDefaultMappingIdx;
698 
699  // If interval is zero, probably a driver trying to probe for our default interval, so return the first available one;
700  // otherwise try to find the desired interval and return the corresponding mapping:
701  if (interval)
702  {
703  float const fps = jevois::VideoMapping::uvcToFps(interval);
704  size_t idx = 0;
705 
706  for (jevois::VideoMapping const & m : itsMappings)
707  if (m.uvcformat == iformat && m.uvcframe == iframe && std::fabs(m.ofps - fps) < 0.1F) return idx;
708  else ++idx;
709 
710  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
711  }
712  else
713  {
714  size_t idx = 0;
715 
716  for (jevois::VideoMapping const & m : itsMappings)
717  if (m.uvcformat == iformat && m.uvcframe == iframe) return idx;
718  else ++idx;
719 
720  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
721  }
722 }
723 
724 // ####################################################################################################
726 { return itsMappings[itsDefaultMappingIdx]; }
727 
728 // ####################################################################################################
730 { return itsDefaultMappingIdx; }
731 
732 // ####################################################################################################
733 jevois::VideoMapping const &
734 jevois::Engine::findVideoMapping(unsigned int oformat, unsigned int owidth, unsigned int oheight,
735  float oframespersec) const
736 {
737  for (jevois::VideoMapping const & m : itsMappings)
738  if (m.match(oformat, owidth, oheight, oframespersec)) return m;
739 
740  LFATAL("Could not find mapping for output format " << jevois::fccstr(oformat) << ' ' <<
741  owidth << 'x' << oheight << " @ " << oframespersec << " fps");
742 }
743 
744 // ####################################################################################################
745 std::string jevois::Engine::camctrlname(int id, char const * longname) const
746 {
747  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
748  if (camcontrols[i].id == id) return camcontrols[i].shortname;
749 
750  // Darn, this control is not in our list, probably something exotic. Compute a name from the control's long name:
751  return abbreviate(longname);
752 }
753 
754 // ####################################################################################################
755 int jevois::Engine::camctrlid(std::string const & shortname)
756 {
757  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
758  if (shortname.compare(camcontrols[i].shortname) == 0) return camcontrols[i].id;
759 
760  // Not in our list, all right, let's find it then in the camera:
761  struct v4l2_queryctrl qc = { };
762  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
763  {
764  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
765  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
766  // doneids set to keep track of the ones already reported:
767  qc.id = cls | 0x900;
768  while (true)
769  {
770  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; unsigned int old_id = qc.id; bool failed = false;
771  try
772  {
773  itsCamera->queryControl(qc);
774  if (abbreviate(reinterpret_cast<char const *>(qc.name)) == shortname) return qc.id;
775  }
776  catch (...) { failed = true; }
777 
778  // With V4L2_CTRL_FLAG_NEXT_CTRL, the camera kernel driver is supposed to pass down the next valid control if
779  // the requested one is not found, but some drivers do not honor that, so let's move on to the next control
780  // manually if needed:
781  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
782  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
783  else if (failed) break;
784  }
785  }
786 
787  LFATAL("Could not find control [" << shortname << "] in the camera");
788 }
789 
790 // ####################################################################################################
791 std::string jevois::Engine::camCtrlHelp(struct v4l2_queryctrl & qc, std::set<int> & doneids)
792 {
793  // See if we have this control:
794  itsCamera->queryControl(qc);
795  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
796 
797  // If we have already done this control, just return an empty string:
798  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
799 
800  // Control exists, let's also get its current value:
801  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
802  itsCamera->getControl(ctrl);
803 
804  // Print out some description depending on control type:
805  std::ostringstream ss;
806  ss << "- " << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
807 
808  switch (qc.type)
809  {
810  case V4L2_CTRL_TYPE_INTEGER:
811  ss << " [int] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
812  << " def=" << qc.default_value << " curr=" << ctrl.value;
813  break;
814 
815  //case V4L2_CTRL_TYPE_INTEGER64:
816  //ss << " [int64] value=" << ctrl.value64;
817  //break;
818 
819  //case V4L2_CTRL_TYPE_STRING:
820  //ss << " [str] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
821  // << " curr=" << ctrl.string;
822  //break;
823 
824  case V4L2_CTRL_TYPE_BOOLEAN:
825  ss << " [bool] default=" << qc.default_value << " curr=" << ctrl.value;
826  break;
827 
828  // This one is not supported by the older kernel on platform:
829  //case V4L2_CTRL_TYPE_INTEGER_MENU:
830  //ss << " [intmenu] min=" << qc.minimum << " max=" << qc.maximum
831  // << " def=" << qc.default_value << " curr=" << ctrl.value;
832  //break;
833 
834  case V4L2_CTRL_TYPE_BUTTON:
835  ss << " [button]";
836  break;
837 
838  case V4L2_CTRL_TYPE_BITMASK:
839  ss << " [bitmask] max=" << qc.maximum << " def=" << qc.default_value << " curr=" << ctrl.value;
840  break;
841 
842  case V4L2_CTRL_TYPE_MENU:
843  {
844  struct v4l2_querymenu querymenu = { };
845  querymenu.id = qc.id;
846  ss << " [menu] values ";
847  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
848  {
849  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
850  ss << querymenu.index << ':' << querymenu.name << ' ';
851  }
852  ss << "curr=" << ctrl.value;
853  }
854  break;
855 
856  default:
857  ss << "[unknown type]";
858  }
859 
860  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " [DISABLED]";
861 
862  return ss.str();
863 }
864 
865 // ####################################################################################################
866 bool jevois::Engine::parseCommand(std::string const & str, std::shared_ptr<UserInterface> s)
867 {
868  // itsMtx should be locked by caller
869 
870  std::string errmsg;
871 
872  // Note: ModemManager on Ubuntu sends this on startup, kill ModemManager to avoid:
873  // 41 54 5e 53 51 50 4f 52 54 3f 0d 41 54 0d 41 54 0d 41 54 0d 7e 00 78 f0 7e 7e 00 78 f0 7e
874  //
875  // AT^SQPORT?
876  // AT
877  // AT
878  // AT
879  // ~
880  //
881  // then later on it insists on trying to mess with us, issuing things like AT, AT+CGMI, AT+GMI, AT+CGMM, AT+GMM,
882  // AT%IPSYS?, ATE0, ATV1, etc etc
883 
884  switch (str.length())
885  {
886  case 0:
887  LDEBUG("Ignoring empty string"); return true;
888  break;
889 
890  case 1:
891  if (str[0] == '~') { LDEBUG("Ignoring modem config command [~]"); return true; }
892 
893  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
894  // from the arduino to be printed out to the user:
895  if (str[0] == '#') { sendSerial(str, true); return true; }
896  break;
897 
898  default: // length is 2 or more:
899 
900  // Ignore any command that starts with a '~':
901  if (str[0] == '~') { LDEBUG("Ignoring modem config command [" << str << ']'); return true; }
902 
903  // Ignore any command that starts with "AT":
904  if (str[0] == 'A' && str[1] == 'T') { LDEBUG("Ignoring AT command [" << str <<']'); return true; }
905 
906  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
907  // in the arduino to be printed out to the user:
908  if (str[0] == '#') { sendSerial(str, true); return true; }
909 
910  // Get the first word, i.e., the command:
911  size_t const idx = str.find(' '); std::string cmd, rem;
912  if (idx == str.npos) cmd = str; else { cmd = str.substr(0, idx); if (idx < str.length()) rem = str.substr(idx+1); }
913 
914  if (cmd == "help")
915  {
916  // Show all commands, first ours, as supported below:
917  s->writeString("GENERAL COMMANDS:");
918  s->writeString("");
919  s->writeString("help - print this help message");
920  s->writeString("info - show system information including CPU speed, load and temperature");
921  s->writeString("setpar <name> <value> - set a parameter value");
922  s->writeString("getpar <name> - get a parameter value(s)");
923  s->writeString("runscript <filename> - run script commands in specified file");
924  s->writeString("setcam <ctrl> <val> - set camera control <ctrl> to value <val>");
925  s->writeString("getcam <ctrl> - get value of camera control <ctrl>");
926  if (camreg::get())
927  {
928  s->writeString("setcamreg <reg> <val> - set raw camera register <reg> to value <val>");
929  s->writeString("getcamreg <reg> - get value of raw camera register <reg>");
930  }
931  s->writeString("listmappings - list all available video mappings");
932  s->writeString("setmapping <num> - select video mapping <num>, only possible while not streaming");
933  s->writeString("setmapping2 <CAMmode> <CAMwidth> <CAMheight> <CAMfps> <Vendor> <Module> - set no-USB-out "
934  "video mapping defined on the fly, while not streaming");
935  if (itsUSBout == false || itsManualStreamon)
936  {
937  s->writeString("streamon - start camera video streaming");
938  s->writeString("streamoff - stop camera video streaming");
939  }
940  s->writeString("ping - returns 'ALIVE'");
941  s->writeString("serlog <string> - forward string to the serial port(s) specified by the serlog parameter");
942  s->writeString("serout <string> - forward string to the serial port(s) specified by the serout parameter");
943 
944 #ifndef JEVOIS_PLATFORM
945  s->writeString("quit - quit this program");
946 #endif
947  s->writeString("");
948 
949  // Then the module's custom commands, if any:
950  if (itsModule)
951  {
952  std::stringstream css; itsModule->supportedCommands(css);
953  s->writeString("MODULE-SPECIFIC COMMANDS:");
954  s->writeString("");
955  for (std::string line; std::getline(css, line); /* */) s->writeString(line);
956  s->writeString("");
957  }
958 
959  // Get the help message for our parameters and write it out line by line so the serial fixes the line endings:
960  std::stringstream pss; constructHelpMessage(pss);
961  for (std::string line; std::getline(pss, line); /* */) s->writeString(line);
962 
963  // Show all camera controls
964  s->writeString("AVAILABLE CAMERA CONTROLS:");
965  s->writeString("");
966 
967  struct v4l2_queryctrl qc = { }; std::set<int> doneids;
968  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
969  {
970  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
971  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
972  // doneids set to keep track of the ones already reported:
973  qc.id = cls | 0x900; unsigned int old_id;
974  while (true)
975  {
976  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; old_id = qc.id; bool failed = false;
977  try { std::string hlp = camCtrlHelp(qc, doneids); if (hlp.empty() == false) s->writeString(hlp); }
978  catch (...) { failed = true; }
979 
980  // The camera kernel driver is supposed to pass down the next valid control if the requested one is not
981  // found, but some drivers do not honor that, so let's move on to the next control manually if needed:
982  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
983  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
984  else if (failed) break;
985  }
986  }
987 
988  return true;
989  }
990 
991  // ----------------------------------------------------------------------------------------------------
992  if (cmd == "info")
993  {
994  s->writeString("INFO: JeVois " JEVOIS_VERSION_STRING);
995  s->writeString("INFO: " + jevois::getSysInfoVersion());
996  s->writeString("INFO: " + jevois::getSysInfoCPU());
997  s->writeString("INFO: " + jevois::getSysInfoMem());
998  return true;
999  }
1000 
1001  // ----------------------------------------------------------------------------------------------------
1002  if (cmd == "setpar")
1003  {
1004  std::istringstream ss(rem); std::string desc, val; ss >> desc; ss >> val;
1005  setParamString(desc, val);
1006  return true;
1007  }
1008 
1009  // ----------------------------------------------------------------------------------------------------
1010  if (cmd == "getpar")
1011  {
1012  auto vec = getParamString(rem);
1013  for (auto const & p : vec) s->writeString(p.first + ' ' + p.second);
1014  return true;
1015  }
1016 
1017  // ----------------------------------------------------------------------------------------------------
1018  if (cmd == "setcam")
1019  {
1020  std::istringstream ss(rem); std::string ctrl; int val; ss >> ctrl >> val;
1021  struct v4l2_control c = { }; c.id = camctrlid(ctrl); c.value = val;
1022  itsCamera->setControl(c);
1023  return true;
1024  }
1025 
1026  // ----------------------------------------------------------------------------------------------------
1027  if (cmd == "getcam")
1028  {
1029  struct v4l2_control c = { }; c.id = camctrlid(rem);
1030  itsCamera->getControl(c);
1031  s->writeString(rem + ' ' + std::to_string(c.value));
1032  return true;
1033  }
1034 
1035  // ----------------------------------------------------------------------------------------------------
1036  if (cmd == "setcamreg")
1037  {
1038  if (camreg::get())
1039  {
1040  // Read register and value as strings, then std::stoi to convert to int, supports 0x (and 0 for octal, caution)
1041  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
1042  itsCamera->writeRegister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
1043  return true;
1044  }
1045  errmsg = "Access to camera registers is disabled, enable with --camreg=true";
1046  }
1047 
1048  // ----------------------------------------------------------------------------------------------------
1049  if (cmd == "getcamreg")
1050  {
1051  if (camreg::get())
1052  {
1053  unsigned int val = itsCamera->readRegister(std::stoi(rem, nullptr, 0));
1054  std::ostringstream os; os << std::hex << val;
1055  s->writeString(os.str());
1056  return true;
1057  }
1058  errmsg = "Access to camera registers is disabled, enable with --camreg=true";
1059  }
1060 
1061  // ----------------------------------------------------------------------------------------------------
1062  if (cmd == "listmappings")
1063  {
1064  s->writeString("AVAILABLE VIDEO MAPPINGS:");
1065  s->writeString("");
1066  for (size_t idx = 0; idx < itsMappings.size(); ++idx)
1067  {
1068  std::string idxstr = std::to_string(idx);
1069  if (idxstr.length() < 5) idxstr = std::string(5 - idxstr.length(), ' ') + idxstr; // pad to 5-char long
1070  s->writeString(idxstr + " - " + itsMappings[idx].str());
1071  }
1072  return true;
1073  }
1074 
1075  // ----------------------------------------------------------------------------------------------------
1076  if (cmd == "setmapping")
1077  {
1078  size_t const idx = std::stoi(rem);
1079  bool was_streaming = itsStreaming.load();
1080 
1081  if (was_streaming)
1082  {
1083  errmsg = "Cannot set mapping while streaming: ";
1084  if (itsUSBout) errmsg += "Stop your webcam program on the host computer first.";
1085  else errmsg += "Issue a 'streamoff' command first.";
1086  }
1087  else if (idx >= itsMappings.size())
1088  errmsg = "Requested mapping index " + std::to_string(idx) + " out of range [0 .. " +
1089  std::to_string(itsMappings.size()-1) + ']';
1090  else
1091  {
1092  setFormatInternal(idx);
1093  return true;
1094  }
1095  }
1096 
1097  // ----------------------------------------------------------------------------------------------------
1098  if (cmd == "setmapping2")
1099  {
1100  bool was_streaming = itsStreaming.load();
1101 
1102  if (was_streaming)
1103  {
1104  errmsg = "Cannot set mapping while streaming: ";
1105  if (itsUSBout) errmsg += "Stop your webcam program on the host computer first.";
1106  else errmsg += "Issue a 'streamoff' command first.";
1107  }
1108  else
1109  {
1111  try
1112  {
1113  std::istringstream full("NONE 0 0 0.0 " + rem); full >> m;
1114  setFormatInternal(m);
1115  return true;
1116  }
1117  catch (std::exception const & e) { errmsg = "Error parsing or setting mapping [" + rem + "]: " + e.what(); }
1118  catch (...) { errmsg = "Error parsing or setting mapping [" + rem + ']'; }
1119  }
1120  }
1121 
1122  // ----------------------------------------------------------------------------------------------------
1123  if (itsUSBout == false || itsManualStreamon)
1124  {
1125  if (cmd == "streamon")
1126  {
1127  // keep this in sync with streamOn(), modulo the fact that here we are already locked:
1128  itsCamera->streamOn();
1129  itsGadget->streamOn();
1130  itsStreaming.store(true);
1131  return true;
1132  }
1133 
1134  if (cmd == "streamoff")
1135  {
1136  // keep this in sync with streamOff(), modulo the fact that here we are already locked:
1137  itsGadget->abortStream();
1138  itsCamera->abortStream();
1139 
1140  itsStreaming.store(false);
1141 
1142  itsGadget->streamOff();
1143  itsCamera->streamOff();
1144  return true;
1145  }
1146  }
1147 
1148  // ----------------------------------------------------------------------------------------------------
1149  if (cmd == "ping")
1150  {
1151  s->writeString("ALIVE");
1152  return true;
1153  }
1154 
1155  // ----------------------------------------------------------------------------------------------------
1156  if (cmd == "serlog")
1157  {
1158  sendSerial(rem, true);
1159  return true;
1160  }
1161 
1162  // ----------------------------------------------------------------------------------------------------
1163  if (cmd == "serout")
1164  {
1165  sendSerial(rem, false);
1166  return true;
1167  }
1168 
1169  // ----------------------------------------------------------------------------------------------------
1170  if (cmd == "runscript")
1171  {
1172  std::string fname;
1173  if (itsModule) fname = itsModule->absolutePath(rem); else fname = rem;
1174 
1175  try { runScriptFromFile(fname, s, true); return true; }
1176  catch (...) { errmsg = "Script execution failed."; }
1177  }
1178 
1179 #ifndef JEVOIS_PLATFORM
1180  // ----------------------------------------------------------------------------------------------------
1181  if (cmd == "quit")
1182  {
1183  s->writeString("Quit command received - bye-bye!");
1184  itsRunning.store(false);
1185  return true;
1186  }
1187  // ----------------------------------------------------------------------------------------------------
1188 #endif
1189  }
1190 
1191  // If we make it here, we did not parse the command. If we have an error message, that means we had started parsing
1192  // the command but it was buggy, so let's throw. Otherwise, we just return false to indicate that we did not parse
1193  // this command and maybe it is for the Module:
1194  if (errmsg.size()) throw std::runtime_error("Command error [" + str + "]: " + errmsg);
1195  return false;
1196 }
1197 
1198 // ####################################################################################################
1199 void jevois::Engine::runScriptFromFile(std::string const & filename, std::shared_ptr<jevois::UserInterface> ser,
1200  bool throw_no_file)
1201 {
1202  // itsMtx should be locked by caller
1203 
1204  // Try to find the file:
1205  std::ifstream ifs(filename);
1206  if (!ifs) { if (throw_no_file) LFATAL("Could not open file " << filename); else return; }
1207 
1208  // We need to identify a serial to send any errors to, if none was given to us. Let's use the one in serlog, or, if
1209  // none is specified there, the first available serial:
1210  if (!ser)
1211  {
1212  if (itsSerials.empty()) LFATAL("Need at least one active serial to run script");
1213  switch (serlog::get())
1214  {
1215  case jevois::engine::SerPort::Hard:
1216  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::Hard) { ser = s; break; }
1217  break;
1218 
1219  case jevois::engine::SerPort::USB:
1220  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::USB) { ser = s; break; }
1221  break;
1222 
1223  default: break;
1224  }
1225  if (!ser) ser = itsSerials.front();
1226  }
1227 
1228  // Ok, run the script, plowing through any errors:
1229  size_t linenum = 1;
1230  for (std::string line; std::getline(ifs, line); /* */)
1231  {
1232  // Skip comments and empty lines:
1233  if (line.length() == 0 || line[0] == '#') continue;
1234 
1235  // Go and parse that line:
1236  try
1237  {
1238  bool parsed = false;
1239  try { parsed = parseCommand(line, ser); }
1240  catch (std::exception const & e)
1241  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + e.what()); }
1242  catch (...)
1243  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ignored"); }
1244 
1245  if (parsed == false)
1246  {
1247  if (itsModule)
1248  {
1249  try { itsModule->parseSerial(line, ser); }
1250  catch (std::exception const & me)
1251  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + me.what()); }
1252  catch (...)
1253  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ignored"); }
1254  }
1255  else ser->writeString("ERR Unsupported command and no module");
1256  }
1257  }
1258  catch (...) { jevois::warnAndIgnoreException(); }
1259 
1260  ++linenum;
1261  }
1262 }
#define LDEBUG(msg)
Convenience macro for users to print out console or syslog messages, DEBUG level. ...
Definition: Log.H:148
std::string warnAndIgnoreException()
Convenience function to catch an exception, issue some LERROR (depending on type), and ignore it.
Definition: Log.C:200
VideoMapping const & getVideoMapping(size_t idx) const
Allow access to our video mappings, which are parsed from file at construction.
Definition: Engine.C:685
float ofps
output frame rate in frames/sec
Definition: VideoMapping.H:46
unsigned int uvcformat
USB-UVC format number (1-based)
Definition: VideoMapping.H:53
size_t numVideoMappings() const
Return the number of video mappings.
Definition: Engine.C:679
Class to open shared object (.so) files and load functions contained in them.
Definition: DynamicLoader.H:69
Exception-safe wrapper around a raw camera input frame.
Definition: Module.H:54
size_t getDefaultVideoMappingIdx() const
Allow access to the default video mapping index.
Definition: Engine.C:729
void postInit() override
Override of Manager::postInit()
Definition: Engine.C:311
#define V4L2_CTRL_CLASS_DETECT
Definition: Engine.C:44
void streamOff()
Stop streaming on video from camera, processing, and USB.
Definition: Engine.C:458
void preInit() override
Calls parseCommandLine()
Definition: Manager.C:49
void preInit() override
Override of Manager::preInit()
Definition: Engine.C:300
bool parseCommand(std::string const &str, std::shared_ptr< UserInterface > s)
Parse a user command received over serial port.
Definition: Engine.C:866
Manager of a hierarchy of Component objects.
Definition: Manager.H:66
void streamOn()
Start streaming on video from camera, processing, and USB.
Definition: Engine.C:447
unsigned int ofmt
output pixel format, or 0 for no output over USB
Definition: VideoMapping.H:43
#define JEVOIS_TIMED_LOCK(mtx)
Helper macro to create a timed_lock_guard object.
Definition: Log.H:292
#define success()
void mainLoop()
Main loop: grab, process, send over USB. Should be called by main application thread.
Definition: Engine.C:567
#define JEVOIS_TRACE(level)
Trace object.
Definition: Log.H:260
std::string modulename
Name of the Module that will process this mapping.
Definition: VideoMapping.H:58
Video output to a movie file, using OpenCV video encoding.
Definition: MovieOutput.H:33
std::string getSysInfoVersion()
Get O.S. version info.
Definition: SysInfo.C:72
void sendSerial(std::string const &str, bool islog=false)
Send a string to all serial ports.
Definition: Engine.C:649
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level. ...
Definition: Log.H:186
VideoMapping const & getDefaultVideoMapping() const
Allow access to the default video mapping.
Definition: Engine.C:725
static float uvcToFps(unsigned int interval)
Convert from USB/UVC interval to fps.
Definition: VideoMapping.C:59
~Engine()
Destructor.
Definition: Engine.C:418
std::string getSysInfoCPU()
Get CPU info: frequency, thermal, load.
Definition: SysInfo.C:38
JeVois camera driver class - grabs frames from a Video4Linux camera sensor.
Definition: Camera.H:64
void logSetEngine(Engine *e)
Set an Engine so that all log messages will be forwarded to its serial ports.
Definition: Log.C:112
Simple struct to hold video mapping definitions for the processing Engine.
Definition: VideoMapping.H:41
void onParamChange(engine::cameradev const &param, std::string const &newval)
Parameter callback.
std::string sopath() const
Return the full absolute path and file name of the module's .so file.
Definition: VideoMapping.C:45
bool stringStartsWith(std::string const &str, std::string const &prefix)
Return true if str starts with prefix (including if both strings are equal)
Definition: Utils.C:89
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:32
unsigned int uvcframe
USB UVC frame number (1-based)
Definition: VideoMapping.H:54
std::vector< VideoMapping > const itsMappings
All our mappings from videomappings.cfg.
Definition: Engine.H:303
JeVois gadget driver - exposes a uvcvideo interface to host computer connected over USB...
Definition: Gadget.H:65
VideoMapping const & findVideoMapping(unsigned int oformat, unsigned int owidth, unsigned int oheight, float oframespersec) const
Find the VideoMapping that has the given output specs, or throw if not found.
Definition: Engine.C:734
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level. ...
Definition: Log.H:205
Movie input, can be used as a replacement for Camera to debug algorithms using a fixed video sequence...
Definition: MovieInput.H:35
std::string str() const
Convenience function to print out the whole mapping in a human-friendly way.
Definition: VideoMapping.C:101
Video output to local screen.
Definition: VideoDisplay.H:34
void runScriptFromFile(std::string const &filename, std::shared_ptr< UserInterface > ser, bool throw_no_file)
Run a script from file.
Definition: Engine.C:1199
std::string to_string(T const &val)
Convert from type to string.
Definition: UtilsImpl.H:51
size_t getVideoMappingIdx(unsigned int iformat, unsigned int iframe, unsigned int interval) const
Get the video mapping index for a given UVC iformat, iframe and interval.
Definition: Engine.C:694
Exception-safe wrapper around a raw image to be sent over USB.
Definition: Module.H:110
bool match(unsigned int oformat, unsigned int owidth, unsigned int oheight, float oframespersec) const
Return true if this VideoMapping's output format is a match to the given output parameters.
Definition: VideoMapping.C:293
void postInit() override
Checks for the –help flag.
Definition: Manager.C:58
std::string getSysInfoMem()
Get memory info.
Definition: SysInfo.C:64
void setFormat(size_t idx)
Callback for when the user selects a new output video format.
Definition: Engine.C:479
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:169
No-op VideoOutput derivative for when there is no video output.
Engine(std::string const &instance)
Constructor.
Definition: Engine.C:196
#define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
std::vector< VideoMapping > loadVideoMappings(size_t &defidx)
Load all the video mappings from the default config file.