JeVois  1.12
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
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>
35 
36 #include <jevois/Debug/Log.H>
37 #include <jevois/Util/Utils.H>
38 #include <jevois/Debug/SysInfo.H>
39 
40 #include <cmath> // for fabs
41 #include <fstream>
42 #include <algorithm>
43 #include <cstdlib> // for std::system()
44 #include <cstdio> // for std::remove()
45 
46 // On the older platform kernel, detect class is not defined:
47 #ifndef V4L2_CTRL_CLASS_DETECT
48 #define V4L2_CTRL_CLASS_DETECT 0x00a30000
49 #endif
50 
51 namespace
52 {
53  // Assign a short name to every V4L2 control, for use by getcam and setcam commands
54  struct shortcontrol { int id; char const * const shortname; };
55 
56  // All V4L2 controls
57  // From this: grep V4L2_CID v4l2-controls.h | awk '{ print " { " $2 ", \"\" }," }'
58  // then fill-in the short names.
59  static shortcontrol camcontrols[] = {
60  // In V4L2_CID_BASE class:
61  { V4L2_CID_BRIGHTNESS, "brightness" },
62  { V4L2_CID_CONTRAST, "contrast" },
63  { V4L2_CID_SATURATION, "saturation" },
64  { V4L2_CID_HUE, "hue" },
65  { V4L2_CID_AUDIO_VOLUME, "audiovol" },
66  { V4L2_CID_AUDIO_BALANCE, "audiobal" },
67  { V4L2_CID_AUDIO_BASS, "audiobass" },
68  { V4L2_CID_AUDIO_TREBLE, "audiotreble" },
69  { V4L2_CID_AUDIO_MUTE, "audiomute" },
70  { V4L2_CID_AUDIO_LOUDNESS, "audioloudness" },
71  { V4L2_CID_BLACK_LEVEL, "blacklevel" },
72  { V4L2_CID_AUTO_WHITE_BALANCE, "autowb" },
73  { V4L2_CID_DO_WHITE_BALANCE, "dowb" },
74  { V4L2_CID_RED_BALANCE, "redbal" },
75  { V4L2_CID_BLUE_BALANCE, "bluebal" },
76  { V4L2_CID_GAMMA, "gamma" },
77  { V4L2_CID_WHITENESS, "whiteness" },
78  { V4L2_CID_EXPOSURE, "exposure" },
79  { V4L2_CID_AUTOGAIN, "autogain" },
80  { V4L2_CID_GAIN, "gain" },
81  { V4L2_CID_HFLIP, "hflip" },
82  { V4L2_CID_VFLIP, "vflip" },
83  { V4L2_CID_POWER_LINE_FREQUENCY, "powerfreq" },
84  { V4L2_CID_HUE_AUTO, "autohue" },
85  { V4L2_CID_WHITE_BALANCE_TEMPERATURE, "wbtemp" },
86  { V4L2_CID_SHARPNESS, "sharpness" },
87  { V4L2_CID_BACKLIGHT_COMPENSATION, "backlight" },
88  { V4L2_CID_CHROMA_AGC, "chromaagc" },
89  { V4L2_CID_COLOR_KILLER, "colorkiller" },
90  { V4L2_CID_COLORFX, "colorfx" },
91  { V4L2_CID_AUTOBRIGHTNESS, "autobrightness" },
92  { V4L2_CID_BAND_STOP_FILTER, "bandfilter" },
93  { V4L2_CID_ROTATE, "rotate" },
94  { V4L2_CID_BG_COLOR, "bgcolor" },
95  { V4L2_CID_CHROMA_GAIN, "chromagain" },
96  { V4L2_CID_ILLUMINATORS_1, "illum1" },
97  { V4L2_CID_ILLUMINATORS_2, "illum2" },
98  { V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, "mincapbuf" },
99  { V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, "minoutbuf" },
100  { V4L2_CID_ALPHA_COMPONENT, "alphacompo" },
101  // This one is not defined in our older platform kernel:
102  //{ V4L2_CID_COLORFX_CBCR, "colorfxcbcr" },
103 
104  // In V4L2_CID_CAMERA_CLASS_BASE class
105  { V4L2_CID_EXPOSURE_AUTO, "autoexp" },
106  { V4L2_CID_EXPOSURE_ABSOLUTE, "absexp" },
107  { V4L2_CID_EXPOSURE_AUTO_PRIORITY, "exppri" },
108  { V4L2_CID_PAN_RELATIVE, "panrel" },
109  { V4L2_CID_TILT_RELATIVE, "tiltrel" },
110  { V4L2_CID_PAN_RESET, "panreset" },
111  { V4L2_CID_TILT_RESET, "tiltreset" },
112  { V4L2_CID_PAN_ABSOLUTE, "panabs" },
113  { V4L2_CID_TILT_ABSOLUTE, "tiltabs" },
114  { V4L2_CID_FOCUS_ABSOLUTE, "focusabs" },
115  { V4L2_CID_FOCUS_RELATIVE, "focusrel" },
116  { V4L2_CID_FOCUS_AUTO, "focusauto" },
117  { V4L2_CID_ZOOM_ABSOLUTE, "zoomabs" },
118  { V4L2_CID_ZOOM_RELATIVE, "zoomrel" },
119  { V4L2_CID_ZOOM_CONTINUOUS, "zoomcontinuous" },
120  { V4L2_CID_PRIVACY, "privacy" },
121  { V4L2_CID_IRIS_ABSOLUTE, "irisabs" },
122  { V4L2_CID_IRIS_RELATIVE, "irisrel" },
123 
124  // definition for this one seems to be in the kernel but missing somehow here:
125 #ifndef V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
126 #define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE (V4L2_CID_CAMERA_CLASS_BASE+20)
127 #endif
128  { V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, "presetwb" },
129 
130  // Those are not defined in our older platform kernel:
131  //{ V4L2_CID_AUTO_EXPOSURE_BIAS, "expbias" },
132  //{ V4L2_CID_WIDE_DYNAMIC_RANGE, "wdr" },
133  //{ V4L2_CID_IMAGE_STABILIZATION, "stabilization" },
134  //{ V4L2_CID_ISO_SENSITIVITY, "isosens" },
135  //{ V4L2_CID_ISO_SENSITIVITY_AUTO, "isosensauto" },
136  //{ V4L2_CID_EXPOSURE_METERING, "expmetering" },
137  //{ V4L2_CID_SCENE_MODE, "scene" },
138  //{ V4L2_CID_3A_LOCK, "3alock" },
139  //{ V4L2_CID_AUTO_FOCUS_START, "autofocusstart" },
140  //{ V4L2_CID_AUTO_FOCUS_STOP, "autofocusstop" },
141  //{ V4L2_CID_AUTO_FOCUS_STATUS, "autofocusstatus" },
142  //{ V4L2_CID_AUTO_FOCUS_RANGE, "autofocusrange" },
143  //{ V4L2_CID_PAN_SPEED, "panspeed" },
144  //{ V4L2_CID_TILT_SPEED, "tiltspeed" },
145 
146  // In V4L2_CID_FLASH_CLASS_BASE:
147  { V4L2_CID_FLASH_LED_MODE, "flashled" },
148  { V4L2_CID_FLASH_STROBE_SOURCE, "flashstrobesrc" },
149  { V4L2_CID_FLASH_STROBE, "flashstrobe" },
150  { V4L2_CID_FLASH_STROBE_STOP, "flashstrobestop" },
151  { V4L2_CID_FLASH_STROBE_STATUS, "flashstrovestat" },
152  { V4L2_CID_FLASH_TIMEOUT, "flashtimeout" },
153  { V4L2_CID_FLASH_INTENSITY, "flashintens" },
154  { V4L2_CID_FLASH_TORCH_INTENSITY, "flashtorch" },
155  { V4L2_CID_FLASH_INDICATOR_INTENSITY, "flashindintens" },
156  { V4L2_CID_FLASH_FAULT, "flashfault" },
157  { V4L2_CID_FLASH_CHARGE, "flashcharge" },
158  { V4L2_CID_FLASH_READY, "flashready" },
159 
160  // In V4L2_CID_JPEG_CLASS_BASE:
161  { V4L2_CID_JPEG_CHROMA_SUBSAMPLING, "jpegchroma" },
162  { V4L2_CID_JPEG_RESTART_INTERVAL, "jpegrestartint" },
163  { V4L2_CID_JPEG_COMPRESSION_QUALITY, "jpegcompression" },
164  { V4L2_CID_JPEG_ACTIVE_MARKER, "jpegmarker" },
165 
166  // In V4L2_CID_IMAGE_SOURCE_CLASS_BASE:
167  // Those are not defined in our older platform kernel:
168  //{ V4L2_CID_VBLANK, "vblank" },
169  //{ V4L2_CID_HBLANK, "hblank" },
170  //{ V4L2_CID_ANALOGUE_GAIN, "again" },
171  //{ V4L2_CID_TEST_PATTERN_RED, "testpatred" },
172  //{ V4L2_CID_TEST_PATTERN_GREENR, "testpatgreenr" },
173  //{ V4L2_CID_TEST_PATTERN_BLUE, "testpatblue" },
174  //{ V4L2_CID_TEST_PATTERN_GREENB, "testpatbreenb" },
175 
176  // In V4L2_CID_IMAGE_PROC_CLASS_BASE:
177  //{ V4L2_CID_LINK_FREQ, "linkfreq" },
178  //{ V4L2_CID_PIXEL_RATE, "pixrate" },
179  //{ V4L2_CID_TEST_PATTERN, "testpat" },
180 
181  // In V4L2_CID_DETECT_CLASS_BASE:
182  //{ V4L2_CID_DETECT_MD_MODE, "detectmode" },
183  //{ V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, "detectthresh" },
184  //{ V4L2_CID_DETECT_MD_THRESHOLD_GRID, "detectthreshgrid" },
185  //{ V4L2_CID_DETECT_MD_REGION_GRID, "detectregiongrid" },
186  };
187 
188  // Convert a long name to a short name:
189  std::string abbreviate(std::string const & longname)
190  {
191  std::string name(longname);
192  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
193  name.erase(std::remove_if(name.begin(), name.end(), [](int c) { return !std::isalnum(c); }), name.end());
194  return name;
195  }
196 } // anonymous namespace
197 
198 
199 // ####################################################################################################
200 jevois::Engine::Engine(std::string const & instance) :
201  jevois::Manager(instance), itsMappings(), itsRunning(false), itsStreaming(false), itsStopMainLoop(false),
202  itsTurbo(false), itsManualStreamon(false), itsVideoErrors(false), itsFrame(0), itsNumSerialSent(0)
203 {
204  JEVOIS_TRACE(1);
205 
206 #ifdef JEVOIS_PLATFORM
207  // Start mass storage thread:
208  itsCheckingMassStorage.store(false); itsMassStorageMode.store(false);
209  itsCheckMassStorageFut = std::async(std::launch::async, &jevois::Engine::checkMassStorage, this);
210  while (itsCheckingMassStorage.load() == false) std::this_thread::sleep_for(std::chrono::milliseconds(5));
211 #endif
212 }
213 
214 // ####################################################################################################
215 jevois::Engine::Engine(int argc, char const* argv[], std::string const & instance) :
216  jevois::Manager(argc, argv, instance), itsMappings(), itsRunning(false), itsStreaming(false),
217  itsStopMainLoop(false), itsTurbo(false), itsManualStreamon(false), itsVideoErrors(false), itsFrame(0),
218  itsNumSerialSent(0)
219 {
220  JEVOIS_TRACE(1);
221 
222 #ifdef JEVOIS_PLATFORM
223  // Start mass storage thread:
224  itsCheckingMassStorage.store(false); itsMassStorageMode.store(false);
225  itsCheckMassStorageFut = std::async(std::launch::async, &jevois::Engine::checkMassStorage, this);
226  while (itsCheckingMassStorage.load() == false) std::this_thread::sleep_for(std::chrono::milliseconds(5));
227 #endif
228 }
229 
230 // ####################################################################################################
231 void jevois::Engine::onParamChange(jevois::engine::serialdev const & JEVOIS_UNUSED_PARAM(param),
232  std::string const & newval)
233 {
235 
236  // If we have a serial already, nuke it:
237  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
238  if ((*itr)->instanceName() == "serial") itr = itsSerials.erase(itr);
239  removeComponent("serial", false);
240 
241  // Open the usb hardware (4-pin connector) serial port, if any:
242  if (newval.empty() == false)
243  try
244  {
245  std::shared_ptr<jevois::UserInterface> s;
246  if (newval == "stdio")
247  s = addComponent<jevois::StdioInterface>("serial");
248  else
249  {
250  s = addComponent<jevois::Serial>("serial", jevois::UserInterface::Type::Hard);
251  s->setParamVal("devname", newval);
252  }
253 
254  itsSerials.push_back(s);
255  LINFO("Using [" << newval << "] hardware (4-pin connector) serial port");
256  }
257  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start hardware (4-pin connector) serial port"); }
258  else LINFO("No hardware (4-pin connector) serial port used");
259 }
260 
261 // ####################################################################################################
262 void jevois::Engine::onParamChange(jevois::engine::usbserialdev const & JEVOIS_UNUSED_PARAM(param),
263  std::string const & newval)
264 {
266 
267  // If we have a usbserial already, nuke it:
268  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
269  if ((*itr)->instanceName() == "usbserial") itr = itsSerials.erase(itr);
270  removeComponent("usbserial", false);
271 
272  // Open the USB serial port, if any:
273  if (newval.empty() == false)
274  try
275  {
276  std::shared_ptr<jevois::UserInterface> s =
277  addComponent<jevois::Serial>("usbserial", jevois::UserInterface::Type::USB);
278  s->setParamVal("devname", newval);
279  itsSerials.push_back(s);
280  LINFO("Using [" << newval << "] USB serial port");
281  }
282  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start USB serial port"); }
283  else LINFO("No USB serial port used");
284 }
285 
286 // ####################################################################################################
287 void jevois::Engine::onParamChange(jevois::engine::cpumode const & JEVOIS_UNUSED_PARAM(param),
288  jevois::engine::CPUmode const & newval)
289 {
290  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");
291  if (ofs.is_open() == false)
292  {
293 #ifdef JEVOIS_PLATFORM
294  LERROR("Cannot set cpu frequency governor mode -- IGNORED");
295 #endif
296  return;
297  }
298 
299  switch (newval)
300  {
301  case engine::CPUmode::PowerSave: ofs << "powersave" << std::endl; break;
302  case engine::CPUmode::Conservative: ofs << "conservative" << std::endl; break;
303  case engine::CPUmode::OnDemand: ofs << "ondemand" << std::endl; break;
304  case engine::CPUmode::Interactive: ofs << "interactive" << std::endl; break;
305  case engine::CPUmode::Performance: ofs << "performance" << std::endl; break;
306  }
307 }
308 
309 // ####################################################################################################
310 void jevois::Engine::onParamChange(jevois::engine::cpumax const & JEVOIS_UNUSED_PARAM(param),
311  unsigned int const & newval)
312 {
313  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
314  if (ofs.is_open() == false)
315  {
316 #ifdef JEVOIS_PLATFORM
317  LERROR("Cannot set cpu max frequency -- IGNORED");
318 #endif
319  return;
320  }
321 
322  ofs << newval * 1000U << std::endl;
323 }
324 
325 // ####################################################################################################
326 void jevois::Engine::onParamChange(jevois::engine::videoerrors const & JEVOIS_UNUSED_PARAM(param),
327  bool const & newval)
328 {
329  itsVideoErrors.store(newval);
330 }
331 
332 // ####################################################################################################
334 {
335  // Set any initial parameters from global config file:
336  std::string const paramcfg = std::string(JEVOIS_CONFIG_PATH) + '/' + JEVOIS_MODULE_PARAMS_FILENAME;
337  std::ifstream ifs(paramcfg); if (ifs.is_open()) setParamsFromStream(ifs, paramcfg);
338 
339  // Run the Manager version. This parses the command line:
341 }
342 
343 // ####################################################################################################
345 {
346  // First make sure the manager gets to run this:
348 
349  // Prevent any setFormat() that may be requested, e.g., by the Inventor, as soon as it detects our gadget, until after
350  // we have completed running the initscript:
352 
353  // Freeze the serial port device names, their params, and camera and gadget too:
354  serialdev::freeze();
355  usbserialdev::freeze();
356  for (auto & s : itsSerials) s->freezeAllParams();
357  cameradev::freeze();
358  camerasens::freeze();
359  cameranbuf::freeze();
360  camturbo::freeze();
361  gadgetdev::freeze();
362  gadgetnbuf::freeze();
363  itsTurbo = camturbo::get();
364  multicam::freeze();
365  quietcmd::freeze();
366  python::freeze();
367 
368  // Grab the log messages, itsSerials is not going to change anymore now that the serial params are frozen:
369  jevois::logSetEngine(this);
370 
371  // Load our video mappings:
372  itsMappings = jevois::loadVideoMappings(camerasens::get(), itsDefaultMappingIdx);
373  LINFO("Loaded " << itsMappings.size() << " vision processing modes.");
374 
375  // Get python going, we need to do this here to avoid segfaults on platform when instantiating our first python
376  // module. This likely has to do with the fact that the python core is not very thread-safe, and setFormatInternal()
377  // in Engine, which instantiates python modules, will indeed be invoked from a different thread (the one that receives
378  // USB UVC events). Have a look at Python Thread State, Python Gobal Interpreter Lock, etc if interested:
379  if (python::get())
380  {
381  LINFO("Initalizing Python...");
382  jevois::pythonModuleSetEngine(this);
383  }
384 
385  // Instantiate a camera: If device names starts with "/dev/v", assume a hardware camera, otherwise a movie file:
386  std::string const camdev = cameradev::get();
387  if (jevois::stringStartsWith(camdev, "/dev/v"))
388  {
389  LINFO("Starting camera device " << camdev);
390 
391 #ifdef JEVOIS_PLATFORM
392  // Set turbo mode or not:
393  std::ofstream ofs("/sys/module/vfe_v4l2/parameters/turbo");
394  if (ofs.is_open())
395  {
396  if (itsTurbo) ofs << "1" << std::endl; else ofs << "0" << std::endl;
397  ofs.close();
398  }
399  else LERROR("Could not access VFE turbo parameter -- IGNORED");
400 #endif
401 
402  // Now instantiate the camera:
403  itsCamera.reset(new jevois::Camera(camdev, camerasens::get(), cameranbuf::get()));
404 
405 #ifndef JEVOIS_PLATFORM
406  // No need to confuse people with a non-working camreg param:
407  camreg::set(false);
408  camreg::freeze();
409 #endif
410  }
411  else
412  {
413  LINFO("Using movie input " << camdev << " -- issue a 'streamon' to start processing.");
414  itsCamera.reset(new jevois::MovieInput(camdev, cameranbuf::get()));
415 
416  // No need to confuse people with a non-working camreg param:
417  camreg::set(false);
418  camreg::freeze();
419  }
420 
421  // Instantiate a USB gadget: Note: it will want to access the mappings. If the user-selected video mapping has no usb
422  // out, do not instantiate a gadget:
423  int midx = videomapping::get();
424 
425  // The videomapping parameter is now disabled, users should use the 'setmapping' command once running:
426  videomapping::freeze();
427 
428  if (midx >= int(itsMappings.size()))
429  { LERROR("Mapping index " << midx << " out of range -- USING DEFAULT"); midx = -1; }
430 
431  if (midx < 0) midx = itsDefaultMappingIdx;
432 
433  // Always instantiate a gadget even if not used right now, may be used later:
434  std::string const gd = gadgetdev::get();
435  if (gd == "None")
436  {
437  LINFO("Using no USB video output.");
438  // No USB output and no display, useful for benchmarking only:
439  itsGadget.reset(new jevois::VideoOutputNone());
440  itsManualStreamon = true;
441  }
442  else if (jevois::stringStartsWith(gd, "/dev/"))
443  {
444  LINFO("Loading USB video driver " << gd);
445  // USB gadget driver:
446  itsGadget.reset(new jevois::Gadget(gd, itsCamera.get(), this, gadgetnbuf::get(), multicam::get()));
447  }
448  else if (gd.empty() == false)
449  {
450  LINFO("Saving output video to file " << gd);
451  // Non-empty filename, save to file:
452  itsGadget.reset(new jevois::MovieOutput(gd));
453  itsManualStreamon = true;
454  }
455  else
456  {
457  LINFO("Using display for video output");
458  // Local video display, for use on a host desktop:
459  itsGadget.reset(new jevois::VideoDisplay("jevois", gadgetnbuf::get()));
460  itsManualStreamon = true;
461  }
462 
463  // We are ready to run:
464  itsRunning.store(true);
465 
466 #ifndef JEVOIS_PLATFORM
467  // Set initial format when running on host, since we will start streaming immediately:
468  try { setFormatInternal(midx); } catch (...) { jevois::warnAndIgnoreException(); }
469 #endif
470 
471  // Run init script:
472  runScriptFromFile(JEVOIS_ENGINE_INIT_SCRIPT, nullptr, false);
473 }
474 
475 // ####################################################################################################
477 {
478  JEVOIS_TRACE(1);
479 
480  // Turn off stream if it is on:
481  streamOff();
482 
483  // Tell our run() thread to finish up:
484  itsRunning.store(false);
485 
486 #ifdef JEVOIS_PLATFORM
487  // Tell checkMassStorage() thread to finish up:
488  itsCheckingMassStorage.store(false);
489 #endif
490 
491  // Nuke our module as soon as we can, hopefully soon now that we turned off streaming and running:
492  {
495  itsModule.reset();
496 
497  // Gone, nuke the loader now:
498  itsLoader.reset();
499  }
500 
501  // Because we passed the camera as a raw pointer to the gadget, nuke the gadget first and then the camera:
502  itsGadget.reset();
503  itsCamera.reset();
504 
505 #ifdef JEVOIS_PLATFORM
506  // Will block until the checkMassStorage() thread completes:
507  if (itsCheckMassStorageFut.valid())
508  try { itsCheckMassStorageFut.get(); } catch (...) { jevois::warnAndIgnoreException(); }
509 #endif
510 
511  // Things should be quiet now, unhook from the logger (this call is not strictly thread safe):
512  jevois::logSetEngine(nullptr);
513 }
514 
515 // ####################################################################################################
516 #ifdef JEVOIS_PLATFORM
517 void jevois::Engine::checkMassStorage()
518 {
519  itsCheckingMassStorage.store(true);
520 
521  while (itsCheckingMassStorage.load())
522  {
523  // Check from the mass storage gadget (with JeVois extension) whether the virtual USB drive is mounted by the
524  // host. If currently in mass storage mode and the host just ejected the virtual flash drive, resume normal
525  // operation. If not in mass-storage mode and the host mounted it, enter mass-storage mode (may happen if
526  // /boot/usbsdauto was selected):
527  std::ifstream ifs("/sys/devices/platform/sunxi_usb_udc/gadget/lun0/mass_storage_in_use");
528  if (ifs.is_open())
529  {
530  int inuse; ifs >> inuse;
531  if (itsMassStorageMode.load())
532  {
533  if (inuse == 0) stopMassStorageMode();
534  }
535  else
536  {
537  if (inuse) { JEVOIS_TIMED_LOCK(itsMtx); startMassStorageMode(); }
538  }
539  }
540  std::this_thread::sleep_for(std::chrono::milliseconds(500));
541  }
542 }
543 #endif
544 
545 // ####################################################################################################
547 {
548  JEVOIS_TRACE(2);
549 
551  itsCamera->streamOn();
552  itsGadget->streamOn();
553  itsStreaming.store(true);
554 }
555 
556 // ####################################################################################################
558 {
559  JEVOIS_TRACE(2);
560 
561  // First, tell both the camera and gadget to abort streaming, this will make get()/done()/send() throw:
562  itsGadget->abortStream();
563  itsCamera->abortStream();
564 
565  // Stop the main loop, which will flip itsStreaming to false and will make it easier for us to lock itsMtx:
566  LDEBUG("Stopping main loop...");
567  itsStopMainLoop.store(true);
568  while (itsStopMainLoop.load() && itsRunning.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
569  LDEBUG("Main loop stopped.");
570 
571  // Lock up and stream off:
573  itsGadget->streamOff();
574  itsCamera->streamOff();
575 }
576 
577 // ####################################################################################################
579 {
580  JEVOIS_TRACE(2);
581 
582  LDEBUG("Set format number " << idx << " start...");
583 
584  if (idx >= itsMappings.size())
585  LFATAL("Requested mapping index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
586 
588  setFormatInternal(idx);
589  LDEBUG("Set format number " << idx << " done");
590 }
591 
592 // ####################################################################################################
593 void jevois::Engine::setFormatInternal(size_t idx)
594 {
595  // itsMtx should be locked by caller, idx should be valid:
596  JEVOIS_TRACE(2);
597 
598  jevois::VideoMapping const & m = itsMappings[idx];
599  setFormatInternal(m);
600 }
601 
602 // ####################################################################################################
603 void jevois::Engine::setFormatInternal(jevois::VideoMapping const & m, bool reload)
604 {
605  // itsMtx should be locked by caller, idx should be valid:
606  JEVOIS_TRACE(2);
607 
608  LINFO(m.str());
609  itsModuleConstructionError = "Unknown error while starting module " + m.modulename + " ...";
610 
611 #ifdef JEVOIS_PLATFORM
612  if (itsMassStorageMode.load())
613  LFATAL("Cannot setup video streaming while in mass-storage mode. Eject the USB drive on your host computer first.");
614 #endif
615 
616  // Set the format at the camera and gadget levels, unless we are just reloading:
617  if (reload == false)
618  {
619  itsCamera->setFormat(m);
620  itsGadget->setFormat(m);
621  }
622 
623  // Keep track of our current mapping:
624  itsCurrentMapping = m;
625 
626  // Nuke the processing module, if any, so we can also safely nuke the loader. We always nuke the module instance so we
627  // won't have any issues with latent state even if we re-use the same module but possibly with different input
628  // image resolution, etc:
629  if (itsModule)
630  try { removeComponent(itsModule); itsModule.reset(); } catch (...) { jevois::warnAndIgnoreException(); }
631 
632  // Reset our master frame counter on each module load:
633  itsFrame.store(0);
634 
635  // Instantiate the module. If the constructor throws, code is bogus, for example some syntax error in a python module
636  // that is detected at load time. We get the exception's error message for later display into video frames in the main
637  // loop, and mark itsModuleConstructionError:
638  try
639  {
640  // For python modules, we do not need a loader, we just instantiate our special python wrapper module instead:
641  std::string const sopath = m.sopath();
642  if (m.ispython)
643  {
644  if (python::get() == false) LFATAL("Python disabled, delete BOOT:nopython and restart to enable python");
645 
646  // Instantiate the python wrapper:
647  itsLoader.reset();
648  itsModule.reset(new jevois::PythonModule(m));
649  }
650  else
651  {
652  // C++ compiled module. We can re-use the same loader and avoid closing the .so if we will use the same module:
653  if (itsLoader.get() == nullptr || itsLoader->sopath() != sopath)
654  {
655  // Nuke our previous loader and free its resources if needed, then start a new loader:
656  LINFO("Instantiating dynamic loader for " << sopath);
657  itsLoader.reset(new jevois::DynamicLoader(sopath, true));
658  }
659 
660  // Check version match:
661  auto version_major = itsLoader->load<int()>(m.modulename + "_version_major");
662  auto version_minor = itsLoader->load<int()>(m.modulename + "_version_minor");
663  if (version_major() != JEVOIS_VERSION_MAJOR || version_minor() != JEVOIS_VERSION_MINOR)
664  LERROR("Module " << m.modulename << " in file " << sopath << " was build for JeVois v" << version_major() << '.'
665  << version_minor() << ", but running framework is v" << JEVOIS_VERSION_STRING << " -- TRYING ANYWAY");
666 
667  // Instantiate the new module:
668  auto create = itsLoader->load<std::shared_ptr<jevois::Module>(std::string const &)>(m.modulename + "_create");
669  itsModule = create(m.modulename); // Here we just use the class name as instance name
670  }
671 
672  // Add the module as a component to us. Keep this code in sync with Manager::addComponent():
673  {
674  // Lock up so we guarantee the instance name does not get robbed as we add the sub:
675  boost::unique_lock<boost::shared_mutex> ulck(itsSubMtx);
676 
677  // Then add it as a sub-component to us:
678  itsSubComponents.push_back(itsModule);
679  itsModule->itsParent = this;
680  itsModule->setPath(sopath.substr(0, sopath.rfind('/')));
681  }
682 
683  // Bring it to our runstate and load any extra params. NOTE: Keep this in sync with Component::init():
684  if (itsInitialized) itsModule->runPreInit();
685 
686  std::string const paramcfg = itsModule->absolutePath(JEVOIS_MODULE_PARAMS_FILENAME);
687  std::ifstream ifs(paramcfg); if (ifs.is_open()) itsModule->setParamsFromStream(ifs, paramcfg);
688 
689  if (itsInitialized) { itsModule->setInitialized(); itsModule->runPostInit(); }
690 
691  // And finally run any config script, sending any errors to USB (likely JeVois Inventor):
692  std::shared_ptr<jevois::UserInterface> ser;
693  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::USB) { ser = s; break; }
694  runScriptFromFile(itsModule->absolutePath(JEVOIS_MODULE_SCRIPT_FILENAME), ser, false);
695 
696  LINFO("Module [" << m.modulename << "] loaded, initialized, and ready.");
697  itsModuleConstructionError.clear();
698  }
699  catch (...)
700  {
701  // Note: we do not nuke the module here, as the Inventor may need its path to fix some config files.
702  itsModuleConstructionError = jevois::warnAndIgnoreException();
703  LERROR("Module [" << m.modulename << "] startup error and not operational.");
704  }
705 }
706 
707 // ####################################################################################################
709 {
710  JEVOIS_TRACE(2);
711 
712  std::string pfx; // optional command prefix
713 
714  // Announce that we are ready to the hardware serial port, if any. Do not use sendSerial() here so we always issue
715  // this message irrespectively of the user serial preferences:
716  for (auto & s : itsSerials)
717  if (s->instanceName() == "serial")
718  try { s->writeString("INF READY JEVOIS " JEVOIS_VERSION_STRING); }
719  catch (...) { jevois::warnAndIgnoreException(); }
720 
721  while (itsRunning.load())
722  {
723  bool dosleep = true;
724 
725  if (itsStreaming.load())
726  {
727  // Lock up while we use the module:
729 
730  if (itsModuleConstructionError.empty() == false)
731  {
732  // If we have a module construction error, render it to an image and stream it over USB now (if we are doing USB
733  // and we want to see errors in the video stream):
734  if (itsCurrentMapping.ofmt && itsVideoErrors.load())
735  try
736  {
737  // Get an output image, draw error message, and send to host:
738  itsGadget->get(itsVideoErrorImage);
739  jevois::drawErrorImage(itsModuleConstructionError, itsVideoErrorImage);
740  itsGadget->send(itsVideoErrorImage);
741 
742  // Also get one camera frame to avoid accumulation of stale buffers:
743  (void)jevois::InputFrame(itsCamera, itsTurbo).get();
744  }
745  catch (...) { jevois::warnAndIgnoreException(); }
746  }
747  else if (itsModule)
748  {
749  // For standard modules, indicate frame start mark if user wants it:
750  jevois::StdModule * stdmod = dynamic_cast<jevois::StdModule *>(itsModule.get());
751  if (stdmod) stdmod->sendSerialMarkStart();
752 
753  // We have a module ready for action. Call its process function and handle any exceptions:
754  try
755  {
756  if (itsCurrentMapping.ofmt) // Process with USB outputs:
757  itsModule->process(jevois::InputFrame(itsCamera, itsTurbo),
758  jevois::OutputFrame(itsGadget, itsVideoErrors.load() ? &itsVideoErrorImage : nullptr));
759  else // Process with no USB outputs:
760  itsModule->process(jevois::InputFrame(itsCamera, itsTurbo));
761  dosleep = false;
762  }
763  catch (...)
764  {
765  // Report exceptions to video if desired: We have to be extra careful here because the exception might have
766  // been called by the input frame (camera not streaming) or the output frame (gadget not streaming), in
767  // addition to exceptions thrown by the module:
768  if (itsCurrentMapping.ofmt && itsVideoErrors.load())
769  {
770  try
771  {
772  // If the module threw before get() or after send() on the output frame, get a buffer from the gadget:
773  if (itsVideoErrorImage.valid() == false)
774  itsGadget->get(itsVideoErrorImage); // could throw when streamoff
775 
776  // Report module exception to serlog and get it back as a string:
777  std::string errstr = jevois::warnAndIgnoreException();
778 
779  // Draw the error message into our video frame:
780  jevois::drawErrorImage(errstr, itsVideoErrorImage);
781  }
782  catch (...) { jevois::warnAndIgnoreException(); }
783 
784  try
785  {
786  // Send the error image over USB:
787  if (itsVideoErrorImage.valid()) itsGadget->send(itsVideoErrorImage); // could throw if gadget stream off
788  }
789  catch (...) { jevois::warnAndIgnoreException(); }
790 
791  // Invalidate the error image so it is clean for the next frame:
792  itsVideoErrorImage.invalidate();
793  }
794  else
795  {
796  // Report module exception to serlog, and ignore:
798  }
799  }
800 
801  // For standard modules, indicate frame start stop if user wants it:
802  if (stdmod) stdmod->sendSerialMarkStop();
803 
804  // Increment our master frame counter
805  ++ itsFrame;
806  itsNumSerialSent.store(0);
807  }
808  }
809 
810  if (itsStopMainLoop.load())
811  {
812  itsStreaming.store(false);
813  LDEBUG("-- Main loop stopped --");
814  itsStopMainLoop.store(false);
815  }
816 
817  if (dosleep)
818  {
819  LDEBUG("No processing module loaded or not streaming... Sleeping...");
820  std::this_thread::sleep_for(std::chrono::milliseconds(50));
821  }
822 
823  // Serial input handling. Note that readSome() and writeString() on the serial could throw. The code below is
824  // organized to catch all other exceptions, except for those, which are caught here at the first try level:
825  for (auto & s : itsSerials)
826  {
827  try
828  {
829  std::string str; bool parsed = false; bool success = false;
830 
831  if (s->readSome(str))
832  {
834 
835 
836  // If the command starts with our hidden command prefix, set the prefix, otherwise clear it:
837  if (jevois::stringStartsWith(str, JEVOIS_JVINV_PREFIX))
838  {
839  pfx = JEVOIS_JVINV_PREFIX;
840  str = str.substr(pfx.length());
841  }
842  else pfx.clear();
843 
844  // Try to execute this command. If the command is for us (e.g., set a parameter) and is correct,
845  // parseCommand() will return true; if it is for us but buggy, it will throw. If it is not recognized by us,
846  // it will return false and we should try sending it to the Module:
847  try { parsed = parseCommand(str, s, pfx); success = parsed; }
848  catch (std::exception const & e)
849  { s->writeString(pfx, std::string("ERR ") + e.what()); parsed = true; }
850  catch (...)
851  { s->writeString(pfx, "ERR Unknown error"); parsed = true; }
852 
853  if (parsed == false)
854  {
855  if (itsModule)
856  {
857  // Note: prefixing is currently not supported for modules, it is for the Engine only
858  try { itsModule->parseSerial(str, s); success = true; }
859  catch (std::exception const & me) { s->writeString(pfx, std::string("ERR ") + me.what()); }
860  catch (...) { s->writeString(pfx, "ERR Command [" + str + "] not recognized by Engine or Module"); }
861  }
862  else s->writeString(pfx, "ERR Unsupported command [" + str + "] and no module");
863  }
864 
865  // If success, let user know:
866  if (success && quietcmd::get() == false) s->writeString(pfx, "OK");
867  }
868  }
869  catch (...) { jevois::warnAndIgnoreException(); }
870  }
871  }
872 }
873 
874 // ####################################################################################################
875 void jevois::Engine::sendSerial(std::string const & str, bool islog)
876 {
877  // If not a log message, we may want to limit the number of serout messages that a module sends on each frame:
878  size_t slim = serlimit::get();
879  if (islog == false && slim)
880  {
881  if (itsNumSerialSent.load() >= slim) return; // limit reached, message dropped
882  ++itsNumSerialSent; // increment number of messages sent. It is reset in the main loop on each new frame.
883  }
884 
885  // Decide where to send this message based on the value of islog:
886  jevois::engine::SerPort p = islog ? serlog::get() : serout::get();
887 
888  switch (p)
889  {
890  case jevois::engine::SerPort::None:
891  break; // Nothing to send
892 
893  case jevois::engine::SerPort::All:
894  for (auto & s : itsSerials)
895  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
896  break;
897 
898  case jevois::engine::SerPort::Hard:
899  for (auto & s : itsSerials)
900  if (s->type() == jevois::UserInterface::Type::Hard)
901  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
902  break;
903 
904  case jevois::engine::SerPort::USB:
905  for (auto & s : itsSerials)
906  if (s->type() == jevois::UserInterface::Type::USB)
907  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
908  break;
909  }
910 }
911 
912 // ####################################################################################################
914 { return itsFrame; }
915 
916 // ####################################################################################################
917 void jevois::Engine::writeCamRegister(unsigned short reg, unsigned short val)
918 {
919  itsCamera->writeRegister(reg, val);
920 }
921 // ####################################################################################################
922 unsigned short jevois::Engine::readCamRegister(unsigned short reg)
923 {
924  return itsCamera->readRegister(reg);
925 }
926 
927 // ####################################################################################################
928 void jevois::Engine::writeIMUregister(unsigned short reg, unsigned short val)
929 {
930  itsCamera->writeIMUregister(reg, val);
931 }
932 
933 // ####################################################################################################
934 unsigned short jevois::Engine::readIMUregister(unsigned short reg)
935 {
936  return itsCamera->readIMUregister(reg);
937 }
938 
939 // ####################################################################################################
940 void jevois::Engine::writeIMUregisterArray(unsigned short reg, unsigned char const * vals, size_t num)
941 {
942  itsCamera->writeIMUregisterArray(reg, vals, num);
943 }
944 
945 // ####################################################################################################
946 void jevois::Engine::readIMUregisterArray(unsigned short reg, unsigned char * vals, size_t num)
947 {
948  itsCamera->readIMUregisterArray(reg, vals, num);
949 }
950 
951 // ####################################################################################################
953 {
954  return itsCurrentMapping;
955 }
956 
957 // ####################################################################################################
959 {
960  return itsMappings.size();
961 }
962 
963 // ####################################################################################################
965 {
966  if (idx >= itsMappings.size())
967  LFATAL("Index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
968 
969  return itsMappings[idx];
970 }
971 
972 // ####################################################################################################
973 size_t jevois::Engine::getVideoMappingIdx(unsigned int iformat, unsigned int iframe, unsigned int interval) const
974 {
975  // If the iformat or iframe is zero, that's probably a probe for the default mode, so return it:
976  if (iformat == 0 || iframe == 0) return itsDefaultMappingIdx;
977 
978  // If interval is zero, probably a driver trying to probe for our default interval, so return the first available one;
979  // otherwise try to find the desired interval and return the corresponding mapping:
980  if (interval)
981  {
982  float const fps = jevois::VideoMapping::uvcToFps(interval);
983  size_t idx = 0;
984 
985  for (jevois::VideoMapping const & m : itsMappings)
986  if (m.uvcformat == iformat && m.uvcframe == iframe && std::fabs(m.ofps - fps) < 0.1F) return idx;
987  else ++idx;
988 
989  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
990  }
991  else
992  {
993  size_t idx = 0;
994 
995  for (jevois::VideoMapping const & m : itsMappings)
996  if (m.uvcformat == iformat && m.uvcframe == iframe) return idx;
997  else ++idx;
998 
999  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
1000  }
1001 }
1002 
1003 // ####################################################################################################
1005 { return itsMappings[itsDefaultMappingIdx]; }
1006 
1007 // ####################################################################################################
1009 { return itsDefaultMappingIdx; }
1010 
1011 // ####################################################################################################
1012 jevois::VideoMapping const &
1013 jevois::Engine::findVideoMapping(unsigned int oformat, unsigned int owidth, unsigned int oheight,
1014  float oframespersec) const
1015 {
1016  for (jevois::VideoMapping const & m : itsMappings)
1017  if (m.match(oformat, owidth, oheight, oframespersec)) return m;
1018 
1019  LFATAL("Could not find mapping for output format " << jevois::fccstr(oformat) << ' ' <<
1020  owidth << 'x' << oheight << " @ " << oframespersec << " fps");
1021 }
1022 
1023 // ####################################################################################################
1024 std::string jevois::Engine::camctrlname(int id, char const * longname) const
1025 {
1026  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
1027  if (camcontrols[i].id == id) return camcontrols[i].shortname;
1028 
1029  // Darn, this control is not in our list, probably something exotic. Compute a name from the control's long name:
1030  return abbreviate(longname);
1031 }
1032 
1033 // ####################################################################################################
1034 int jevois::Engine::camctrlid(std::string const & shortname)
1035 {
1036  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
1037  if (shortname.compare(camcontrols[i].shortname) == 0) return camcontrols[i].id;
1038 
1039  // Not in our list, all right, let's find it then in the camera:
1040  struct v4l2_queryctrl qc = { };
1041  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
1042  {
1043  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
1044  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
1045  // doneids set to keep track of the ones already reported:
1046  qc.id = cls | 0x900;
1047  while (true)
1048  {
1049  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; unsigned int old_id = qc.id; bool failed = false;
1050  try
1051  {
1052  itsCamera->queryControl(qc);
1053  if (abbreviate(reinterpret_cast<char const *>(qc.name)) == shortname) return qc.id;
1054  }
1055  catch (...) { failed = true; }
1056 
1057  // With V4L2_CTRL_FLAG_NEXT_CTRL, the camera kernel driver is supposed to pass down the next valid control if
1058  // the requested one is not found, but some drivers do not honor that, so let's move on to the next control
1059  // manually if needed:
1060  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
1061  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
1062  else if (failed) break;
1063  }
1064  }
1065 
1066  LFATAL("Could not find control [" << shortname << "] in the camera");
1067 }
1068 
1069 // ####################################################################################################
1070 std::string jevois::Engine::camCtrlHelp(struct v4l2_queryctrl & qc, std::set<int> & doneids)
1071 {
1072  // See if we have this control:
1073  itsCamera->queryControl(qc);
1074  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
1075 
1076  // If we have already done this control, just return an empty string:
1077  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
1078 
1079  // Control exists, let's also get its current value:
1080  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
1081  itsCamera->getControl(ctrl);
1082 
1083  // Print out some description depending on control type:
1084  std::ostringstream ss;
1085  ss << "- " << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
1086 
1087  switch (qc.type)
1088  {
1089  case V4L2_CTRL_TYPE_INTEGER:
1090  ss << " [int] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
1091  << " def=" << qc.default_value << " curr=" << ctrl.value;
1092  break;
1093 
1094  //case V4L2_CTRL_TYPE_INTEGER64:
1095  //ss << " [int64] value=" << ctrl.value64;
1096  //break;
1097 
1098  //case V4L2_CTRL_TYPE_STRING:
1099  //ss << " [str] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
1100  // << " curr=" << ctrl.string;
1101  //break;
1102 
1103  case V4L2_CTRL_TYPE_BOOLEAN:
1104  ss << " [bool] default=" << qc.default_value << " curr=" << ctrl.value;
1105  break;
1106 
1107  // This one is not supported by the older kernel on platform:
1108  //case V4L2_CTRL_TYPE_INTEGER_MENU:
1109  //ss << " [intmenu] min=" << qc.minimum << " max=" << qc.maximum
1110  // << " def=" << qc.default_value << " curr=" << ctrl.value;
1111  //break;
1112 
1113  case V4L2_CTRL_TYPE_BUTTON:
1114  ss << " [button]";
1115  break;
1116 
1117  case V4L2_CTRL_TYPE_BITMASK:
1118  ss << " [bitmask] max=" << qc.maximum << " def=" << qc.default_value << " curr=" << ctrl.value;
1119  break;
1120 
1121  case V4L2_CTRL_TYPE_MENU:
1122  {
1123  struct v4l2_querymenu querymenu = { };
1124  querymenu.id = qc.id;
1125  ss << " [menu] values ";
1126  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
1127  {
1128  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
1129  ss << querymenu.index << ':' << querymenu.name << ' ';
1130  }
1131  ss << "curr=" << ctrl.value;
1132  }
1133  break;
1134 
1135  default:
1136  ss << "[unknown type]";
1137  }
1138 
1139  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " [DISABLED]";
1140 
1141  return ss.str();
1142 }
1143 
1144 // ####################################################################################################
1145 std::string jevois::Engine::camCtrlInfo(struct v4l2_queryctrl & qc, std::set<int> & doneids)
1146 {
1147  // See if we have this control:
1148  itsCamera->queryControl(qc);
1149  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
1150 
1151  // If we have already done this control, just return an empty string:
1152  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
1153 
1154  // Control exists, let's also get its current value:
1155  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
1156  itsCamera->getControl(ctrl);
1157 
1158  // Print out some description depending on control type:
1159  std::ostringstream ss;
1160  ss << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
1161 
1162  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " D ";
1163 
1164  switch (qc.type)
1165  {
1166  case V4L2_CTRL_TYPE_INTEGER:
1167  ss << " I " << qc.minimum << ' ' << qc.maximum << ' ' << qc.step
1168  << ' ' << qc.default_value << ' ' << ctrl.value;
1169  break;
1170 
1171  //case V4L2_CTRL_TYPE_INTEGER64:
1172  //ss << " J " << ctrl.value64;
1173  //break;
1174 
1175  //case V4L2_CTRL_TYPE_STRING:
1176  //ss << " S " << qc.minimum << ' ' << qc.maximum << ' ' << qc.step << ' ' << ctrl.string;
1177  //break;
1178 
1179  case V4L2_CTRL_TYPE_BOOLEAN:
1180  ss << " B " << qc.default_value << ' ' << ctrl.value;
1181  break;
1182 
1183  // This one is not supported by the older kernel on platform:
1184  //case V4L2_CTRL_TYPE_INTEGER_MENU:
1185  //ss << " N " << qc.minimum << ' ' << qc.maximum << ' ' << qc.default_value << ' ' << ctrl.value;
1186  //break;
1187 
1188  case V4L2_CTRL_TYPE_BUTTON:
1189  ss << " U";
1190  break;
1191 
1192  case V4L2_CTRL_TYPE_BITMASK:
1193  ss << " K " << qc.maximum << ' ' << qc.default_value << ' ' << ctrl.value;
1194  break;
1195 
1196  case V4L2_CTRL_TYPE_MENU:
1197  {
1198  struct v4l2_querymenu querymenu = { };
1199  querymenu.id = qc.id;
1200  ss << " M " << qc.default_value << ' ' << ctrl.value;
1201  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
1202  {
1203  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
1204  ss << ' ' << querymenu.index << ':' << querymenu.name << ' ';
1205  }
1206  }
1207  break;
1208 
1209  default:
1210  ss << 'X';
1211  }
1212 
1213  return ss.str();
1214 }
1215 
1216 #ifdef JEVOIS_PLATFORM
1217 // ####################################################################################################
1218 void jevois::Engine::startMassStorageMode()
1219 {
1220  // itsMtx must be locked by caller
1221 
1222  if (itsMassStorageMode.load()) { LERROR("Already in mass-storage mode -- IGNORED"); return; }
1223 
1224  // Nuke any module and loader so we have nothing loaded that uses /jevois:
1225  if (itsModule) { removeComponent(itsModule); itsModule.reset(); }
1226  if (itsLoader) itsLoader.reset();
1227 
1228  // Unmount /jevois:
1229  if (std::system("sync")) LERROR("Disk sync failed -- IGNORED");
1230  if (std::system("mount -o remount,ro /jevois")) LERROR("Failed to remount /jevois read-only -- IGNORED");
1231 
1232  // Now set the backing partition in mass-storage gadget:
1233  std::ofstream ofs(JEVOIS_USBSD_SYS);
1234  if (ofs.is_open() == false) LFATAL("Cannot setup mass-storage backing file to " << JEVOIS_USBSD_SYS);
1235  ofs << JEVOIS_USBSD_FILE << std::endl;
1236 
1237  LINFO("Exported JEVOIS partition of microSD to host computer as virtual flash drive.");
1238  itsMassStorageMode.store(true);
1239 }
1240 
1241 // ####################################################################################################
1242 void jevois::Engine::stopMassStorageMode()
1243 {
1244  //itsMassStorageMode.store(false);
1245  LINFO("JeVois virtual USB drive ejected by host -- REBOOTING");
1246  reboot();
1247 }
1248 
1249 // ####################################################################################################
1250 void jevois::Engine::reboot()
1251 {
1252  if (std::system("sync")) LERROR("Disk sync failed -- IGNORED");
1253  itsCheckingMassStorage.store(false);
1254  itsRunning.store(false);
1255 
1256  // Hard reset to avoid possible hanging during module unload, etc:
1257  if ( ! std::ofstream("/proc/sys/kernel/sysrq").put('1')) LERROR("Cannot trigger hard reset -- please unplug me!");
1258  if ( ! std::ofstream("/proc/sysrq-trigger").put('s')) LERROR("Cannot trigger hard reset -- please unplug me!");
1259  if ( ! std::ofstream("/proc/sysrq-trigger").put('b')) LERROR("Cannot trigger hard reset -- please unplug me!");
1260  std::terminate();
1261 }
1262 #endif
1263 
1264 // ####################################################################################################
1265 void jevois::Engine::cmdInfo(std::shared_ptr<UserInterface> s, bool showAll, std::string const & pfx)
1266 {
1267  s->writeString(pfx, "help - print this help message");
1268  s->writeString(pfx, "help2 - print compact help message about current vision module only");
1269  s->writeString(pfx, "info - show system information including CPU speed, load and temperature");
1270  s->writeString(pfx, "setpar <name> <value> - set a parameter value");
1271  s->writeString(pfx, "getpar <name> - get a parameter value(s)");
1272  s->writeString(pfx, "runscript <filename> - run script commands in specified file");
1273  s->writeString(pfx, "setcam <ctrl> <val> - set camera control <ctrl> to value <val>");
1274  s->writeString(pfx, "getcam <ctrl> - get value of camera control <ctrl>");
1275 
1276  if (showAll || camreg::get())
1277  {
1278  s->writeString(pfx, "setcamreg <reg> <val> - set raw camera register <reg> to value <val>");
1279  s->writeString(pfx, "getcamreg <reg> - get value of raw camera register <reg>");
1280  s->writeString(pfx, "setimureg <reg> <val> - set raw IMU register <reg> to value <val>");
1281  s->writeString(pfx, "getimureg <reg> - get value of raw IMU register <reg>");
1282  s->writeString(pfx, "setimuregs <reg> <num> <val1> ... <valn> - set array of raw IMU register values");
1283  s->writeString(pfx, "getimuregs <reg> <num> - get array of raw IMU register values");
1284  }
1285 
1286  s->writeString(pfx, "listmappings - list all available video mappings");
1287  s->writeString(pfx, "setmapping <num> - select video mapping <num>, only possible while not streaming");
1288  s->writeString(pfx, "setmapping2 <CAMmode> <CAMwidth> <CAMheight> <CAMfps> <Vendor> <Module> - set no-USB-out "
1289  "video mapping defined on the fly, while not streaming");
1290  s->writeString(pfx, "reload - reload and reset the current module");
1291 
1292  if (showAll || itsCurrentMapping.ofmt == 0 || itsManualStreamon)
1293  {
1294  s->writeString(pfx, "streamon - start camera video streaming");
1295  s->writeString(pfx, "streamoff - stop camera video streaming");
1296  }
1297 
1298  s->writeString(pfx, "ping - returns 'ALIVE'");
1299  s->writeString(pfx, "serlog <string> - forward string to the serial port(s) specified by the serlog parameter");
1300  s->writeString(pfx, "serout <string> - forward string to the serial port(s) specified by the serout parameter");
1301 
1302  if (showAll)
1303  {
1304  // Hide machine-oriented commands by default
1305  s->writeString(pfx, "caminfo - returns machine-readable info about camera parameters");
1306  s->writeString(pfx, "cmdinfo [all] - returns machine-readable info about Engine commands");
1307  s->writeString(pfx, "modcmdinfo - returns machine-readable info about Module commands");
1308  s->writeString(pfx, "paraminfo [hot|mod|modhot] - returns machine-readable info about parameters");
1309  s->writeString(pfx, "serinfo - returns machine-readable info about serial settings (serout serlog serstyle serprec serstamp)");
1310  s->writeString(pfx, "fileget <filepath> - get a file from JeVois to the host. Use with caution!");
1311  s->writeString(pfx, "fileput <filepath> - put a file from the host to JeVois. Use with caution!");
1312  }
1313 
1314 #ifdef JEVOIS_PLATFORM
1315  s->writeString(pfx, "usbsd - export the JEVOIS partition of the microSD card as a virtual USB drive");
1316 #endif
1317  s->writeString(pfx, "sync - commit any pending data write to microSD");
1318  s->writeString(pfx, "date [date and time] - get or set the system date and time");
1319 
1320  s->writeString(pfx, "shell <string> - execute <string> as a shell command. Use with caution!");
1321 
1322 #ifdef JEVOIS_PLATFORM
1323  s->writeString(pfx, "restart - restart the JeVois smart camera");
1324 #else
1325  s->writeString(pfx, "quit - quit this program");
1326 #endif
1327 }
1328 
1329 // ####################################################################################################
1330 void jevois::Engine::modCmdInfo(std::shared_ptr<UserInterface> s, std::string const & pfx)
1331 {
1332  if (itsModule)
1333  {
1334  std::stringstream css; itsModule->supportedCommands(css);
1335  for (std::string line; std::getline(css, line); /* */) s->writeString(pfx, line);
1336  }
1337 }
1338 
1339 // ####################################################################################################
1340 bool jevois::Engine::parseCommand(std::string const & str, std::shared_ptr<UserInterface> s, std::string const & pfx)
1341 {
1342  // itsMtx should be locked by caller
1343 
1344  std::string errmsg;
1345 
1346  // Note: ModemManager on Ubuntu sends this on startup, kill ModemManager to avoid:
1347  // 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
1348  //
1349  // AT^SQPORT?
1350  // AT
1351  // AT
1352  // AT
1353  // ~
1354  //
1355  // then later on it insists on trying to mess with us, issuing things like AT, AT+CGMI, AT+GMI, AT+CGMM, AT+GMM,
1356  // AT%IPSYS?, ATE0, ATV1, etc etc
1357 
1358  switch (str.length())
1359  {
1360  case 0:
1361  LDEBUG("Ignoring empty string"); return true;
1362  break;
1363 
1364  case 1:
1365  if (str[0] == '~') { LDEBUG("Ignoring modem config command [~]"); return true; }
1366 
1367  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
1368  // from the arduino to be printed out to the user:
1369  if (str[0] == '#') { sendSerial(str, true); return true; }
1370  break;
1371 
1372  default: // length is 2 or more:
1373 
1374  // Ignore any command that starts with a '~':
1375  if (str[0] == '~') { LDEBUG("Ignoring modem config command [" << str << ']'); return true; }
1376 
1377  // Ignore any command that starts with "AT":
1378  if (str[0] == 'A' && str[1] == 'T') { LDEBUG("Ignoring AT command [" << str <<']'); return true; }
1379 
1380  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
1381  // in the arduino to be printed out to the user:
1382  if (str[0] == '#') { sendSerial(str, true); return true; }
1383 
1384  // Get the first word, i.e., the command:
1385  size_t const idx = str.find(' '); std::string cmd, rem;
1386  if (idx == str.npos) cmd = str; else { cmd = str.substr(0, idx); if (idx < str.length()) rem = str.substr(idx+1); }
1387 
1388  // ----------------------------------------------------------------------------------------------------
1389  if (cmd == "help")
1390  {
1391  // Show all commands, first ours, as supported below:
1392  s->writeString(pfx, "GENERAL COMMANDS:");
1393  s->writeString(pfx, "");
1394  cmdInfo(s, false, pfx);
1395  s->writeString(pfx, "");
1396 
1397  // Then the module's custom commands, if any:
1398  if (itsModule)
1399  {
1400  s->writeString(pfx, "MODULE-SPECIFIC COMMANDS:");
1401  s->writeString(pfx, "");
1402  modCmdInfo(s, pfx);
1403  s->writeString(pfx, "");
1404  }
1405 
1406  // Get the help message for our parameters and write it out line by line so the serial fixes the line endings:
1407  std::stringstream pss; constructHelpMessage(pss);
1408  for (std::string line; std::getline(pss, line); /* */) s->writeString(pfx, line);
1409 
1410  // Show all camera controls
1411  s->writeString(pfx, "AVAILABLE CAMERA CONTROLS:");
1412  s->writeString(pfx, "");
1413 
1414  struct v4l2_queryctrl qc = { }; std::set<int> doneids;
1415  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
1416  {
1417  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
1418  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
1419  // doneids set to keep track of the ones already reported:
1420  qc.id = cls | 0x900; unsigned int old_id;
1421  while (true)
1422  {
1423  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; old_id = qc.id; bool failed = false;
1424  try { std::string hlp = camCtrlHelp(qc, doneids); if (hlp.empty() == false) s->writeString(pfx, hlp); }
1425  catch (...) { failed = true; }
1426 
1427  // The camera kernel driver is supposed to pass down the next valid control if the requested one is not
1428  // found, but some drivers do not honor that, so let's move on to the next control manually if needed:
1429  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
1430  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
1431  else if (failed) break;
1432  }
1433  }
1434 
1435  return true;
1436  }
1437 
1438  // ----------------------------------------------------------------------------------------------------
1439  if (cmd == "caminfo")
1440  {
1441  // Machine-readable list of camera parameters:
1442  struct v4l2_queryctrl qc = { }; std::set<int> doneids;
1443  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
1444  {
1445  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
1446  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
1447  // doneids set to keep track of the ones already reported:
1448  qc.id = cls | 0x900; unsigned int old_id;
1449  while (true)
1450  {
1451  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; old_id = qc.id; bool failed = false;
1452  try { std::string hlp = camCtrlInfo(qc, doneids); if (hlp.empty() == false) s->writeString(pfx, hlp); }
1453  catch (...) { failed = true; }
1454 
1455  // The camera kernel driver is supposed to pass down the next valid control if the requested one is not
1456  // found, but some drivers do not honor that, so let's move on to the next control manually if needed:
1457  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
1458  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
1459  else if (failed) break;
1460  }
1461  }
1462 
1463  return true;
1464  }
1465 
1466  // ----------------------------------------------------------------------------------------------------
1467  if (cmd == "cmdinfo")
1468  {
1469  bool showAll = (rem == "all") ? true : false;
1470  cmdInfo(s, showAll, pfx);
1471  return true;
1472  }
1473 
1474  // ----------------------------------------------------------------------------------------------------
1475  if (cmd == "modcmdinfo")
1476  {
1477  modCmdInfo(s, pfx);
1478  return true;
1479  }
1480 
1481  // ----------------------------------------------------------------------------------------------------
1482  if (cmd == "paraminfo")
1483  {
1484  std::map<std::string, std::string> categs;
1485  bool skipFrozen = (rem == "hot" || rem == "modhot") ? true : false;
1486 
1487  if (rem == "mod" || rem == "modhot")
1488  {
1489  // Report only on our module's parameter, if any:
1490  if (itsModule) itsModule->paramInfo(s, categs, skipFrozen, instanceName(), pfx);
1491  }
1492  else
1493  {
1494  // Report on all parameters:
1495  paramInfo(s, categs, skipFrozen, "", pfx);
1496  }
1497 
1498  return true;
1499  }
1500 
1501  // ----------------------------------------------------------------------------------------------------
1502  if (cmd == "serinfo")
1503  {
1504  std::string info = getParamStringUnique("serout") + ' ' + getParamStringUnique("serlog");
1505  if (auto mod = dynamic_cast<jevois::StdModule *>(itsModule.get()))
1506  info += ' ' + mod->getParamStringUnique("serstyle") + ' ' + mod->getParamStringUnique("serprec") +
1507  ' ' + mod->getParamStringUnique("serstamp");
1508  else info += " - - -";
1509 
1510  s->writeString(pfx, info);
1511 
1512  return true;
1513  }
1514 
1515  // ----------------------------------------------------------------------------------------------------
1516  if (cmd == "help2")
1517  {
1518  if (itsModule)
1519  {
1520  // Start with the module's commands:
1521  std::stringstream css; itsModule->supportedCommands(css);
1522  s->writeString(pfx, "MODULE-SPECIFIC COMMANDS:");
1523  s->writeString(pfx, "");
1524  for (std::string line; std::getline(css, line); /* */) s->writeString(pfx, line);
1525  s->writeString(pfx, "");
1526 
1527  // Now the parameters for that module (and its subs) only:
1528  s->writeString(pfx, "MODULE PARAMETERS:");
1529  s->writeString(pfx, "");
1530 
1531  // Keep this in sync with Manager::constructHelpMessage():
1532  std::unordered_map<std::string, // category:description
1533  std::unordered_map<std::string, // --name (type) default=[def]
1534  std::vector<std::pair<std::string, // component name
1535  std::string // current param value
1536  > > > > helplist;
1537  itsModule->populateHelpMessage("", helplist);
1538 
1539  if (helplist.empty())
1540  s->writeString(pfx, "None.");
1541  else
1542  {
1543  for (auto const & c : helplist)
1544  {
1545  // Print out the category name and description
1546  s->writeString(pfx, c.first);
1547 
1548  // Print out the parameter details
1549  for (auto const & n : c.second)
1550  {
1551  std::vector<std::string> tok = jevois::split(n.first, "[\\r\\n]+");
1552  bool first = true;
1553  for (auto const & t : tok)
1554  {
1555  // Add current value info to the first thing we write (which is name, default, etc)
1556  if (first)
1557  {
1558  auto const & v = n.second;
1559  if (v.size() == 1) // only one component using this param
1560  {
1561  if (v[0].second.empty())
1562  s->writeString(pfx, t); // only one comp, and using default val
1563  else
1564  s->writeString(pfx, t + " current=[" + v[0].second + ']'); // using non-default val
1565  }
1566  else if (v.size() > 1) // several components using this param with possibly different values
1567  {
1568  std::string sss = t + " current=";
1569  for (auto const & pp : v)
1570  if (pp.second.empty() == false) sss += '[' + pp.first + ':' + pp.second + "] ";
1571  s->writeString(pfx, sss);
1572  }
1573  else s->writeString(pfx, t); // no non-default value(s) to report
1574 
1575  first = false;
1576  }
1577 
1578  else // just write out the other lines (param description)
1579  s->writeString(pfx, t);
1580  }
1581  }
1582  s->writeString(pfx, "");
1583  }
1584  }
1585  }
1586  else
1587  s->writeString(pfx, "No module loaded.");
1588 
1589  return true;
1590  }
1591 
1592  // ----------------------------------------------------------------------------------------------------
1593  if (cmd == "info")
1594  {
1595  s->writeString(pfx, "INFO: JeVois " JEVOIS_VERSION_STRING);
1596  s->writeString(pfx, "INFO: " + jevois::getSysInfoVersion());
1597  s->writeString(pfx, "INFO: " + jevois::getSysInfoCPU());
1598  s->writeString(pfx, "INFO: " + jevois::getSysInfoMem());
1599  if (itsModule) s->writeString(pfx, "INFO: " + itsCurrentMapping.str());
1600  else s->writeString(pfx, "INFO: " + jevois::VideoMapping().str());
1601  return true;
1602  }
1603 
1604  // ----------------------------------------------------------------------------------------------------
1605  if (cmd == "setpar")
1606  {
1607  size_t const remidx = rem.find(' ');
1608  if (remidx != rem.npos)
1609  {
1610  std::string const desc = rem.substr(0, remidx);
1611  if (remidx < rem.length())
1612  {
1613  std::string const val = rem.substr(remidx+1);
1614  setParamString(desc, val);
1615  return true;
1616  }
1617  }
1618  errmsg = "Need to provide a parameter name and a parameter value in setpar";
1619  }
1620 
1621  // ----------------------------------------------------------------------------------------------------
1622  if (cmd == "getpar")
1623  {
1624  auto vec = getParamString(rem);
1625  for (auto const & p : vec) s->writeString(pfx, p.first + ' ' + p.second);
1626  return true;
1627  }
1628 
1629  // ----------------------------------------------------------------------------------------------------
1630  if (cmd == "setcam")
1631  {
1632  std::istringstream ss(rem); std::string ctrl; int val; ss >> ctrl >> val;
1633  struct v4l2_control c = { }; c.id = camctrlid(ctrl); c.value = val;
1634  itsCamera->setControl(c);
1635  return true;
1636  }
1637 
1638  // ----------------------------------------------------------------------------------------------------
1639  if (cmd == "getcam")
1640  {
1641  struct v4l2_control c = { }; c.id = camctrlid(rem);
1642  itsCamera->getControl(c);
1643  s->writeString(pfx, rem + ' ' + std::to_string(c.value));
1644  return true;
1645  }
1646 
1647  // ----------------------------------------------------------------------------------------------------
1648  if (cmd == "setcamreg")
1649  {
1650  if (camreg::get())
1651  {
1652  // Read register and value as strings, then std::stoi to convert to int, supports 0x (and 0 for octal, caution)
1653  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
1654  itsCamera->writeRegister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
1655  return true;
1656  }
1657  errmsg = "Access to camera registers is disabled, enable with: setpar camreg true";
1658  }
1659 
1660  // ----------------------------------------------------------------------------------------------------
1661  if (cmd == "getcamreg")
1662  {
1663  if (camreg::get())
1664  {
1665  unsigned int val = itsCamera->readRegister(std::stoi(rem, nullptr, 0));
1666  std::ostringstream os; os << std::hex << val;
1667  s->writeString(pfx, os.str());
1668  return true;
1669  }
1670  errmsg = "Access to camera registers is disabled, enable with: setpar camreg true";
1671  }
1672 
1673  // ----------------------------------------------------------------------------------------------------
1674  if (cmd == "setimureg")
1675  {
1676  if (camreg::get())
1677  {
1678  // Read register and value as strings, then std::stoi to convert to int, supports 0x (and 0 for octal, caution)
1679  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
1680  itsCamera->writeIMUregister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
1681  return true;
1682  }
1683  errmsg = "Access to camera's IMU registers is disabled, enable with: setpar camreg true";
1684  }
1685 
1686  // ----------------------------------------------------------------------------------------------------
1687  if (cmd == "getimureg")
1688  {
1689  if (camreg::get())
1690  {
1691  unsigned int val = itsCamera->readIMUregister(std::stoi(rem, nullptr, 0));
1692  std::ostringstream os; os << std::hex << val;
1693  s->writeString(pfx, os.str());
1694  return true;
1695  }
1696  errmsg = "Access to camera's IMU registers is disabled, enable with: setpar camreg true";
1697  }
1698 
1699  // ----------------------------------------------------------------------------------------------------
1700  if (cmd == "setimuregs")
1701  {
1702  if (camreg::get())
1703  {
1704  // Read register and value as strings, then std::stoi to convert to int, supports 0x (and 0 for octal, caution)
1705  std::vector<std::string> v = jevois::split(rem);
1706  if (v.size() < 3) errmsg = "Malformed arguments, need at least 3";
1707  else
1708  {
1709  unsigned short reg = std::stoi(v[0], nullptr, 0);
1710  size_t num = std::stoi(v[1], nullptr, 0);
1711  if (num > 32) errmsg = "Maximum transfer size is 32 bytes";
1712  else if (num != v.size() - 2) errmsg = "Incorrect number of data bytes, should pass " + v[1] + " values.";
1713  else
1714  {
1715  unsigned char data[32];
1716  for (size_t i = 2; i < v.size(); ++i) data[i-2] = std::stoi(v[i], nullptr, 0) & 0xff;
1717 
1718  itsCamera->writeIMUregisterArray(reg, data, num);
1719  return true;
1720  }
1721  }
1722  }
1723  else errmsg = "Access to camera's IMU registers is disabled, enable with: setpar camreg true";
1724  }
1725 
1726  // ----------------------------------------------------------------------------------------------------
1727  if (cmd == "getimuregs")
1728  {
1729  if (camreg::get())
1730  {
1731  std::istringstream ss(rem); std::string reg, num; ss >> reg >> num;
1732  int n = std::stoi(num, nullptr, 0);
1733 
1734  if (n > 32) errmsg = "Maximum transfer size is 32 bytes";
1735  else
1736  {
1737  unsigned char data[32];
1738  itsCamera->readIMUregisterArray(std::stoi(reg, nullptr, 0), data, n);
1739 
1740  std::ostringstream os; os << std::hex;
1741  for (int i = 0; i < n; ++i) os << (unsigned int)(data[i]) << ' ';
1742  s->writeString(pfx, os.str());
1743  return true;
1744  }
1745  }
1746  else errmsg = "Access to camera's IMU registers is disabled, enable with: setpar camreg true";
1747  }
1748 
1749  // ----------------------------------------------------------------------------------------------------
1750  if (cmd == "listmappings")
1751  {
1752  s->writeString(pfx, "AVAILABLE VIDEO MAPPINGS:");
1753  s->writeString(pfx, "");
1754  for (size_t idx = 0; idx < itsMappings.size(); ++idx)
1755  {
1756  std::string idxstr = std::to_string(idx);
1757  if (idxstr.length() < 5) idxstr = std::string(5 - idxstr.length(), ' ') + idxstr; // pad to 5-char long
1758  s->writeString(pfx, idxstr + " - " + itsMappings[idx].str());
1759  }
1760  return true;
1761  }
1762 
1763  // ----------------------------------------------------------------------------------------------------
1764  if (cmd == "setmapping")
1765  {
1766  size_t const idx = std::stoi(rem);
1767 
1768  if (itsStreaming.load() && itsCurrentMapping.ofmt)
1769  errmsg = "Cannot set mapping while streaming: Stop your webcam program on the host computer first.";
1770  else if (idx >= itsMappings.size())
1771  errmsg = "Requested mapping index " + std::to_string(idx) + " out of range [0 .. " +
1772  std::to_string(itsMappings.size()-1) + ']';
1773  else
1774  {
1775  try
1776  {
1777  setFormatInternal(idx);
1778  return true;
1779  }
1780  catch (std::exception const & e) { errmsg = "Error parsing or setting mapping [" + rem + "]: " + e.what(); }
1781  catch (...) { errmsg = "Error parsing or setting mapping [" + rem + ']'; }
1782  }
1783  }
1784 
1785  // ----------------------------------------------------------------------------------------------------
1786  if (cmd == "setmapping2")
1787  {
1788  if (itsStreaming.load() && itsCurrentMapping.ofmt)
1789  errmsg = "Cannot set mapping while streaming: Stop your webcam program on the host computer first.";
1790  else
1791  {
1792  try
1793  {
1794  jevois::VideoMapping m; std::istringstream full("NONE 0 0 0.0 " + rem); full >> m;
1795  setFormatInternal(m);
1796  return true;
1797  }
1798  catch (std::exception const & e) { errmsg = "Error parsing or setting mapping [" + rem + "]: " + e.what(); }
1799  catch (...) { errmsg = "Error parsing or setting mapping [" + rem + ']'; }
1800  }
1801  }
1802 
1803  // ----------------------------------------------------------------------------------------------------
1804  if (cmd == "reload")
1805  {
1806  setFormatInternal(itsCurrentMapping, true);
1807  return true;
1808  }
1809 
1810  // ----------------------------------------------------------------------------------------------------
1811  if (itsCurrentMapping.ofmt == 0 || itsManualStreamon)
1812  {
1813  if (cmd == "streamon")
1814  {
1815  // keep this in sync with streamOn(), modulo the fact that here we are already locked:
1816  itsCamera->streamOn();
1817  itsGadget->streamOn();
1818  itsStreaming.store(true);
1819  return true;
1820  }
1821 
1822  if (cmd == "streamoff")
1823  {
1824  // keep this in sync with streamOff(), modulo the fact that here we are already locked:
1825  itsGadget->abortStream();
1826  itsCamera->abortStream();
1827 
1828  itsStreaming.store(false);
1829 
1830  itsGadget->streamOff();
1831  itsCamera->streamOff();
1832  return true;
1833  }
1834  }
1835 
1836  // ----------------------------------------------------------------------------------------------------
1837  if (cmd == "ping")
1838  {
1839  s->writeString(pfx, "ALIVE");
1840  return true;
1841  }
1842 
1843  // ----------------------------------------------------------------------------------------------------
1844  if (cmd == "serlog")
1845  {
1846  sendSerial(rem, true);
1847  return true;
1848  }
1849 
1850  // ----------------------------------------------------------------------------------------------------
1851  if (cmd == "serout")
1852  {
1853  sendSerial(rem, false);
1854  return true;
1855  }
1856 
1857  // ----------------------------------------------------------------------------------------------------
1858 #ifdef JEVOIS_PLATFORM
1859  if (cmd == "usbsd")
1860  {
1861  if (itsStreaming.load())
1862  {
1863  errmsg = "Cannot export microSD over USB while streaming: ";
1864  if (itsCurrentMapping.ofmt) errmsg += "Stop your webcam program on the host computer first.";
1865  else errmsg += "Issue a 'streamoff' command first.";
1866  }
1867  else
1868  {
1869  startMassStorageMode();
1870  return true;
1871  }
1872  }
1873 #endif
1874 
1875  // ----------------------------------------------------------------------------------------------------
1876  if (cmd == "sync")
1877  {
1878  if (std::system("sync")) errmsg = "Disk sync failed";
1879  else return true;
1880  }
1881 
1882  // ----------------------------------------------------------------------------------------------------
1883  if (cmd == "date")
1884  {
1885  std::string dat = jevois::system("/bin/date " + rem);
1886  s->writeString(pfx, "date now " + dat.substr(0, dat.size()-1)); // skip trailing newline
1887  return true;
1888  }
1889 
1890  // ----------------------------------------------------------------------------------------------------
1891  if (cmd == "runscript")
1892  {
1893  std::string const fname = itsModule ? itsModule->absolutePath(rem) : rem;
1894 
1895  try { runScriptFromFile(fname, s, true); return true; }
1896  catch (...) { errmsg = "Script " + fname + " execution failed"; }
1897  }
1898 
1899  // ----------------------------------------------------------------------------------------------------
1900  if (cmd == "shell")
1901  {
1902  std::string ret = jevois::system(rem, true);
1903  std::vector<std::string> rvec = jevois::split(ret, "\n");
1904  for (std::string const & r : rvec) s->writeString(pfx, r);
1905  return true;
1906  }
1907 
1908  // ----------------------------------------------------------------------------------------------------
1909  if (cmd == "fileget")
1910  {
1911  std::shared_ptr<jevois::Serial> ser = std::dynamic_pointer_cast<jevois::Serial>(s);
1912  if (!ser)
1913  errmsg = "File transfer only supported over USB or Hard serial ports";
1914  else
1915  {
1916  std::string const abspath = itsModule ? itsModule->absolutePath(rem) : rem;
1917  ser->fileGet(abspath);
1918  return true;
1919  }
1920  }
1921 
1922  // ----------------------------------------------------------------------------------------------------
1923  if (cmd == "fileput")
1924  {
1925  std::shared_ptr<jevois::Serial> ser = std::dynamic_pointer_cast<jevois::Serial>(s);
1926  if (!ser)
1927  errmsg = "File transfer only supported over USB or Hard serial ports";
1928  else
1929  {
1930  std::string const abspath = itsModule ? itsModule->absolutePath(rem) : rem;
1931  ser->filePut(abspath);
1932  if (std::system("sync")) { } // quietly ignore any errors on sync
1933  return true;
1934  }
1935  }
1936 
1937 #ifdef JEVOIS_PLATFORM
1938  // ----------------------------------------------------------------------------------------------------
1939  if (cmd == "restart")
1940  {
1941  s->writeString(pfx, "Restart command received - bye-bye!");
1942 
1943  if (itsStreaming.load())
1944  s->writeString(pfx, "ERR Video streaming is on - you should quit your video viewer before rebooting");
1945 
1946  if (std::system("sync")) s->writeString(pfx, "ERR Disk sync failed -- IGNORED");
1947 
1948  // Turn off the SD storage if it is there:
1949  std::ofstream(JEVOIS_USBSD_SYS).put('\n'); // ignore errors
1950 
1951  if (std::system("sync")) s->writeString(pfx, "ERR Disk sync failed -- IGNORED");
1952 
1953  // Hard reboot:
1954  this->reboot();
1955  return true;
1956  }
1957  // ----------------------------------------------------------------------------------------------------
1958 #else
1959  // ----------------------------------------------------------------------------------------------------
1960  if (cmd == "quit")
1961  {
1962  s->writeString(pfx, "Quit command received - bye-bye!");
1963  itsGadget->abortStream();
1964  itsCamera->abortStream();
1965  itsStreaming.store(false);
1966  itsGadget->streamOff();
1967  itsCamera->streamOff();
1968  itsRunning.store(false);
1969  return true;
1970  }
1971  // ----------------------------------------------------------------------------------------------------
1972 #endif
1973  }
1974 
1975  // If we make it here, we did not parse the command. If we have an error message, that means we had started parsing
1976  // the command but it was buggy, so let's throw. Otherwise, we just return false to indicate that we did not parse
1977  // this command and maybe it is for the Module:
1978  if (errmsg.size()) throw std::runtime_error("Command error [" + str + "]: " + errmsg);
1979  return false;
1980 }
1981 
1982 // ####################################################################################################
1983 void jevois::Engine::runScriptFromFile(std::string const & filename, std::shared_ptr<jevois::UserInterface> ser,
1984  bool throw_no_file)
1985 {
1986  // itsMtx should be locked by caller
1987 
1988  // Try to find the file:
1989  std::ifstream ifs(filename);
1990  if (!ifs) { if (throw_no_file) LFATAL("Could not open file " << filename); else return; }
1991 
1992  // 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
1993  // none is specified there, the first available serial:
1994  if (!ser)
1995  {
1996  if (itsSerials.empty()) LFATAL("Need at least one active serial to run script");
1997  switch (serlog::get())
1998  {
1999  case jevois::engine::SerPort::Hard:
2000  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::Hard) { ser = s; break; }
2001  break;
2002 
2003  case jevois::engine::SerPort::USB:
2004  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::USB) { ser = s; break; }
2005  break;
2006 
2007  default: break;
2008  }
2009  if (!ser) ser = itsSerials.front();
2010  }
2011 
2012  // Ok, run the script, plowing through any errors:
2013  size_t linenum = 0;
2014  for (std::string line; std::getline(ifs, line); /* */)
2015  {
2016  ++linenum;
2017 
2018  // Strip any extra whitespace at end, which could be a CR if the file was edited in Windows:
2019  line = jevois::strip(line);
2020 
2021  // Skip comments and empty lines:
2022  if (line.length() == 0 || line[0] == '#') continue;
2023 
2024  // Go and parse that line:
2025  try
2026  {
2027  bool parsed = false;
2028  try { parsed = parseCommand(line, ser); }
2029  catch (std::exception const & e)
2030  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + e.what()); }
2031  catch (...)
2032  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ["+line+"] ignored"); }
2033 
2034  if (parsed == false)
2035  {
2036  if (itsModule)
2037  {
2038  try { itsModule->parseSerial(line, ser); }
2039  catch (std::exception const & me)
2040  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + me.what()); }
2041  catch (...)
2042  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum)+": Bogus command ["+line+"] ignored"); }
2043  }
2044  else ser->writeString("ERR Unsupported command [" + line + "] and no module");
2045  }
2046  }
2047  catch (...) { jevois::warnAndIgnoreException(); }
2048  }
2049 }
#define LDEBUG(msg)
Convenience macro for users to print out console or syslog messages, DEBUG level. ...
Definition: Log.H:155
std::string warnAndIgnoreException()
Convenience function to catch an exception, issue some LERROR (depending on type), and ignore it.
Definition: Log.C:211
float ofps
output frame rate in frames/sec
Definition: VideoMapping.H:48
std::string strip(std::string const &str)
Strip white space (including CR, LF, tabs, etc) from the end of a string.
Definition: Utils.C:180
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:973
size_t numVideoMappings() const
Return the number of video mappings.
Definition: Engine.C:958
unsigned int uvcformat
USB-UVC format number (1-based)
Definition: VideoMapping.H:55
void drawErrorImage(std::string const &errmsg, RawImage &videoerrimg)
Display an error message into a RawImage.
Definition: Log.C:244
Class to open shared object (.so) files and load functions contained in them.
Definition: DynamicLoader.H:69
Data collection mode RAW means that the latest available raw data is returned each time get() is called
Exception-safe wrapper around a raw camera input frame.
Definition: Module.H:57
size_t itsDefaultMappingIdx
Index of default mapping.
Definition: Engine.H:396
std::vector< std::string > setParamString(std::string const &paramdescriptor, std::string const &val)
Set a parameter value, by string.
Definition: Component.C:358
void writeIMUregisterArray(unsigned short reg, unsigned char const *vals, size_t num)
Write an array of values to the camera&#39;s IMU registers.
Definition: Engine.C:940
void postInit() override
Override of Manager::postInit()
Definition: Engine.C:344
std::shared_ptr< VideoInput > itsCamera
Our camera.
Definition: Engine.H:400
#define V4L2_CTRL_CLASS_DETECT
Definition: Engine.C:48
void streamOff()
Stop streaming on video from camera, processing, and USB.
Definition: Engine.C:557
void preInit() override
Calls parseCommandLine()
Definition: Manager.C:49
void preInit() override
Override of Manager::preInit()
Definition: Engine.C:333
std::unique_ptr< DynamicLoader > itsLoader
Our module loader.
Definition: Engine.H:403
void writeCamRegister(unsigned short reg, unsigned short val)
Write a value of one of the camera&#39;s registers.
Definition: Engine.C:917
std::atomic< bool > itsStreaming
True when we are streaming video.
Definition: Engine.H:407
Manager of a hierarchy of Component objects.
Definition: Manager.H:73
void streamOn()
Start streaming on video from camera, processing, and USB.
Definition: Engine.C:546
unsigned int ofmt
output pixel format, or 0 for no output over USB
Definition: VideoMapping.H:45
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:1013
#define JEVOIS_TIMED_LOCK(mtx)
Helper macro to create a timed_lock_guard object.
Definition: Log.H:299
std::vector< VideoMapping > itsMappings
All our mappings from videomappings.cfg.
Definition: Engine.H:397
#define success()
void invalidate()
Invalidate the image by zero&#39;ing out the pointer to pixel buffer and the dims and format...
Definition: RawImage.C:42
RawImage const & get(bool casync=false) const
Get the next captured camera image.
Definition: Module.C:51
void mainLoop()
Main loop: grab, process, send over USB. Should be called by main application thread.
Definition: Engine.C:708
#define JEVOIS_TRACE(level)
Trace object.
Definition: Log.H:267
std::string modulename
Name of the Module that will process this mapping.
Definition: VideoMapping.H:60
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
Interface to a serial port.
Definition: Serial.H:84
void sendSerial(std::string const &str, bool islog=false)
Send a string to all serial ports.
Definition: Engine.C:875
bool parseCommand(std::string const &str, std::shared_ptr< UserInterface > s, std::string const &pfx="")
Parse a user command received over serial port.
Definition: Engine.C:1340
std::string str() const
Convenience function to print out the whole mapping in a human-friendly way.
Definition: VideoMapping.C:87
size_t frameNum() const
Get frame number.
Definition: Engine.C:913
void writeIMUregister(unsigned short reg, unsigned short val)
Write a value to one of the camera&#39;s IMU registers.
Definition: Engine.C:928
bool valid() const
Check whether the image has a valid pixel buffer.
Definition: RawImage.C:46
void removeComponent(std::shared_ptr< Comp > &component)
Remove a top-level Component from the Manager, by shared_ptr.
Definition: ManagerImpl.H:100
std::shared_ptr< VideoOutput > itsGadget
Our gadget.
Definition: Engine.H:401
void constructHelpMessage(std::ostream &out) const
Constructs a help message from all parameters in the model, and outputs it to &#39;out&#39;.
Definition: Manager.C:82
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level. ...
Definition: Log.H:193
void readIMUregisterArray(unsigned short reg, unsigned char *vals, size_t num)
Read an array of values from the camera&#39;s IMU registers.
Definition: Engine.C:946
std::string getParamStringUnique(std::string const &paramdescriptor) const
Get a parameter value by string, simple version assuming only one parameter match.
Definition: Component.C:401
std::string sopath() const
Return the full absolute path and file name of the module&#39;s .so or .py file.
Definition: VideoMapping.C:30
VideoMapping itsCurrentMapping
Current video mapping, may not match any in itsMappings if setmapping2 used.
Definition: Engine.H:398
static float uvcToFps(unsigned int interval)
Convert from USB/UVC interval to fps.
Definition: VideoMapping.C:45
VideoMapping const & getVideoMapping(size_t idx) const
Allow access to our video mappings which are parsed from file at construction.
Definition: Engine.C:964
~Engine()
Destructor.
Definition: Engine.C:476
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:114
Simple struct to hold video mapping definitions for the processing Engine.
Definition: VideoMapping.H:43
std::string system(std::string const &cmd, bool errtoo=true)
Execute a command and grab stdout output to a string.
Definition: Utils.C:248
VideoMapping const & getCurrentVideoMapping() const
Get the current video mapping.
Definition: Engine.C:952
unsigned short readCamRegister(unsigned short reg)
Read a value from one of the camera&#39;s registers.
Definition: Engine.C:922
std::atomic< bool > itsStopMainLoop
Flag used to stop the main loop.
Definition: Engine.H:408
void onParamChange(engine::cameradev const &param, std::string const &newval)
Parameter callback.
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:166
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:39
unsigned int uvcframe
USB UVC frame number (1-based)
Definition: VideoMapping.H:56
unsigned short readIMUregister(unsigned short reg)
Read a value from one of the camera&#39;s IMU registers.
Definition: Engine.C:934
void sendSerialMarkStop()
Send a message MARK STOP to indicate the end of processing.
Definition: Module.C:747
JeVois gadget driver - exposes a uvcvideo interface to host computer connected over USB...
Definition: Gadget.H:65
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level. ...
Definition: Log.H:212
Movie input, can be used as a replacement for Camera to debug algorithms using a fixed video sequence...
Definition: MovieInput.H:35
size_t getDefaultVideoMappingIdx() const
Allow access to the default video mapping index.
Definition: Engine.C:1008
std::istream & setParamsFromStream(std::istream &is, std::string const &absfile)
Set some parameters from an open stream.
Definition: Component.C:463
virtual void paramInfo(std::shared_ptr< UserInterface > s, std::map< std::string, std::string > &categs, bool skipFrozen, std::string const &cname="", std::string const &pfx="")
Get machine-oriented descriptions of all parameters.
Definition: Component.C:533
Video output to local screen.
Definition: VideoDisplay.H:34
Wrapper module to allow users to develop new modules written in Python.
Definition: PythonModule.H:189
std::vector< std::pair< std::string, std::string > > getParamString(std::string const &paramdescriptor) const
Get a parameter value, by string.
Definition: Component.C:385
Base class for a module that supports standardized serial messages.
Definition: Module.H:488
void runScriptFromFile(std::string const &filename, std::shared_ptr< UserInterface > ser, bool throw_no_file)
Run a script from file.
Definition: Engine.C:1983
std::string to_string(T const &val)
Convert from type to string.
Definition: UtilsImpl.H:58
bool match(unsigned int oformat, unsigned int owidth, unsigned int oheight, float oframespersec) const
Return true if this VideoMapping&#39;s output format is a match to the given output parameters.
Definition: VideoMapping.C:306
std::timed_mutex itsMtx
Mutex to protect our internals.
Definition: Engine.H:410
Exception-safe wrapper around a raw image to be sent over USB.
Definition: Module.H:145
VideoMapping const & getDefaultVideoMapping() const
Allow access to the default video mapping.
Definition: Engine.C:1004
bool ispython
True if the module is written in Python; affects behavior of sopath() only.
Definition: VideoMapping.H:62
void postInit() override
Checks for the –help flag.
Definition: Manager.C:58
std::string const & instanceName() const
The instance name of this component.
Definition: Component.C:50
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:578
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:176
std::atomic< bool > itsRunning
True when we are running.
Definition: Engine.H:406
void sendSerialMarkStart()
Send a message MARK START to indicate the beginning of processing.
Definition: Module.C:739
std::shared_ptr< Module > itsModule
Our current module.
Definition: Engine.H:404
No-op VideoOutput derivative for when there is no video output.
Engine(std::string const &instance)
Constructor.
Definition: Engine.C:200
std::vector< std::string > split(std::string const &input, std::string const &regex="\+")
Split string into vector of tokens using a regex to specify what to split on; default regex splits by...
Definition: Utils.C:142
#define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE