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  LINFO(m.str());
501 
502  // Now that the module is nuked, we won't have any get()/done()/send() requests on the camera or gadget, thus it is
503  // safe to change the formats on both:
504  itsCamera->setFormat(m);
505  if (m.ofmt == 0) itsUSBout = false; else { itsGadget->setFormat(m); itsUSBout = true;}
506 
507  // Nuke the processing module, if any, so we can also safely nuke the loader. We always nuke the module instance so we
508  // won't have any issues with latent state even if we re-use the same module but possibly with different input
509  // image resolution, etc:
510  if (itsModule) { removeComponent(itsModule); itsModule.reset(); }
511 
512  // We can however re-use the same loader and avoid closing the .so if we will use the same module:
513  std::string const sopath = m.sopath();
514  if (itsLoader.get() == nullptr || itsLoader->sopath() != sopath)
515  {
516  // Nuke our previous loader and free its resources if needed, then start a new loader:
517  LINFO("Instantiating dynamic loader for " << sopath);
518  itsLoader.reset(new jevois::DynamicLoader(sopath, true));
519  }
520 
521  // Check version match:
522  auto version_major = itsLoader->load<int()>(m.modulename + "_version_major");
523  auto version_minor = itsLoader->load<int()>(m.modulename + "_version_minor");
524  if (version_major() != JEVOIS_VERSION_MAJOR || version_minor() != JEVOIS_VERSION_MINOR)
525  LERROR("Module " << m.modulename << " in file " << sopath << " was build for JeVois v" << version_major() << '.'
526  << version_minor() << ", but running framework is v" << JEVOIS_VERSION_STRING << " -- TRYING ANYWAY");
527 
528  // Instantiate the new module:
529  auto create = itsLoader->load<std::shared_ptr<jevois::Module>(std::string const &)>(m.modulename + "_create");
530  itsModule = create(m.modulename); // Here we just use the class name as instance name
531 
532  // Add it as a component to us. Keep this code in sync with Manager::addComponent():
533  {
534  // Lock up so we guarantee the instance name does not get robbed as we add the sub:
535  boost::unique_lock<boost::shared_mutex> ulck(itsSubMtx);
536 
537  // Then add it as a sub-component to us, if there is not instance name clash with our other sub-components:
538  itsSubComponents.push_back(itsModule);
539  itsModule->itsParent = this;
540  itsModule->setPath(sopath.substr(0, sopath.rfind('/')));
541  }
542 
543  // Bring it to our runstate and load any extra params. NOTE: Keep this in sync with Component::init():
544  if (itsInitialized) itsModule->runPreInit();
545 
546  std::string const paramcfg = itsModule->absolutePath(JEVOIS_MODULE_PARAMS_FILENAME);
547  std::ifstream ifs(paramcfg); if (ifs.is_open()) itsModule->setParamsFromStream(ifs, paramcfg);
548 
549  if (itsInitialized) { itsModule->setInitialized(); itsModule->runPostInit(); }
550 
551  // And finally run any config script:
552  runScriptFromFile(itsModule->absolutePath(JEVOIS_MODULE_SCRIPT_FILENAME), nullptr, false);
553 
554  LINFO("Module [" << m.modulename << "] loaded, initialized, and ready.");
555 }
556 
557 // ####################################################################################################
559 {
560  JEVOIS_TRACE(2);
561 
562  // Announce that we are ready to the hardware serial port, if any. Do not use sendSerial() here so we always issue
563  // this message irrespectively of the user serial preferences:
564  for (auto & s : itsSerials)
565  if (s->instanceName() == "serial")
566  try { s->writeString("INF READY JEVOIS " JEVOIS_VERSION_STRING); }
567  catch (...) { jevois::warnAndIgnoreException(); }
568 
569  while (itsRunning.load())
570  {
571  bool dosleep = true;
572 
573  if (itsStreaming.load())
574  {
575  JEVOIS_TIMED_LOCK(itsMtx);
576 
577  if (itsModule)
578  try
579  {
580  if (itsUSBout) itsModule->process(jevois::InputFrame(itsCamera, itsTurbo), jevois::OutputFrame(itsGadget));
581  else itsModule->process(jevois::InputFrame(itsCamera, itsTurbo));
582  dosleep = false;
583  }
584  catch (...) { jevois::warnAndIgnoreException(); }
585  }
586 
587  if (itsStopMainLoop.load())
588  {
589  itsStreaming.store(false);
590  LDEBUG("-- Main loop stopped --");
591  itsStopMainLoop.store(false);
592  }
593 
594  if (dosleep)
595  {
596  LDEBUG("No processing module loaded or not streaming... Sleeping...");
597  std::this_thread::sleep_for(std::chrono::milliseconds(50));
598  }
599 
600  // Serial input handling. Note that readSome() and writeString() on the serial could throw. The code below is
601  // organized to catch all other exceptions, except for those, which are caught here at the first try level:
602  for (auto & s : itsSerials)
603  {
604  try
605  {
606  std::string str; bool parsed = false; bool success = false;
607 
608  if (s->readSome(str))
609  {
610  JEVOIS_TIMED_LOCK(itsMtx);
611 
612  // Try to execute this command. If the command is for us (e.g., set a parameter) and is correct,
613  // parseCommand() will return true; if it is for us but buggy, it will throw. If it is not recognized by us,
614  // it will return false and we should try sending it to the Module:
615  try { parsed = parseCommand(str, s); success = parsed; }
616  catch (std::exception const & e) { s->writeString(std::string("ERR ") + e.what()); parsed = true; }
617  catch (...) { s->writeString("ERR Unknown error"); parsed = true; }
618 
619  if (parsed == false)
620  {
621  if (itsModule)
622  {
623  try { itsModule->parseSerial(str, s); success = true; }
624  catch (std::exception const & me) { s->writeString(std::string("ERR ") + me.what()); }
625  catch (...) { s->writeString("ERR Command not recognized by Engine or Module"); }
626  }
627  else s->writeString("ERR Unsupported command and no module");
628  }
629 
630  // If success, let user know:
631  if (success) s->writeString("OK");
632  }
633  }
634  catch (...) { jevois::warnAndIgnoreException(); }
635  }
636  }
637 }
638 
639 // ####################################################################################################
640 void jevois::Engine::sendSerial(std::string const & str, bool islog)
641 {
642  // Decide where to send this message based on the value of islog:
643  jevois::engine::SerPort p = islog ? serlog::get() : serout::get();
644 
645  switch (p)
646  {
647  case jevois::engine::SerPort::None:
648  break; // Nothing to send
649 
650  case jevois::engine::SerPort::All:
651  for (auto & s : itsSerials)
652  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
653  break;
654 
655  case jevois::engine::SerPort::Hard:
656  for (auto & s : itsSerials)
657  if (s->type() == jevois::UserInterface::Type::Hard)
658  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
659  break;
660 
661  case jevois::engine::SerPort::USB:
662  for (auto & s : itsSerials)
663  if (s->type() == jevois::UserInterface::Type::USB)
664  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
665  break;
666  }
667 }
668 
669 // ####################################################################################################
671 {
672  return itsMappings.size();
673 }
674 
675 // ####################################################################################################
677 {
678  if (idx >= itsMappings.size())
679  LFATAL("Index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
680 
681  return itsMappings[idx];
682 }
683 
684 // ####################################################################################################
685 size_t jevois::Engine::getVideoMappingIdx(unsigned int iformat, unsigned int iframe, unsigned int interval) const
686 {
687  // If the iformat or iframe is zero, that's probably a probe for the default mode, so return it:
688  if (iformat == 0 || iframe == 0) return itsDefaultMappingIdx;
689 
690  // If interval is zero, probably a driver trying to probe for our default interval, so return the first available one;
691  // otherwise try to find the desired interval and return the corresponding mapping:
692  if (interval)
693  {
694  float const fps = jevois::VideoMapping::uvcToFps(interval);
695  size_t idx = 0;
696 
697  for (jevois::VideoMapping const & m : itsMappings)
698  if (m.uvcformat == iformat && m.uvcframe == iframe && std::fabs(m.ofps - fps) < 0.1F) return idx;
699  else ++idx;
700 
701  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
702  }
703  else
704  {
705  size_t idx = 0;
706 
707  for (jevois::VideoMapping const & m : itsMappings)
708  if (m.uvcformat == iformat && m.uvcframe == iframe) return idx;
709  else ++idx;
710 
711  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
712  }
713 }
714 
715 // ####################################################################################################
717 { return itsMappings[itsDefaultMappingIdx]; }
718 
719 // ####################################################################################################
721 { return itsDefaultMappingIdx; }
722 
723 // ####################################################################################################
724 jevois::VideoMapping const &
725 jevois::Engine::findVideoMapping(unsigned int oformat, unsigned int owidth, unsigned int oheight,
726  float oframespersec) const
727 {
728  for (jevois::VideoMapping const & m : itsMappings)
729  if (m.match(oformat, owidth, oheight, oframespersec)) return m;
730 
731  LFATAL("Could not find mapping for output format " << jevois::fccstr(oformat) << ' ' <<
732  owidth << 'x' << oheight << " @ " << oframespersec << " fps");
733 }
734 
735 // ####################################################################################################
736 std::string jevois::Engine::camctrlname(int id, char const * longname) const
737 {
738  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
739  if (camcontrols[i].id == id) return camcontrols[i].shortname;
740 
741  // Darn, this control is not in our list, probably something exotic. Compute a name from the control's long name:
742  return abbreviate(longname);
743 }
744 
745 // ####################################################################################################
746 int jevois::Engine::camctrlid(std::string const & shortname)
747 {
748  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
749  if (shortname.compare(camcontrols[i].shortname) == 0) return camcontrols[i].id;
750 
751  // Not in our list, all right, let's find it then in the camera:
752  struct v4l2_queryctrl qc = { };
753  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
754  {
755  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
756  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
757  // doneids set to keep track of the ones already reported:
758  qc.id = cls | 0x900;
759  while (true)
760  {
761  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; unsigned int old_id = qc.id; bool failed = false;
762  try
763  {
764  itsCamera->queryControl(qc);
765  if (abbreviate(reinterpret_cast<char const *>(qc.name)) == shortname) return qc.id;
766  }
767  catch (...) { failed = true; }
768 
769  // With V4L2_CTRL_FLAG_NEXT_CTRL, the camera kernel driver is supposed to pass down the next valid control if
770  // the requested one is not found, but some drivers do not honor that, so let's move on to the next control
771  // manually if needed:
772  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
773  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
774  else if (failed) break;
775  }
776  }
777 
778  LFATAL("Could not find control [" << shortname << "] in the camera");
779 }
780 
781 // ####################################################################################################
782 std::string jevois::Engine::camCtrlHelp(struct v4l2_queryctrl & qc, std::set<int> & doneids)
783 {
784  // See if we have this control:
785  itsCamera->queryControl(qc);
786  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
787 
788  // If we have already done this control, just return an empty string:
789  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
790 
791  // Control exists, let's also get its current value:
792  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
793  itsCamera->getControl(ctrl);
794 
795  // Print out some description depending on control type:
796  std::ostringstream ss;
797  ss << "- " << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
798 
799  switch (qc.type)
800  {
801  case V4L2_CTRL_TYPE_INTEGER:
802  ss << " [int] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
803  << " def=" << qc.default_value << " curr=" << ctrl.value;
804  break;
805 
806  //case V4L2_CTRL_TYPE_INTEGER64:
807  //ss << " [int64] value=" << ctrl.value64;
808  //break;
809 
810  //case V4L2_CTRL_TYPE_STRING:
811  //ss << " [str] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
812  // << " curr=" << ctrl.string;
813  //break;
814 
815  case V4L2_CTRL_TYPE_BOOLEAN:
816  ss << " [bool] default=" << qc.default_value << " curr=" << ctrl.value;
817  break;
818 
819  // This one is not supported by the older kernel on platform:
820  //case V4L2_CTRL_TYPE_INTEGER_MENU:
821  //ss << " [intmenu] min=" << qc.minimum << " max=" << qc.maximum
822  // << " def=" << qc.default_value << " curr=" << ctrl.value;
823  //break;
824 
825  case V4L2_CTRL_TYPE_BUTTON:
826  ss << " [button]";
827  break;
828 
829  case V4L2_CTRL_TYPE_BITMASK:
830  ss << " [bitmask] max=" << qc.maximum << " def=" << qc.default_value << " curr=" << ctrl.value;
831  break;
832 
833  case V4L2_CTRL_TYPE_MENU:
834  {
835  struct v4l2_querymenu querymenu = { };
836  querymenu.id = qc.id;
837  ss << " [menu] values ";
838  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
839  {
840  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
841  ss << querymenu.index << ':' << querymenu.name << ' ';
842  }
843  ss << "curr=" << ctrl.value;
844  }
845  break;
846 
847  default:
848  ss << "[unknown type]";
849  }
850 
851  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " [DISABLED]";
852 
853  return ss.str();
854 }
855 
856 // ####################################################################################################
857 bool jevois::Engine::parseCommand(std::string const & str, std::shared_ptr<UserInterface> s)
858 {
859  // itsMtx should be locked by caller
860 
861  std::string errmsg;
862 
863  // Note: ModemManager on Ubuntu sends this on startup, kill ModemManager to avoid:
864  // 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
865  //
866  // AT^SQPORT?
867  // AT
868  // AT
869  // AT
870  // ~
871  //
872  // then later on it insists on trying to mess with us, issuing things like AT, AT+CGMI, AT+GMI, AT+CGMM, AT+GMM,
873  // AT%IPSYS?, ATE0, ATV1, etc etc
874 
875  switch (str.length())
876  {
877  case 0:
878  LDEBUG("Ignoring empty string"); return true;
879  break;
880 
881  case 1:
882  if (str[0] == '~') { LDEBUG("Ignoring modem config command [~]"); return true; }
883 
884  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
885  // from the arduino to be printed out to the user:
886  if (str[0] == '#') { sendSerial(str, true); return true; }
887  break;
888 
889  default: // length is 2 or more:
890 
891  // Ignore any command that starts with a '~':
892  if (str[0] == '~') { LDEBUG("Ignoring modem config command [" << str << ']'); return true; }
893 
894  // Ignore any command that starts with "AT":
895  if (str[0] == 'A' && str[1] == 'T') { LDEBUG("Ignoring AT command [" << str <<']'); return true; }
896 
897  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
898  // in the arduino to be printed out to the user:
899  if (str[0] == '#') { sendSerial(str, true); return true; }
900 
901  // Get the first word, i.e., the command:
902  size_t const idx = str.find(' '); std::string cmd, rem;
903  if (idx == str.npos) cmd = str; else { cmd = str.substr(0, idx); if (idx < str.length()) rem = str.substr(idx+1); }
904 
905  if (cmd == "help")
906  {
907  // Show all commands, first ours, as supported below:
908  s->writeString("GENERAL COMMANDS:");
909  s->writeString("");
910  s->writeString("help - print this help message");
911  s->writeString("info - show system information including CPU speed, load and temperature");
912  s->writeString("setpar <name> <value> - set a parameter value");
913  s->writeString("getpar <name> - get a parameter value(s)");
914  s->writeString("runscript <filename> - run script commands in specified file");
915  s->writeString("setcam <ctrl> <val> - set camera control <ctrl> to value <val>");
916  s->writeString("getcam <ctrl> - get value of camera control <ctrl>");
917  if (camreg::get())
918  {
919  s->writeString("setcamreg <reg> <val> - set raw camera register <reg> to value <val>");
920  s->writeString("getcamreg <reg> - get value of raw camera register <reg>");
921  }
922  s->writeString("listmappings - list all available video mappings");
923  s->writeString("setmapping <num> - select video mapping <num>, only possible while not streaming to USB");
924  if (itsUSBout == false || itsManualStreamon)
925  {
926  s->writeString("streamon - start camera video streaming");
927  s->writeString("streamoff - stop camera video streaming");
928  }
929  s->writeString("ping - returns 'ALIVE'");
930  s->writeString("serlog <string> - forward string to the serial port(s) specified by the serlog parameter");
931  s->writeString("serout <string> - forward string to the serial port(s) specified by the serout parameter");
932 
933 #ifndef JEVOIS_PLATFORM
934  s->writeString("quit - quit this program");
935 #endif
936  s->writeString("");
937 
938  // Then the module's custom commands, if any:
939  if (itsModule)
940  {
941  std::stringstream css; itsModule->supportedCommands(css);
942  s->writeString("MODULE-SPECIFIC COMMANDS:");
943  s->writeString("");
944  for (std::string line; std::getline(css, line); /* */) s->writeString(line);
945  s->writeString("");
946  }
947 
948  // Get the help message for our parameters and write it out line by line so the serial fixes the line endings:
949  std::stringstream pss; constructHelpMessage(pss);
950  for (std::string line; std::getline(pss, line); /* */) s->writeString(line);
951 
952  // Show all camera controls
953  s->writeString("AVAILABLE CAMERA CONTROLS:");
954  s->writeString("");
955 
956  struct v4l2_queryctrl qc = { }; std::set<int> doneids;
957  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
958  {
959  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
960  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
961  // doneids set to keep track of the ones already reported:
962  qc.id = cls | 0x900; unsigned int old_id;
963  while (true)
964  {
965  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; old_id = qc.id; bool failed = false;
966  try { std::string hlp = camCtrlHelp(qc, doneids); if (hlp.empty() == false) s->writeString(hlp); }
967  catch (...) { failed = true; }
968 
969  // The camera kernel driver is supposed to pass down the next valid control if the requested one is not
970  // found, but some drivers do not honor that, so let's move on to the next control manually if needed:
971  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
972  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
973  else if (failed) break;
974  }
975  }
976 
977  return true;
978  }
979 
980  // ----------------------------------------------------------------------------------------------------
981  if (cmd == "info")
982  {
983  s->writeString("INFO: JeVois " JEVOIS_VERSION_STRING);
984  s->writeString("INFO: " + jevois::getSysInfoVersion());
985  s->writeString("INFO: " + jevois::getSysInfoCPU());
986  s->writeString("INFO: " + jevois::getSysInfoMem());
987  return true;
988  }
989 
990  // ----------------------------------------------------------------------------------------------------
991  if (cmd == "setpar")
992  {
993  std::istringstream ss(rem); std::string desc, val; ss >> desc; ss >> val;
994  setParamString(desc, val);
995  return true;
996  }
997 
998  // ----------------------------------------------------------------------------------------------------
999  if (cmd == "getpar")
1000  {
1001  auto vec = getParamString(rem);
1002  for (auto const & p : vec) s->writeString(p.first + ' ' + p.second);
1003  return true;
1004  }
1005 
1006  // ----------------------------------------------------------------------------------------------------
1007  if (cmd == "setcam")
1008  {
1009  std::istringstream ss(rem); std::string ctrl; int val; ss >> ctrl >> val;
1010  struct v4l2_control c = { }; c.id = camctrlid(ctrl); c.value = val;
1011  itsCamera->setControl(c);
1012  return true;
1013  }
1014 
1015  // ----------------------------------------------------------------------------------------------------
1016  if (cmd == "getcam")
1017  {
1018  struct v4l2_control c = { }; c.id = camctrlid(rem);
1019  itsCamera->getControl(c);
1020  s->writeString(rem + ' ' + std::to_string(c.value));
1021  return true;
1022  }
1023 
1024  // ----------------------------------------------------------------------------------------------------
1025  if (cmd == "setcamreg")
1026  {
1027  if (camreg::get())
1028  {
1029  std::istringstream ss(rem); unsigned char reg, val; ss >> reg >> val;
1030  itsCamera->writeRegister(reg, val);
1031  return true;
1032  }
1033  errmsg = "Access to camera registers is disabled, enable with --camreg=true";
1034  }
1035 
1036  // ----------------------------------------------------------------------------------------------------
1037  if (cmd == "getcamreg")
1038  {
1039  if (camreg::get())
1040  {
1041  unsigned int val = itsCamera->readRegister(std::stoi(rem));
1042  std::ostringstream os; os << std::hex << val;
1043  s->writeString(os.str());
1044  return true;
1045  }
1046  errmsg = "Access to camera registers is disabled, enable with --camreg=true";
1047  }
1048 
1049  // ----------------------------------------------------------------------------------------------------
1050  if (cmd == "listmappings")
1051  {
1052  s->writeString("AVAILABLE VIDEO MAPPINGS:");
1053  s->writeString("");
1054  for (size_t idx = 0; idx < itsMappings.size(); ++idx)
1055  {
1056  std::string idxstr = std::to_string(idx);
1057  if (idxstr.length() < 5) idxstr = std::string(5 - idxstr.length(), ' ') + idxstr; // pad to 5-char long
1058  s->writeString(idxstr + " - " + itsMappings[idx].str());
1059  }
1060  return true;
1061  }
1062 
1063  // ----------------------------------------------------------------------------------------------------
1064  if (cmd == "setmapping")
1065  {
1066  size_t const idx = std::stoi(rem);
1067  bool was_streaming = itsStreaming.load();
1068 
1069  if (was_streaming)
1070  {
1071  errmsg = "Cannot set mapping while streaming: ";
1072  if (itsUSBout) errmsg += "Stop your webcam program on the host computer first.";
1073  else errmsg += "Issue a 'streamoff' command first.";
1074  }
1075  else if (idx >= itsMappings.size())
1076  errmsg = "Requested mapping index " + std::to_string(idx) + " out of range [0 .. " +
1077  std::to_string(itsMappings.size()-1) + ']';
1078  else
1079  {
1080  setFormatInternal(idx);
1081  return true;
1082  }
1083  }
1084 
1085  // ----------------------------------------------------------------------------------------------------
1086  if (itsUSBout == false || itsManualStreamon)
1087  {
1088  if (cmd == "streamon")
1089  {
1090  // keep this in sync with streamOn(), modulo the fact that here we are already locked:
1091  itsCamera->streamOn();
1092  itsGadget->streamOn();
1093  itsStreaming.store(true);
1094  return true;
1095  }
1096 
1097  if (cmd == "streamoff")
1098  {
1099  // keep this in sync with streamOff(), modulo the fact that here we are already locked:
1100  itsGadget->abortStream();
1101  itsCamera->abortStream();
1102 
1103  itsStreaming.store(false);
1104 
1105  itsGadget->streamOff();
1106  itsCamera->streamOff();
1107  return true;
1108  }
1109  }
1110 
1111  // ----------------------------------------------------------------------------------------------------
1112  if (cmd == "ping")
1113  {
1114  s->writeString("ALIVE");
1115  return true;
1116  }
1117 
1118  // ----------------------------------------------------------------------------------------------------
1119  if (cmd == "serlog")
1120  {
1121  sendSerial(rem, true);
1122  return true;
1123  }
1124 
1125  // ----------------------------------------------------------------------------------------------------
1126  if (cmd == "serout")
1127  {
1128  sendSerial(rem, false);
1129  return true;
1130  }
1131 
1132  // ----------------------------------------------------------------------------------------------------
1133  if (cmd == "runscript")
1134  {
1135  std::string fname;
1136  if (itsModule) fname = itsModule->absolutePath(rem); else fname = rem;
1137 
1138  try { runScriptFromFile(fname, s, true); return true; }
1139  catch (...) { errmsg = "Script execution failed."; }
1140  }
1141 
1142 #ifndef JEVOIS_PLATFORM
1143  // ----------------------------------------------------------------------------------------------------
1144  if (cmd == "quit")
1145  {
1146  s->writeString("Quit command received - bye-bye!");
1147  itsRunning.store(false);
1148  return true;
1149  }
1150  // ----------------------------------------------------------------------------------------------------
1151 #endif
1152  }
1153 
1154  // If we make it here, we did not parse the command. If we have an error message, that means we had started parsing
1155  // the command but it was buggy, so let's throw. Otherwise, we just return false to indicate that we did not parse
1156  // this command and maybe it is for the Module:
1157  if (errmsg.size()) throw std::runtime_error("Command error [" + str + "]: " + errmsg);
1158  return false;
1159 }
1160 
1161 // ####################################################################################################
1162 void jevois::Engine::runScriptFromFile(std::string const & filename, std::shared_ptr<jevois::UserInterface> ser,
1163  bool throw_no_file)
1164 {
1165  // itsMtx should be locked by caller
1166 
1167  // Try to find the file:
1168  std::ifstream ifs(filename);
1169  if (!ifs) { if (throw_no_file) LFATAL("Could not open file " << filename); else return; }
1170 
1171  // 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
1172  // none is specified there, the first available serial:
1173  if (!ser)
1174  {
1175  if (itsSerials.empty()) LFATAL("Need at least one active serial to run script");
1176  switch (serlog::get())
1177  {
1178  case jevois::engine::SerPort::Hard:
1179  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::Hard) { ser = s; break; }
1180  break;
1181 
1182  case jevois::engine::SerPort::USB:
1183  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::USB) { ser = s; break; }
1184  break;
1185 
1186  default: break;
1187  }
1188  if (!ser) ser = itsSerials.front();
1189  }
1190 
1191  // Ok, run the script, plowing through any errors:
1192  size_t linenum = 1;
1193  for (std::string line; std::getline(ifs, line); /* */)
1194  {
1195  // Skip comments and empty lines:
1196  if (line.length() == 0 || line[0] == '#') continue;
1197 
1198  // Go and parse that line:
1199  try
1200  {
1201  bool parsed = false;
1202  try { parsed = parseCommand(line, ser); }
1203  catch (std::exception const & e)
1204  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + e.what()); }
1205  catch (...)
1206  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ignored"); }
1207 
1208  if (parsed == false)
1209  {
1210  if (itsModule)
1211  {
1212  try { itsModule->parseSerial(line, ser); }
1213  catch (std::exception const & me)
1214  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + me.what()); }
1215  catch (...)
1216  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ignored"); }
1217  }
1218  else ser->writeString("ERR Unsupported command and no module");
1219  }
1220  }
1221  catch (...) { jevois::warnAndIgnoreException(); }
1222 
1223  ++linenum;
1224  }
1225 }
#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:676
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:670
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:720
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:857
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:558
#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:640
#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:716
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:87
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:30
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:725
#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.
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:1162
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:685
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:272
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.