JeVois  1.20
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/IMU.H>
24 #include <jevois/Core/IMUspi.H>
25 #include <jevois/Core/IMUi2c.H>
26 
27 #include <jevois/Core/Gadget.H>
33 #include <jevois/GPU/GUIhelper.H>
34 #include <jevois/GPU/GUIconsole.H>
35 
36 #include <jevois/Core/Serial.H>
38 
39 #include <jevois/Core/Module.H>
43 
44 #include <jevois/Debug/Log.H>
45 #include <jevois/Util/Utils.H>
46 #include <jevois/Util/Async.H>
47 #include <jevois/Debug/SysInfo.H>
48 
49 #include <cmath> // for fabs
50 #include <fstream>
51 #include <algorithm>
52 #include <cstdlib> // for std::system()
53 #include <cstdio> // for std::remove()
54 #include <regex>
55 
56 #ifdef JEVOIS_PRO
57 #include <imgui_internal.h>
58 #endif
59 
60 // On the older JeVois-A33 platform kernel, detect class is not defined:
61 #ifndef V4L2_CTRL_CLASS_DETECT
62 #define V4L2_CTRL_CLASS_DETECT 0x00a30000
63 #endif
64 
65 namespace
66 {
67  // Assign a short name to every V4L2 control, for use by getcam and setcam commands
68  struct shortcontrol { unsigned int id; char const * const shortname; };
69 
70  // All V4L2 controls
71  // From this: grep V4L2_CID v4l2-controls.h | awk '{ print " { " $2 ", \"\" }," }'
72  // then fill-in the short names.
73  static shortcontrol camcontrols[] = {
74  // In V4L2_CID_BASE class:
75  { V4L2_CID_BRIGHTNESS, "brightness" },
76  { V4L2_CID_CONTRAST, "contrast" },
77  { V4L2_CID_SATURATION, "saturation" },
78  { V4L2_CID_HUE, "hue" },
79  { V4L2_CID_AUDIO_VOLUME, "audiovol" },
80  { V4L2_CID_AUDIO_BALANCE, "audiobal" },
81  { V4L2_CID_AUDIO_BASS, "audiobass" },
82  { V4L2_CID_AUDIO_TREBLE, "audiotreble" },
83  { V4L2_CID_AUDIO_MUTE, "audiomute" },
84  { V4L2_CID_AUDIO_LOUDNESS, "audioloudness" },
85  { V4L2_CID_BLACK_LEVEL, "blacklevel" },
86  { V4L2_CID_AUTO_WHITE_BALANCE, "autowb" },
87  { V4L2_CID_DO_WHITE_BALANCE, "dowb" },
88  { V4L2_CID_RED_BALANCE, "redbal" },
89  { V4L2_CID_BLUE_BALANCE, "bluebal" },
90  { V4L2_CID_GAMMA, "gamma" },
91  { V4L2_CID_WHITENESS, "whiteness" },
92  { V4L2_CID_EXPOSURE, "exposure" },
93  { V4L2_CID_AUTOGAIN, "autogain" },
94  { V4L2_CID_GAIN, "gain" },
95  { V4L2_CID_HFLIP, "hflip" },
96  { V4L2_CID_VFLIP, "vflip" },
97  { V4L2_CID_POWER_LINE_FREQUENCY, "powerfreq" },
98  { V4L2_CID_HUE_AUTO, "autohue" },
99  { V4L2_CID_WHITE_BALANCE_TEMPERATURE, "wbtemp" },
100  { V4L2_CID_SHARPNESS, "sharpness" },
101  { V4L2_CID_BACKLIGHT_COMPENSATION, "backlight" },
102  { V4L2_CID_CHROMA_AGC, "chromaagc" },
103  { V4L2_CID_COLOR_KILLER, "colorkiller" },
104  { V4L2_CID_COLORFX, "colorfx" },
105  { V4L2_CID_AUTOBRIGHTNESS, "autobrightness" },
106  { V4L2_CID_BAND_STOP_FILTER, "bandfilter" },
107  { V4L2_CID_ROTATE, "rotate" },
108  { V4L2_CID_BG_COLOR, "bgcolor" },
109  { V4L2_CID_CHROMA_GAIN, "chromagain" },
110  { V4L2_CID_ILLUMINATORS_1, "illum1" },
111  { V4L2_CID_ILLUMINATORS_2, "illum2" },
112  { V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, "mincapbuf" },
113  { V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, "minoutbuf" },
114  { V4L2_CID_ALPHA_COMPONENT, "alphacompo" },
115  // This one is not defined in our older platform kernel:
116  //{ V4L2_CID_COLORFX_CBCR, "colorfxcbcr" },
117 
118  // In V4L2_CID_CAMERA_CLASS_BASE class
119  { V4L2_CID_EXPOSURE_AUTO, "autoexp" },
120  { V4L2_CID_EXPOSURE_ABSOLUTE, "absexp" },
121  { V4L2_CID_EXPOSURE_AUTO_PRIORITY, "exppri" },
122  { V4L2_CID_PAN_RELATIVE, "panrel" },
123  { V4L2_CID_TILT_RELATIVE, "tiltrel" },
124  { V4L2_CID_PAN_RESET, "panreset" },
125  { V4L2_CID_TILT_RESET, "tiltreset" },
126  { V4L2_CID_PAN_ABSOLUTE, "panabs" },
127  { V4L2_CID_TILT_ABSOLUTE, "tiltabs" },
128  { V4L2_CID_FOCUS_ABSOLUTE, "focusabs" },
129  { V4L2_CID_FOCUS_RELATIVE, "focusrel" },
130  { V4L2_CID_FOCUS_AUTO, "focusauto" },
131  { V4L2_CID_ZOOM_ABSOLUTE, "zoomabs" },
132  { V4L2_CID_ZOOM_RELATIVE, "zoomrel" },
133  { V4L2_CID_ZOOM_CONTINUOUS, "zoomcontinuous" },
134  { V4L2_CID_PRIVACY, "privacy" },
135  { V4L2_CID_IRIS_ABSOLUTE, "irisabs" },
136  { V4L2_CID_IRIS_RELATIVE, "irisrel" },
137 
138  // definition for this one seems to be in the kernel but missing somehow here:
139 #ifndef V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
140 #define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE (V4L2_CID_CAMERA_CLASS_BASE+20)
141 #endif
142  { V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, "presetwb" },
143 
144  // Those are not defined in our older platform kernel:
145  //{ V4L2_CID_AUTO_EXPOSURE_BIAS, "expbias" },
146  //{ V4L2_CID_WIDE_DYNAMIC_RANGE, "wdr" },
147  //{ V4L2_CID_IMAGE_STABILIZATION, "stabilization" },
148  //{ V4L2_CID_ISO_SENSITIVITY, "isosens" },
149  //{ V4L2_CID_ISO_SENSITIVITY_AUTO, "isosensauto" },
150  //{ V4L2_CID_EXPOSURE_METERING, "expmetering" },
151  //{ V4L2_CID_SCENE_MODE, "scene" },
152  //{ V4L2_CID_3A_LOCK, "3alock" },
153  //{ V4L2_CID_AUTO_FOCUS_START, "autofocusstart" },
154  //{ V4L2_CID_AUTO_FOCUS_STOP, "autofocusstop" },
155  //{ V4L2_CID_AUTO_FOCUS_STATUS, "autofocusstatus" },
156  //{ V4L2_CID_AUTO_FOCUS_RANGE, "autofocusrange" },
157  //{ V4L2_CID_PAN_SPEED, "panspeed" },
158  //{ V4L2_CID_TILT_SPEED, "tiltspeed" },
159 
160  // In V4L2_CID_FLASH_CLASS_BASE:
161  { V4L2_CID_FLASH_LED_MODE, "flashled" },
162  { V4L2_CID_FLASH_STROBE_SOURCE, "flashstrobesrc" },
163  { V4L2_CID_FLASH_STROBE, "flashstrobe" },
164  { V4L2_CID_FLASH_STROBE_STOP, "flashstrobestop" },
165  { V4L2_CID_FLASH_STROBE_STATUS, "flashstrovestat" },
166  { V4L2_CID_FLASH_TIMEOUT, "flashtimeout" },
167  { V4L2_CID_FLASH_INTENSITY, "flashintens" },
168  { V4L2_CID_FLASH_TORCH_INTENSITY, "flashtorch" },
169  { V4L2_CID_FLASH_INDICATOR_INTENSITY, "flashindintens" },
170  { V4L2_CID_FLASH_FAULT, "flashfault" },
171  { V4L2_CID_FLASH_CHARGE, "flashcharge" },
172  { V4L2_CID_FLASH_READY, "flashready" },
173 
174  // In V4L2_CID_JPEG_CLASS_BASE:
175  { V4L2_CID_JPEG_CHROMA_SUBSAMPLING, "jpegchroma" },
176  { V4L2_CID_JPEG_RESTART_INTERVAL, "jpegrestartint" },
177  { V4L2_CID_JPEG_COMPRESSION_QUALITY, "jpegcompression" },
178  { V4L2_CID_JPEG_ACTIVE_MARKER, "jpegmarker" },
179 
180  // In V4L2_CID_IMAGE_SOURCE_CLASS_BASE:
181  // Those are not defined in our older platform kernel:
182  //{ V4L2_CID_VBLANK, "vblank" },
183  //{ V4L2_CID_HBLANK, "hblank" },
184  //{ V4L2_CID_ANALOGUE_GAIN, "again" },
185  //{ V4L2_CID_TEST_PATTERN_RED, "testpatred" },
186  //{ V4L2_CID_TEST_PATTERN_GREENR, "testpatgreenr" },
187  //{ V4L2_CID_TEST_PATTERN_BLUE, "testpatblue" },
188  //{ V4L2_CID_TEST_PATTERN_GREENB, "testpatbreenb" },
189 
190  // In V4L2_CID_IMAGE_PROC_CLASS_BASE:
191  //{ V4L2_CID_LINK_FREQ, "linkfreq" },
192  //{ V4L2_CID_PIXEL_RATE, "pixrate" },
193  //{ V4L2_CID_TEST_PATTERN, "testpat" },
194 
195  // In V4L2_CID_DETECT_CLASS_BASE:
196  //{ V4L2_CID_DETECT_MD_MODE, "detectmode" },
197  //{ V4L2_CID_DETECT_MD_GLOBAL_THRESHOLD, "detectthresh" },
198  //{ V4L2_CID_DETECT_MD_THRESHOLD_GRID, "detectthreshgrid" },
199  //{ V4L2_CID_DETECT_MD_REGION_GRID, "detectregiongrid" },
200  };
201 
202  // Convert a long name to a short name:
203  std::string abbreviate(std::string const & longname)
204  {
205  std::string name(longname);
206  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
207  name.erase(std::remove_if(name.begin(), name.end(), [](int c) { return !std::isalnum(c); }), name.end());
208  return name;
209  }
210 } // anonymous namespace
211 
212 
213 // ####################################################################################################
214 namespace jevois { namespace engine { static std::atomic<size_t> frameNumber(0); } }
215 
216 size_t jevois::frameNum()
217 { return jevois::engine::frameNumber.load(); }
218 
219 // ####################################################################################################
220 jevois::Engine::Engine(std::string const & instance) :
221  jevois::Manager(instance), itsMappings(), itsRunning(false), itsStreaming(false), itsStopMainLoop(false),
222  itsShellMode(false), itsTurbo(false), itsManualStreamon(false), itsVideoErrors(false),
223  itsNumSerialSent(0), itsRequestedFormat(-2)
224 {
225  JEVOIS_TRACE(1);
226 
227 #ifdef JEVOIS_PLATFORM_A33
228  // Start mass storage thread:
229  itsCheckingMassStorage.store(false); itsMassStorageMode.store(false);
230  itsCheckMassStorageFut = jevois::async_little(&jevois::Engine::checkMassStorage, this);
231  while (itsCheckingMassStorage.load() == false) std::this_thread::sleep_for(std::chrono::milliseconds(5));
232 #endif
233 
234  jevois::engine::frameNumber.store(0);
235 }
236 
237 // ####################################################################################################
238 jevois::Engine::Engine(int argc, char const* argv[], std::string const & instance) :
239  jevois::Manager(argc, argv, instance), itsMappings(), itsRunning(false), itsStreaming(false),
240  itsStopMainLoop(false), itsShellMode(false), itsTurbo(false), itsManualStreamon(false), itsVideoErrors(false),
241  itsNumSerialSent(0), itsRequestedFormat(-2)
242 {
243  JEVOIS_TRACE(1);
244 
245 #ifdef JEVOIS_PLATFORM_A33
246  // Start mass storage thread:
247  itsCheckingMassStorage.store(false); itsMassStorageMode.store(false);
248  itsCheckMassStorageFut = jevois::async_little(&jevois::Engine::checkMassStorage, this);
249  while (itsCheckingMassStorage.load() == false) std::this_thread::sleep_for(std::chrono::milliseconds(5));
250 #endif
251 
252  jevois::engine::frameNumber.store(0);
253 }
254 
255 // ####################################################################################################
256 void jevois::Engine::onParamChange(jevois::engine::serialdev const & JEVOIS_UNUSED_PARAM(param),
257  std::string const & newval)
258 {
259  JEVOIS_TIMED_LOCK(itsMtx);
260 
261  // If we have a serial already, nuke it:
262  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
263  if ((*itr)->instanceName() == "serial") itr = itsSerials.erase(itr);
264  removeComponent("serial", false);
265 
266  // Open the usb hardware (4-pin connector) serial port, if any:
267  if (newval.empty() == false)
268  try
269  {
270  std::shared_ptr<jevois::UserInterface> s;
271  if (newval == "stdio")
272  s = addComponent<jevois::StdioInterface>("serial");
273  else
274  {
275  s = addComponent<jevois::Serial>("serial", jevois::UserInterface::Type::Hard);
276  s->setParamVal("devname", newval);
277  }
278 
279  itsSerials.push_back(s);
280  LINFO("Using [" << newval << "] hardware (4-pin connector) serial port");
281  }
282  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start hardware (4-pin connector) serial port"); }
283  else LINFO("No hardware (4-pin connector) serial port used");
284 }
285 
286 // ####################################################################################################
287 void jevois::Engine::onParamChange(jevois::engine::usbserialdev const & JEVOIS_UNUSED_PARAM(param),
288  std::string const & newval)
289 {
290  JEVOIS_TIMED_LOCK(itsMtx);
291 
292  // If we have a usbserial already, nuke it:
293  for (std::list<std::shared_ptr<UserInterface> >::iterator itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
294  if ((*itr)->instanceName() == "usbserial") itr = itsSerials.erase(itr);
295  removeComponent("usbserial", false);
296 
297  // Open the USB serial port, if any:
298  if (newval.empty() == false)
299  try
300  {
301  std::shared_ptr<jevois::UserInterface> s =
302  addComponent<jevois::Serial>("usbserial", jevois::UserInterface::Type::USB);
303  s->setParamVal("devname", newval);
304  itsSerials.push_back(s);
305  LINFO("Using [" << newval << "] USB serial port");
306  }
307  catch (...) { jevois::warnAndIgnoreException(); LERROR("Could not start USB serial port"); }
308  else LINFO("No USB serial port used");
309 }
310 
311 // ####################################################################################################
312 void jevois::Engine::onParamChange(jevois::engine::cpumode const & JEVOIS_UNUSED_PARAM(param),
313  jevois::engine::CPUmode const & newval)
314 {
315 #ifdef JEVOIS_PRO
316  std::ofstream ofs("/sys/devices/system/cpu/cpu2/cpufreq/scaling_governor");
317 #else
318  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");
319 #endif
320  if (ofs.is_open() == false)
321  {
322 #ifdef JEVOIS_PLATFORM
323  LERROR("Cannot set cpu frequency governor mode -- IGNORED");
324 #endif
325  return;
326  }
327 
328  switch (newval)
329  {
330  case engine::CPUmode::PowerSave: ofs << "powersave" << std::endl; break;
331  case engine::CPUmode::Conservative: ofs << "conservative" << std::endl; break;
332  case engine::CPUmode::OnDemand: ofs << "ondemand" << std::endl; break;
333  case engine::CPUmode::Interactive: ofs << "interactive" << std::endl; break;
334  case engine::CPUmode::Performance: ofs << "performance" << std::endl; break;
335  }
336 }
337 
338 #ifdef JEVOIS_PRO
339 // ####################################################################################################
340 void jevois::Engine::onParamChange(jevois::engine::cpumodel const & JEVOIS_UNUSED_PARAM(param),
341  jevois::engine::CPUmode const & newval)
342 {
343  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");
344  if (ofs.is_open() == false)
345  {
346 #ifdef JEVOIS_PLATFORM
347  LERROR("Cannot set cpu frequency governor mode -- IGNORED");
348 #endif
349  return;
350  }
351 
352  switch (newval)
353  {
354  case engine::CPUmode::PowerSave: ofs << "powersave" << std::endl; break;
355  case engine::CPUmode::Conservative: ofs << "conservative" << std::endl; break;
356  case engine::CPUmode::OnDemand: ofs << "ondemand" << std::endl; break;
357  case engine::CPUmode::Interactive: ofs << "interactive" << std::endl; break;
358  case engine::CPUmode::Performance: ofs << "performance" << std::endl; break;
359  }
360 }
361 #endif
362 
363 // ####################################################################################################
364 void jevois::Engine::onParamChange(jevois::engine::cpumax const & JEVOIS_UNUSED_PARAM(param),
365  unsigned int const & newval)
366 {
367 #ifdef JEVOIS_PRO
368  std::ofstream ofs("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq");
369 #else
370  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
371 #endif
372 
373  if (ofs.is_open() == false)
374  {
375 #ifdef JEVOIS_PLATFORM
376  LERROR("Cannot set cpu max frequency -- IGNORED");
377 #endif
378  return;
379  }
380 
381  ofs << newval * 1000U << std::endl;
382 }
383 
384 // ####################################################################################################
385 void jevois::Engine::onParamChange(jevois::engine::videoerrors const & JEVOIS_UNUSED_PARAM(param),
386  bool const & newval)
387 {
388  itsVideoErrors.store(newval);
389 }
390 
391 #ifdef JEVOIS_PRO
392 // ####################################################################################################
393 void jevois::Engine::onParamChange(jevois::engine::gui const & JEVOIS_UNUSED_PARAM(param),
394  bool const & newval)
395 {
396  JEVOIS_TIMED_LOCK(itsMtx);
397  if (newval)
398  {
399  if (!itsGUIhelper)
400  {
401  itsGUIhelper = addComponent<jevois::GUIhelper>("gui", conslock::get());
402  auto s = addComponent<jevois::GUIconsole>("guiconsole");
403  itsSerials.push_back(s);
404  LINFO("GUI enabled.");
405  }
406  }
407  else if (itsGUIhelper)
408  {
409  for (auto itr = itsSerials.begin(); itr != itsSerials.end(); ++itr)
410  if ((*itr)->instanceName() == "guiconsole") { itsSerials.erase(itr); break; }
411  removeComponent("guiconsole", false);
412  removeComponent(itsGUIhelper);
413  itsGUIhelper.reset();
414  LINFO("GUI disabled.");
415  }
416 }
417 
418 // ####################################################################################################
419 void jevois::Engine::onParamChange(jevois::engine::cpumaxl const & JEVOIS_UNUSED_PARAM(param),
420  unsigned int const & newval)
421 {
422  std::ofstream ofs("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq");
423 
424  if (ofs.is_open() == false)
425  {
426 #ifdef JEVOIS_PLATFORM
427  LERROR("Cannot set cpu max frequency -- IGNORED");
428 #endif
429  return;
430  }
431 
432  ofs << newval * 1000U << std::endl;
433 }
434 
435 // ####################################################################################################
436 void jevois::Engine::onParamChange(jevois::engine::demomode const & JEVOIS_UNUSED_PARAM(param),
437  float const & newval)
438 {
439  // Restart the demo each time this param is changed to 0:
440  if (newval == 0.0F) itsDemoReset = true;
441 }
442 
443 // ####################################################################################################
444 void jevois::Engine::runDemoStep()
445 {
446  if (! itsGUIhelper) return; // silently ignore if not running the GUI
447 
448  int constexpr fade = 30; // number of frames for fade in/out
449  int constexpr msg = 90; // number of frames to show message
450  int constexpr tmax = 15; // max amount of twirl
451 
452  // Basic flow is loop over bunch of modules, and for each:
453  // - fade out (i.e., apply increasing twirl, decreasing overlay alpha) over fade_out frames
454  // - display demo message for show_msg frames
455  // - stream off, setmapping, apply params, stream on
456  // - fade in (untwirl, restore overlay alpha) over fade_in frames
457  // - let the module run for itsDemoMode seconds
458 
459  static size_t modidx = 0;
460  static int fade_out = 0, show_msg = 0, fade_in = 0;
461  static std::chrono::time_point<std::chrono::steady_clock> mod_load_time;
462  std::chrono::time_point<std::chrono::steady_clock> const now = std::chrono::steady_clock::now();
463 
464  if (itsDemoReset)
465  {
466  // Load the demo data from YAML:
467  itsDemoData.clear();
468  cv::FileStorage fs(JEVOISPRO_DEMO_DATA_FILE, cv::FileStorage::READ);
469  if (fs.isOpened() == false)
470  { LERROR("Could not open " << JEVOISPRO_DEMO_DATA_FILE << " -- DEMO MODE ABORTED"); demomode::set(0.0F); return; }
471 
472  cv::FileNode fn = fs.root();
473  for (cv::FileNodeIterator gfit = fn.begin(); gfit != fn.end(); ++gfit)
474  {
475  cv::FileNode item = *gfit;
476  if (! item.isMap()) continue;
477  DemoData dd { };
478 
479  for(cv::FileNodeIterator fit = item.begin(); fit != item.end(); ++fit)
480  {
481  cv::FileNode param = *fit;
482 
483  std::string k = param.name();
484  if (k == "demomapping")
485  {
486  std::string const vmstr = (std::string)param;
487  jevois::VideoMapping vm; std::istringstream iss(vmstr); iss >> vm;
488  int idx = 0; dd.mapping_idx = -1;
489  foreachVideoMapping([&dd, &vm, &idx](jevois::VideoMapping const & m)
490  { if (vm.isSameAs(m)) dd.mapping_idx = idx; else ++idx; });
491  if (dd.mapping_idx == -1) { LERROR("Video mapping not found for [" << vmstr << "] -- SKIPPED"); break; }
492  }
493  else if (k == "demotitle")
494  dd.title = (std::string)param;
495  else if (k == "demomsg")
496  dd.msg = (std::string)param;
497  else
498  {
499  std::string v;
500  switch (param.type())
501  {
502  case cv::FileNode::INT: v = std::to_string((int)param); break;
503  case cv::FileNode::REAL: v = std::to_string((float)param); break;
504  case cv::FileNode::STRING: v = (std::string)param; break;
505  default: LERROR("Invalid demo parameter for [" << item.name() << "]: " << k << " type " << param.type());
506  }
507 
508  if (dd.mapping_idx != -1) dd.params.emplace_back(std::make_pair(k, v));
509  }
510  }
511 
512  itsDemoData.emplace_back(std::move(dd));
513  }
514 
515  if (itsDemoData.empty())
516  { LERROR("No demos in " << JEVOISPRO_DEMO_DATA_FILE << " -- DEMO MODE ABORTED"); demomode::set(0.0F); return; }
517  else LINFO("Loaded demo information with " << itsDemoData.size() << " demo modules.");
518 
519  // Start demo from the beginning:
520  fade_out = 0; show_msg = msg * 3; fade_in = 0; mod_load_time = now; modidx = 0;
521  itsGUIhelper->demoBanner("Welcome to JeVois-Pro!", "This demo will cycle through a few machine vision algorithms.");
522  itsDemoReset = false;
523  return; // skip one frame to allow the GUI to be all ready to go
524  }
525 
526  // Did user request to switch to next demo?
527  if (itsNextDemoRequested)
528  {
529  ++modidx; if (modidx >= itsDemoData.size()) modidx = 0;
530  fade_out = 0; show_msg = msg; fade_in = 0; mod_load_time = now;
531  itsNextDemoRequested = false;
532  }
533 
534  // Show message then load module:
535  if (show_msg == msg || itsGUIhelper->idle() == false)
536  itsGUIhelper->demoBanner(itsDemoData[modidx].title, itsDemoData[modidx].msg);
537 
538  if (show_msg)
539  {
540  itsGUIhelper->twirl::set(tmax);
541 
542  if (show_msg == msg)
543  {
544  LINFO("Loading demo: " << itsDemoData[modidx].title);
545  requestSetFormat(itsDemoData[modidx].mapping_idx);
546  mod_load_time = now;
547  }
548 
549  --show_msg;
550 
551  if (show_msg == 0) fade_in = fade;
552  return;
553  }
554 
555  // Before we fade in and after the module is loaded, set the parameters:
556  if (fade_in == fade)
557  for (auto const & pp : itsDemoData[modidx].params)
558  try { setParamString(pp.first, pp.second); }
559  catch (...) { LERROR("Failed to set param [" << pp.first << "] to [" << pp.second << "] -- IGNORED"); }
560 
561  // Run any fade_in (untwirl):
562  if (fade_in)
563  {
564  itsGUIhelper->twirl::set(float(tmax * fade_in - tmax) / float(fade));
565  if (--fade_in == 0 && itsGUIhelper->idle()) itsGUIhelper->demoBanner(); // turn off banner at end of fade-in
566  return;
567  }
568 
569  // Run any fade out (twirl):
570  if (fade_out)
571  {
572  itsGUIhelper->twirl::set(tmax - float(tmax * fade_out - tmax) / float(fade));
573  if (--fade_out == 0) show_msg = msg;
574  return;
575  }
576 
577  // Let the module run for a while, then initiate fade out:
578  std::chrono::duration<float> const elapsed = now - mod_load_time;
579  if (elapsed.count() > demomode::get())
580  {
581  fade_out = fade;
582  ++modidx; if (modidx >= itsDemoData.size()) modidx = 0;
583  return;
584  }
585 }
586 // ####################################################################################################
588 { itsNextDemoRequested = true; }
589 
590 // ####################################################################################################
592 {
593  demomode::set(0.0F);
594 
595  if (! itsGUIhelper) return;
596 
597  itsGUIhelper->twirl::set(0.0F);
598  itsGUIhelper->demoBanner();
599 }
600 
601 #endif // JEVOIS_PRO
602 
603 // ####################################################################################################
605 {
606  // Set any initial parameters from global config file:
607  std::string const paramcfg = std::string(JEVOIS_CONFIG_PATH) + '/' + JEVOIS_MODULE_PARAMS_FILENAME;
608  std::ifstream ifs(paramcfg); if (ifs.is_open()) setParamsFromStream(ifs, paramcfg);
609 
610  // Run the Manager version. This parses the command line:
612 }
613 
614 // ####################################################################################################
616 {
617  // Check iw we want to use GUI mode:
618  bool usegui = false;
619 #ifdef JEVOIS_PRO
620  usegui = gui::get();
621 #endif
622 
623  itsMappings = jevois::loadVideoMappings(camerasens::get(), itsDefaultMappingIdx, true, usegui);
624  LINFO("Loaded " << itsMappings.size() << " vision processing modes.");
625 }
626 
627 // ####################################################################################################
629 {
630  // First make sure the manager gets to run this:
632 
633  // Prevent any setFormat() that may be requested, e.g., by the Inventor, as soon as it detects our gadget, until after
634  // we have completed running the initscript:
635  JEVOIS_TIMED_LOCK(itsMtx);
636 
637  // Freeze the serial port device names, their params, and camera and gadget too:
638  serialdev::freeze();
639  usbserialdev::freeze();
640  for (auto & s : itsSerials) s->freezeAllParams();
641  cameradev::freeze();
642  imudev::freeze();
643  cameranbuf::freeze();
644  camturbo::freeze();
645  gadgetdev::freeze();
646  gadgetnbuf::freeze();
647  itsTurbo = camturbo::get();
648  multicam::freeze();
649  quietcmd::freeze();
650  python::freeze();
651 
652  // On JeVois-Pro platform, we may get the camera sensor automatically from the device tree. Users should still load
653  // the correct overlay in /boot/env.txt to match the installed sensor:
654  jevois::CameraSensor camsens = camerasens::get();
655 #ifdef JEVOIS_PLATFORM_PRO
656  if (camsens == jevois::CameraSensor::any)
657  {
658  std::string str = jevois::getFileString("/proc/device-tree/sensor/sensor-name"); // that name has trailing garbage
659  size_t idx = 0; while (idx < str.length() && std::isalnum(str[idx])) ++idx;
660  str = str.substr(0, idx);
661  camerasens::strset(str);
662  camsens = camerasens::get();
663  LINFO("Camera sensor selected from device tree: " << camsens);
664  }
665 #endif
666  camerasens::freeze();
667  LINFO("Using camera sensor: " << camsens);
668 
669  // Check iw we want to use GUI mode:
670  bool usegui = false;
671 #ifdef JEVOIS_PRO
672  gui::freeze();
673  usegui = gui::get();
674  conslock::freeze();
675  watchdog::freeze();
676 #endif
677 
678  // Grab the log messages, itsSerials is not going to change anymore now that the serial params are frozen:
679  jevois::logSetEngine(this);
680 
681  // Load our video mappings:
682  reloadVideoMappings();
683 
684  // Get python going, we need to do this here to avoid segfaults on platform when instantiating our first python
685  // module. This likely has to do with the fact that the python core is not very thread-safe, and setFormatInternal()
686  // in Engine, which instantiates python modules, will indeed be invoked from a different thread (the one that receives
687  // USB UVC events). Have a look at Python Thread State, Python Gobal Interpreter Lock, etc if interested:
688  if (python::get())
689  {
690  LINFO("Initalizing Python...");
692  }
693 
694  // Instantiate a camera: If device names starts with "/dev/v", assume a hardware camera, otherwise a movie file:
695  std::string const camdev = cameradev::get();
696  if (jevois::stringStartsWith(camdev, "/dev/v"))
697  {
698  LINFO("Starting camera device " << camdev);
699 
700 #ifdef JEVOIS_PLATFORM_A33
701  // Set turbo mode or not:
702  std::ofstream ofs("/sys/module/vfe_v4l2/parameters/turbo");
703  if (ofs.is_open())
704  {
705  if (itsTurbo) ofs << "1" << std::endl; else ofs << "0" << std::endl;
706  ofs.close();
707  }
708  else LERROR("Could not access VFE turbo parameter -- IGNORED");
709 #endif
710 
711  // Now instantiate the camera:
712  itsCamera.reset(new jevois::Camera(camdev, camsens, cameranbuf::get()));
713 
714 #ifndef JEVOIS_PLATFORM
715  // No need to confuse people with a non-working camreg and imureg params:
716  camreg::set(false); camreg::freeze();
717  imureg::set(false); imureg::freeze();
718 #endif
719 
720  try
721  {
722  // On JeVois-A33 platform, hook up an I2C-based IMU to the camera sensor, if supported:
723 #ifdef JEVOIS_PLATFORM_A33
724  if (jevois::sensorHasIMU(camsens))
725  itsIMU.reset(new jevois::IMUi2c(std::dynamic_pointer_cast<jevois::Camera>(itsCamera)));
726 #endif
727 
728  // On JeVois-Pro platform, instantiate an SPI-based IMU, if supported:
729 #ifdef JEVOIS_PLATFORM_PRO
730  if (jevois::sensorHasIMU(camsens))
731  itsIMU.reset(new jevois::IMUspi(imudev::get()));
732 #endif
733  } catch (...) { LERROR("Sensor should have an IMU but we failed to initialize it."); }
734  }
735  else
736  {
737  LINFO("Using movie input " << camdev << " -- issue a 'streamon' to start processing.");
738  itsCamera.reset(new jevois::MovieInput(camdev, cameranbuf::get()));
739 
740  // No need to confuse people with a non-working camreg param:
741  camreg::set(false);
742  camreg::freeze();
743  }
744 
745  // Instantiate a USB gadget: Note: it will want to access the mappings. If the user-selected video mapping has no usb
746  // out, do not instantiate a gadget:
747  int midx = videomapping::get();
748 
749  // The videomapping parameter is now disabled, users should use the 'setmapping' command once running:
750  videomapping::freeze();
751 
752  if (midx >= int(itsMappings.size()))
753  { LERROR("Mapping index " << midx << " out of range -- USING DEFAULT"); midx = -1; }
754 
755  if (midx < 0) midx = itsDefaultMappingIdx;
756 
757  // Always instantiate a gadget even if not used right now, may be used later:
758  std::string const gd = gadgetdev::get();
759  if (gd == "None")
760  {
761  LINFO("Using no USB video output.");
762  // No USB output and no display, useful for benchmarking only:
763  itsGadget.reset(new jevois::VideoOutputNone());
764  itsManualStreamon = true;
765  }
766  else if (jevois::stringStartsWith(gd, "/dev/"))
767  {
768  LINFO("Loading USB video driver " << gd);
769  // USB gadget driver:
770  itsGadget.reset(new jevois::Gadget(gd, itsCamera.get(), this, gadgetnbuf::get(), multicam::get()));
771  }
772  else if (gd.empty() == false)
773  {
774  LINFO("Saving output video to file " << gd);
775  // Non-empty filename, save to file:
776  itsGadget.reset(new jevois::MovieOutput(gd));
777  itsManualStreamon = true;
778  }
779  else
780  {
781  // Local video display, for use on a host desktop or on JeVois-Pro HDMI output:
782 #ifdef JEVOIS_PRO
783  // On JevoisPro, use OpenGL or ImGui display:
784  if (usegui)
785  {
786  LINFO("Using OpenGL + ImGui display for video output");
787  itsGadget.reset(new jevois::VideoDisplayGUI(itsGUIhelper, gadgetnbuf::get()));
788  }
789  else
790  {
791  LINFO("Using OpenGL display for video output");
792  itsGadget.reset(new jevois::VideoDisplayGL(gadgetnbuf::get()));
793  }
794 #else
795  // On JeVois-A33, use an OpenCV display:
796  LINFO("Using OpenCV display for video output");
797  itsGadget.reset(new jevois::VideoDisplay("JeVois", gadgetnbuf::get()));
798  (void)usegui; // keep compiler happy
799 #endif
800  itsManualStreamon = true;
801  }
802 
803  // We are ready to run:
804  itsRunning.store(true);
805 
806  // Set initial format:
807  try { setFormatInternal(midx); } catch (...) { jevois::warnAndIgnoreException(); }
808 
809  // Run init script:
810  runScriptFromFile(JEVOIS_ENGINE_INIT_SCRIPT, nullptr, false);
811 }
812 
813 // ####################################################################################################
815 {
816  JEVOIS_TRACE(1);
817 
818  // Turn off stream if it is on:
819  streamOff();
820 
821  // Tell our run() thread to finish up:
822  itsRunning.store(false);
823 
824 #ifdef JEVOIS_PLATFORM_A33
825  // Tell checkMassStorage() thread to finish up:
826  itsCheckingMassStorage.store(false);
827 #endif
828 
829  // Nuke our module as soon as we can, hopefully soon now that we turned off streaming and running:
830  {
831  JEVOIS_TIMED_LOCK(itsMtx);
832  if (itsModule) removeComponent(itsModule);
833  itsModule.reset();
834 
835  // Gone, nuke the loader now:
836  itsLoader.reset();
837  }
838 
839  // Because we passed the camera as a raw pointer to the gadget, nuke the gadget first and then the camera:
840  itsGadget.reset();
841  itsCamera.reset();
842 
843 #ifdef JEVOIS_PLATFORM_A33
844  // Will block until the checkMassStorage() thread completes:
845  if (itsCheckMassStorageFut.valid())
846  try { itsCheckMassStorageFut.get(); } catch (...) { jevois::warnAndIgnoreException(); }
847 #endif
848 
849  // Things should be quiet now, unhook from the logger (this call is not strictly thread safe):
850  jevois::logSetEngine(nullptr);
851 }
852 
853 // ####################################################################################################
854 #ifdef JEVOIS_PLATFORM_A33
855 void jevois::Engine::checkMassStorage()
856 {
857  itsCheckingMassStorage.store(true);
858 
859  while (itsCheckingMassStorage.load())
860  {
861  // Check from the mass storage gadget (with JeVois extension) whether the virtual USB drive is mounted by the
862  // host. If currently in mass storage mode and the host just ejected the virtual flash drive, resume normal
863  // operation. If not in mass-storage mode and the host mounted it, enter mass-storage mode (may happen if
864  // /boot/usbsdauto was selected):
865  std::ifstream ifs("/sys/devices/platform/sunxi_usb_udc/gadget/lun0/mass_storage_in_use");
866  if (ifs.is_open())
867  {
868  int inuse; ifs >> inuse;
869  if (itsMassStorageMode.load())
870  {
871  if (inuse == 0) stopMassStorageMode();
872  }
873  else
874  {
875  if (inuse) { JEVOIS_TIMED_LOCK(itsMtx); startMassStorageMode(); }
876  }
877  }
878  std::this_thread::sleep_for(std::chrono::milliseconds(500));
879  }
880 }
881 #endif
882 
883 // ####################################################################################################
885 {
886  JEVOIS_TRACE(2);
887 
888  JEVOIS_TIMED_LOCK(itsMtx);
889  if (itsCamera) itsCamera->streamOn();
890  if (itsGadget) itsGadget->streamOn();
891  itsStreaming.store(true);
892 }
893 
894 // ####################################################################################################
896 {
897  JEVOIS_TRACE(2);
898 
899  // First, tell both the camera and gadget to abort streaming, this will make get()/done()/send() throw:
900  if (itsGadget) itsGadget->abortStream();
901  if (itsCamera) itsCamera->abortStream();
902 
903  // Stop the main loop, which will flip itsStreaming to false and will make it easier for us to lock itsMtx:
904  LDEBUG("Stopping main loop...");
905  itsStopMainLoop.store(true);
906  while (itsStopMainLoop.load() && itsRunning.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10));
907  LDEBUG("Main loop stopped.");
908 
909  // Lock up and stream off:
910  JEVOIS_TIMED_LOCK(itsMtx);
911  if (itsGadget) itsGadget->streamOff();
912  if (itsCamera) itsCamera->streamOff();
913 }
914 
915 // ####################################################################################################
917 {
918  JEVOIS_TRACE(2);
919  itsRequestedFormat.store(idx);
920 }
921 
922 // ####################################################################################################
924 {
925  JEVOIS_TRACE(2);
926 
927  LDEBUG("Set format number " << idx << " start...");
928 
929  if (idx >= itsMappings.size())
930  LFATAL("Requested mapping index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
931 
932  JEVOIS_TIMED_LOCK(itsMtx);
933  setFormatInternal(idx);
934  LDEBUG("Set format number " << idx << " done");
935 }
936 
937 // ####################################################################################################
938 void jevois::Engine::setFormatInternal(size_t idx)
939 {
940  // itsMtx should be locked by caller, idx should be valid:
941  JEVOIS_TRACE(2);
942 
943  jevois::VideoMapping const & m = itsMappings[idx];
944  setFormatInternal(m);
945 }
946 
947 // ####################################################################################################
948 void jevois::Engine::setFormatInternal(jevois::VideoMapping const & m, bool reload)
949 {
950  // itsMtx should be locked by caller, idx should be valid:
951  JEVOIS_TRACE(2);
952 
953  LINFO(m.str());
954  itsModuleConstructionError = "Unknown error while starting module " + m.modulename + " ...";
955 
956 #ifdef JEVOIS_PLATFORM_A33
957  if (itsMassStorageMode.load())
958  LFATAL("Cannot setup video streaming while in mass-storage mode. Eject the USB drive on your host computer first.");
959 #endif
960 
961  // Nuke the processing module, if any, so we can also safely nuke the loader. We always nuke the module instance so we
962  // won't have any issues with latent state even if we re-use the same module but possibly with different input
963  // image resolution, etc:
964  if (itsModule)
965  {
966  LDEBUG("Removing current module " << itsModule->className() << ": " << itsModule->descriptor());
967  try { removeComponent(itsModule); itsModule.reset(); LDEBUG("Current module removed."); }
968  catch (...) { jevois::warnAndIgnoreException(); }
969  }
970 
971  // Set the format at the camera and gadget levels, unless we are just reloading:
972  if (reload == false)
973  {
974  LDEBUG("Setting camera format: " << m.cstrall());
975  try { itsCamera->setFormat(m); }
976  catch (...)
977  {
979  itsModuleConstructionError = "Camera did not accept format:\n\n" + m.cstrall() +
980  "\n\nCheck videomappings.cfg and camera sensor specifications.";
981  return;
982  }
983 
984  LDEBUG("Setting gadget format: " << m.ostr());
985  try { itsGadget->setFormat(m); }
986  catch (...)
987  {
989  itsModuleConstructionError = "Gadget did not accept format:\n\n" + m.ostr() +
990  "\n\nCheck videomappings.cfg for any unsupported output formats.";
991  return;
992  }
993  }
994 
995  // Keep track of our current mapping:
996  itsCurrentMapping = m;
997 
998  // Reset our master frame counter on each module load:
999  jevois::engine::frameNumber.store(0);
1000 
1001  // Instantiate the module. If the constructor throws, code is bogus, for example some syntax error in a python module
1002  // that is detected at load time. We get the exception's error message for later display into video frames in the main
1003  // loop, and mark itsModuleConstructionError:
1004  try
1005  {
1006  // For python modules, we do not need a loader, we just instantiate our special python wrapper module instead:
1007  std::string const sopath = m.sopath(true); // true here to delete any old .so.x versions compiled on platform
1008  if (m.ispython)
1009  {
1010  if (python::get() == false) LFATAL("Python disabled, delete BOOT:nopython and restart to enable python");
1011 
1012  // Instantiate the python wrapper:
1013  itsLoader.reset();
1014  itsModule.reset(new jevois::PythonModule(m));
1015  }
1016  else
1017  {
1018  // C++ compiled module. We can re-use the same loader and avoid closing the .so if we will use the same module,
1019  // but only for immutable jevois modules, not for user modules that may just have been recompiled (in which case a
1020  // new version number will have been generated):
1021  if (itsLoader.get() == nullptr || itsLoader->sopath() != sopath)
1022  {
1023  LINFO("Instantiating dynamic loader for " << sopath);
1024  itsLoader.reset();
1025  itsLoader.reset(new jevois::DynamicLoader(sopath, true));
1026  }
1027 
1028  // Check version match:
1029  auto version_major = itsLoader->load<int()>(m.modulename + "_version_major");
1030  auto version_minor = itsLoader->load<int()>(m.modulename + "_version_minor");
1031  if (version_major() != JEVOIS_VERSION_MAJOR || version_minor() != JEVOIS_VERSION_MINOR)
1032  LERROR("Module " << m.modulename << " in file " << sopath << " was build for JeVois v" << version_major() << '.'
1033  << version_minor() << ", but running framework is v" << JEVOIS_VERSION_STRING << " -- TRYING ANYWAY");
1034 
1035  // Instantiate the new module:
1036  auto create = itsLoader->load<std::shared_ptr<jevois::Module>(std::string const &)>(m.modulename + "_create");
1037  itsModule = create(m.modulename); // Here we just use the class name as instance name
1038  }
1039 
1040  // Add the module as a component to us. Keep this code in sync with Manager::addComponent():
1041  {
1042  // Lock up so we guarantee the instance name does not get robbed as we add the sub:
1043  boost::unique_lock<boost::shared_mutex> ulck(itsSubMtx);
1044 
1045  // Then add it as a sub-component to us:
1046  itsSubComponents.push_back(itsModule);
1047  itsModule->itsParent = this;
1048  itsModule->setPath(sopath.substr(0, sopath.rfind('/')));
1049  }
1050 
1051  // Bring it to our runstate and load any extra params. NOTE: Keep this in sync with Component::init():
1052  if (itsInitialized) itsModule->runPreInit();
1053 
1054  std::string const paramcfg = itsModule->absolutePath(JEVOIS_MODULE_PARAMS_FILENAME);
1055  std::ifstream ifs(paramcfg); if (ifs.is_open()) itsModule->setParamsFromStream(ifs, paramcfg);
1056 
1057  if (itsInitialized) { itsModule->setInitialized(); itsModule->runPostInit(); }
1058 
1059  // And finally run any config script, sending any errors to USB (likely JeVois Inventor) and GUI:
1060  std::shared_ptr<jevois::UserInterface> ser;
1061  for (auto & s : itsSerials)
1063  { ser = s; break; }
1064 
1065  runScriptFromFile(itsModule->absolutePath(JEVOIS_MODULE_SCRIPT_FILENAME), ser, false);
1066 
1067  LINFO("Module [" << m.modulename << "] loaded, initialized, and ready.");
1068  itsModuleConstructionError.clear();
1069  }
1070  catch (...)
1071  {
1072  // Note: we do not nuke the module here, as the Inventor may need its path to fix some config files.
1073  itsModuleConstructionError = jevois::warnAndIgnoreException();
1074  LERROR("Module [" << m.modulename << "] startup error and not operational.");
1075  }
1076 }
1077 
1078 // ####################################################################################################
1080 {
1081  JEVOIS_TRACE(2);
1082 
1083 #ifdef JEVOIS_PRO
1084  // Start watchdog:
1085  itsWatchdog.reset(new jevois::Watchdog(watchdog::get()));
1086 #endif
1087 
1088  std::string pfx; // optional command prefix
1089  int ret = 0; // our return value
1090 
1091  // Announce that we are ready to the hardware serial port, if any. Do not use sendSerial() here so we always issue
1092  // this message irrespectively of the user serial preferences:
1093  for (auto & s : itsSerials)
1095  try { s->writeString("INF READY JEVOIS " JEVOIS_VERSION_STRING); }
1096  catch (...) { jevois::warnAndIgnoreException(); }
1097 
1098  while (itsRunning.load())
1099  {
1100  bool dosleep = true;
1101 
1102 #ifdef JEVOIS_PRO
1103  // Reset the watchdog:
1104  itsWatchdog->reset();
1105 #endif
1106 
1107  // If we got a format change request through requestSetFormat(), honor it now while we are unlocked:
1108  // -2 means no change requested; -1 means reload requested (we do not need to change camera or gadget)
1109  int rf = itsRequestedFormat.load();
1110  if (rf != -2)
1111  {
1112  // This format change request is now marked as handled:
1113  itsRequestedFormat.store(-2);
1114 
1115  try
1116  {
1117  // Stop camera and gadget unless we are just reloading:
1118  if (rf != -1 && itsStreaming.load())
1119  {
1120  // Keep this code in sync with streamOff():
1121  if (itsGadget) itsGadget->abortStream();
1122  if (itsCamera) itsCamera->abortStream();
1123  JEVOIS_TIMED_LOCK(itsMtx);
1124  if (itsGadget) itsGadget->streamOff();
1125  if (itsCamera) itsCamera->streamOff();
1126  itsStreaming.store(false);
1127  }
1128 
1129  // Set new format or reload current module:
1130  if (rf == -1)
1131  {
1132  // Reload the current format, eg, after editing code:
1133  JEVOIS_TIMED_LOCK(itsMtx);
1134  setFormatInternal(itsCurrentMapping, true);
1135  }
1136  else setFormat(rf);
1137 
1138 #ifdef JEVOIS_PRO
1139  // Reset the GUI to clear various texture caches and such:
1140  if (itsGUIhelper) itsGUIhelper->reset( (rf != -1) );
1141 #endif
1142 
1143  // Restart camera and gadget if we stopped them:
1144  if (rf != -1 && itsCurrentMapping.ofmt != 0)
1145  {
1146  // Keep this code in sync with streamOn();
1147  JEVOIS_TIMED_LOCK(itsMtx);
1148  if (itsCamera) itsCamera->streamOn();
1149  if (itsGadget) itsGadget->streamOn();
1150  itsStreaming.store(true);
1151  }
1152 
1153  // On JeVois Pro running the GUI we need to get the camera and gadget streaming at all times for the GUI to
1154  // refresh, so restart them. When not using the GUI, users will have to issue a "streamon" to get going:
1155 #ifdef JEVOIS_PRO
1156  if (itsGUIhelper && itsStreaming.load() == false)
1157  {
1158  // Keep this code in sync with streamOn();
1159  JEVOIS_TIMED_LOCK(itsMtx);
1160  if (itsCamera) itsCamera->streamOn();
1161  if (itsGadget) itsGadget->streamOn();
1162  itsStreaming.store(true);
1163  }
1164 #endif
1165  }
1166  catch (...)
1167  {
1168  reportErrorInternal();
1169 
1170  // Stream off:
1171  try
1172  {
1173  if (itsGadget) itsGadget->abortStream();
1174  if (itsCamera) itsCamera->abortStream();
1175  JEVOIS_TIMED_LOCK(itsMtx);
1176  if (itsGadget) itsGadget->streamOff();
1177  if (itsCamera) itsCamera->streamOff();
1178  itsStreaming.store(false);
1179  }
1180  catch (...) { }
1181  }
1182  }
1183 
1184 #ifdef JEVOIS_PRO
1185  // If in demo mode, run a demo step:
1186  if (demomode::get()) runDemoStep();
1187 #endif
1188 
1189  // Run the current module:
1190  if (itsStreaming.load())
1191  {
1192  // Lock up while we use the module:
1193  JEVOIS_TIMED_LOCK(itsMtx);
1194 
1195  if (itsModuleConstructionError.empty() == false)
1196  {
1197  // If we have a module construction error, report it now to GUI/USB/console:
1198  reportErrorInternal(itsModuleConstructionError);
1199 
1200  // Also get one camera frame to avoid accumulation of stale buffers:
1201  //try { (void)jevois::InputFrame(itsCamera, itsTurbo).get(); }
1202  //catch (...) { jevois::warnAndIgnoreException(); }
1203  }
1204  else if (itsModule)
1205  {
1206  // For standard modules, indicate frame start mark if user wants it:
1207  jevois::StdModule * stdmod = dynamic_cast<jevois::StdModule *>(itsModule.get());
1208  if (stdmod) stdmod->sendSerialMarkStart();
1209 
1210  // We have a module ready for action. Call its process function and handle any exceptions:
1211  try
1212  {
1213  switch (itsCurrentMapping.ofmt)
1214  {
1215  case 0:
1216  {
1217  // Process with no USB outputs:
1218  itsModule->process(jevois::InputFrame(itsCamera, itsTurbo));
1219 
1220 #ifdef JEVOIS_PRO
1221  // We always need startFrame()/endFrame() when using the GUI:
1222  if (itsGUIhelper) itsGUIhelper->headlessDisplay();
1223 #endif
1224  break;
1225  }
1226 
1227 #ifdef JEVOIS_PRO
1228  case JEVOISPRO_FMT_GUI:
1229  {
1230  // Process with GUI display on JeVois-Pro:
1231  itsModule->process(jevois::InputFrame(itsCamera, itsTurbo), *itsGUIhelper);
1232  break;
1233  }
1234 #endif
1235  default:
1236  {
1237  // Process with USB outputs:
1238  itsModule->process(jevois::InputFrame(itsCamera, itsTurbo),
1239  jevois::OutputFrame(itsGadget, itsVideoErrors.load() ? &itsVideoErrorImage : nullptr));
1240  }
1241  }
1242 
1243  // If process() did not throw, no need to sleep:
1244  dosleep = false;
1245  }
1246  catch (...) { reportErrorInternal(); }
1247 
1248  // For standard modules, indicate frame start stop if user wants it:
1249  if (stdmod) stdmod->sendSerialMarkStop();
1250 
1251  // Increment our master frame counter
1252  ++ jevois::engine::frameNumber;
1253  itsNumSerialSent.store(0);
1254  }
1255  }
1256 
1257  if (itsStopMainLoop.load())
1258  {
1259  itsStreaming.store(false);
1260  LDEBUG("-- Main loop stopped --");
1261  itsStopMainLoop.store(false);
1262  }
1263 
1264  if (dosleep)
1265  {
1266  LDEBUG("No processing module loaded or not streaming... Sleeping...");
1267  std::this_thread::sleep_for(std::chrono::milliseconds(25));
1268  }
1269 
1270  // Serial input handling. Note that readSome() and writeString() on the serial could throw. The code below is
1271  // organized to catch all other exceptions, except for those, which are caught here at the first try level:
1272  for (auto & s : itsSerials)
1273  {
1274  try
1275  {
1276  std::string str; bool parsed = false; bool success = false;
1277 
1278  if (s->readSome(str))
1279  {
1280  JEVOIS_TIMED_LOCK(itsMtx);
1281 
1282  // If the command starts with our hidden command prefix, set the prefix, otherwise clear it:
1283  if (jevois::stringStartsWith(str, JEVOIS_JVINV_PREFIX))
1284  {
1285  pfx = JEVOIS_JVINV_PREFIX;
1286  str = str.substr(pfx.length());
1287  }
1288  else pfx.clear();
1289 
1290  // Try to execute this command. If the command is for us (e.g., set a parameter) and is correct,
1291  // parseCommand() will return true; if it is for us but buggy, it will throw. If it is not recognized by us,
1292  // it will return false and we should try sending it to the Module:
1293  try { parsed = parseCommand(str, s, pfx); success = parsed; }
1294  catch (std::exception const & e)
1295  { s->writeString(pfx, std::string("ERR ") + e.what()); parsed = true; }
1296  catch (...)
1297  { s->writeString(pfx, "ERR Unknown error"); parsed = true; }
1298 
1299  if (parsed == false)
1300  {
1301  if (itsModule)
1302  {
1303  // Note: prefixing is currently not supported for modules, it is for the Engine only
1304  try { itsModule->parseSerial(str, s); success = true; }
1305  catch (std::exception const & me) { s->writeString(pfx, std::string("ERR ") + me.what()); }
1306  catch (...) { s->writeString(pfx, "ERR Command [" + str + "] not recognized by Engine or Module"); }
1307  }
1308  else s->writeString(pfx, "ERR Unsupported command [" + str + "] and no module");
1309  }
1310 
1311  // If success, let user know:
1312  if (success && quietcmd::get() == false && itsShellMode == false) s->writeString(pfx, "OK");
1313  }
1314  }
1315  catch (...) { jevois::warnAndIgnoreException(); }
1316  }
1317  }
1318  return ret;
1319 }
1320 
1321 // ####################################################################################################
1322 void jevois::Engine::sendSerial(std::string const & str, bool islog)
1323 {
1324  // If not a log message, we may want to limit the number of serout messages that a module sends on each frame:
1325  size_t slim = serlimit::get();
1326  if (islog == false && slim)
1327  {
1328  if (itsNumSerialSent.load() >= slim) return; // limit reached, message dropped
1329  ++itsNumSerialSent; // increment number of messages sent. It is reset in the main loop on each new frame.
1330  }
1331 
1332  // Decide where to send this message based on the value of islog:
1333  jevois::engine::SerPort p = islog ? serlog::get() : serout::get();
1334  switch (p)
1335  {
1336  case jevois::engine::SerPort::None:
1337  break; // Nothing to send
1338 
1339  case jevois::engine::SerPort::All:
1340  for (auto & s : itsSerials)
1341  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
1342  break;
1343 
1344  case jevois::engine::SerPort::Hard:
1345  for (auto & s : itsSerials)
1347  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
1348  break;
1349 
1350  case jevois::engine::SerPort::USB:
1351  for (auto & s : itsSerials)
1353  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
1354  break;
1355  }
1356 
1357 #ifdef JEVOIS_PRO
1358  // If we did not send to All (which includes the GUI), check whether the GUI wants it too:
1359  if (itsGUIhelper && ((islog && itsGUIhelper->serlogEnabled()) || (!islog && itsGUIhelper->seroutEnabled())))
1360  for (auto & s : itsSerials)
1362  try { s->writeString(str); } catch (...) { jevois::warnAndIgnoreException(); }
1363 #endif
1364 }
1365 
1366 // ####################################################################################################
1367 void jevois::Engine::reportError(std::string const & err)
1368 {
1369 #ifdef JEVOIS_PRO
1370  if (itsGUIhelper) itsGUIhelper->reportError(err);
1371 #endif
1372  LERROR(err);
1373 }
1374 
1375 // ####################################################################################################
1377 {
1378 #ifdef JEVOIS_PRO
1379  // If using a GUI, clear errors in the GUI:
1380  if (itsGUIhelper) itsGUIhelper->clearErrors();
1381 #endif
1382  // Otherwise, no need to clear anything, other errors are not persistently displayed.
1383 }
1384 
1385 // ####################################################################################################
1386 void jevois::Engine::reportErrorInternal(std::string const & err)
1387 {
1388 #ifdef JEVOIS_PRO
1389  // If using a GUI, report error to GUI:
1390  if (itsGUIhelper && itsCurrentMapping.ofmt == JEVOISPRO_FMT_GUI)
1391  {
1392  if (itsGUIhelper->frameStarted() == false) { unsigned short w, h; itsGUIhelper->startFrame(w, h); }
1393  if (err.empty()) itsGUIhelper->reportError(jevois::warnAndIgnoreException());
1394  else itsGUIhelper->reportError(err);
1395  itsGUIhelper->endFrame();
1396  }
1397  else
1398 #endif
1399  // Report exceptions to video if desired: We have to be extra careful here because the exception might have
1400  // been called by the input frame (camera not streaming) or the output frame (gadget not streaming), in
1401  // addition to exceptions thrown by the module:
1402  if (itsCurrentMapping.ofmt != 0 && itsCurrentMapping.ofmt != JEVOISPRO_FMT_GUI && itsVideoErrors.load())
1403  {
1404  try
1405  {
1406  // If the module threw before get() or after send() on the output frame, get a buffer from the gadget:
1407  if (itsVideoErrorImage.valid() == false) itsGadget->get(itsVideoErrorImage); // could throw if streamoff
1408 
1409  // Draw the error message into our video frame:
1410  if (err.empty()) jevois::drawErrorImage(jevois::warnAndIgnoreException(), itsVideoErrorImage);
1411  else jevois::drawErrorImage(err, itsVideoErrorImage);
1412  }
1413  catch (...) { jevois::warnAndIgnoreException(); }
1414 
1415  try
1416  {
1417  // Send the error image over USB:
1418  if (itsVideoErrorImage.valid()) itsGadget->send(itsVideoErrorImage); // could throw if gadget stream off
1419  }
1420  catch (...) { jevois::warnAndIgnoreException(); }
1421 
1422  // Invalidate the error image so it is clean for the next frame:
1423  itsVideoErrorImage.invalidate();
1424  }
1425  else
1426  {
1427  // Report module exception to serlog, and ignore:
1428  if (err.empty()) jevois::warnAndIgnoreException();
1429  else LERROR(err);
1430  }
1431 }
1432 
1433 // ####################################################################################################
1434 std::shared_ptr<jevois::Module> jevois::Engine::module() const
1435 { return itsModule; }
1436 
1437 // ####################################################################################################
1438 std::shared_ptr<jevois::IMU> jevois::Engine::imu() const
1439 { return itsIMU; }
1440 
1441 // ####################################################################################################
1442 std::shared_ptr<jevois::Camera> jevois::Engine::camera() const
1443 { return std::dynamic_pointer_cast<jevois::Camera>(itsCamera); }
1444 
1445 // ####################################################################################################
1446 jevois::VideoMapping const & jevois::Engine::getCurrentVideoMapping() const
1447 { return itsCurrentMapping; }
1448 
1449 // ####################################################################################################
1451 { return itsMappings.size(); }
1452 
1453 // ####################################################################################################
1454 jevois::VideoMapping const & jevois::Engine::getVideoMapping(size_t idx) const
1455 {
1456  if (idx >= itsMappings.size())
1457  LFATAL("Index " << idx << " out of range [0 .. " << itsMappings.size()-1 << ']');
1458 
1459  return itsMappings[idx];
1460 }
1461 
1462 // ####################################################################################################
1463 size_t jevois::Engine::getVideoMappingIdx(unsigned int iformat, unsigned int iframe, unsigned int interval) const
1464 {
1465  // If the iformat or iframe is zero, that's probably a probe for the default mode, so return it:
1466  if (iformat == 0 || iframe == 0) return itsDefaultMappingIdx;
1467 
1468  // If interval is zero, probably a driver trying to probe for our default interval, so return the first available one;
1469  // otherwise try to find the desired interval and return the corresponding mapping:
1470  if (interval)
1471  {
1472  float const fps = jevois::VideoMapping::uvcToFps(interval);
1473  size_t idx = 0;
1474 
1475  for (jevois::VideoMapping const & m : itsMappings)
1476  if (m.uvcformat == iformat && m.uvcframe == iframe && std::fabs(m.ofps - fps) < 0.1F) return idx;
1477  else ++idx;
1478 
1479  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
1480  }
1481  else
1482  {
1483  size_t idx = 0;
1484 
1485  for (jevois::VideoMapping const & m : itsMappings)
1486  if (m.uvcformat == iformat && m.uvcframe == iframe) return idx;
1487  else ++idx;
1488 
1489  LFATAL("No video mapping for iformat=" << iformat <<", iframe=" << iframe << ", interval=" << interval);
1490  }
1491 }
1492 
1493 // ####################################################################################################
1494 jevois::VideoMapping const & jevois::Engine::getDefaultVideoMapping() const
1495 { return itsMappings[itsDefaultMappingIdx]; }
1496 
1497 // ####################################################################################################
1499 { return itsDefaultMappingIdx; }
1500 
1501 // ####################################################################################################
1502 void jevois::Engine::foreachVideoMapping(std::function<void(jevois::VideoMapping const & m)> && func)
1503 {
1504  for (jevois::VideoMapping const & m : itsMappings)
1505  try { func(m); } catch (...) { jevois::warnAndIgnoreException(); }
1506 }
1507 
1508 // ####################################################################################################
1509 jevois::VideoMapping const &
1510 jevois::Engine::findVideoMapping(unsigned int oformat, unsigned int owidth, unsigned int oheight,
1511  float oframespersec) const
1512 {
1513  for (jevois::VideoMapping const & m : itsMappings)
1514  if (m.match(oformat, owidth, oheight, oframespersec)) return m;
1515 
1516  LFATAL("Could not find mapping for output format " << jevois::fccstr(oformat) << ' ' <<
1517  owidth << 'x' << oheight << " @ " << oframespersec << " fps");
1518 }
1519 
1520 // ####################################################################################################
1521 void jevois::Engine::foreachCamCtrl(std::function<void(struct v4l2_queryctrl & qc, std::set<int> & doneids)> && func)
1522 {
1523  struct v4l2_queryctrl qc = { }; std::set<int> doneids;
1524  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
1525  {
1526  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
1527  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
1528  // doneids set to keep track of the ones already reported:
1529  qc.id = cls | 0x900; unsigned int old_id;
1530  while (true)
1531  {
1532  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; old_id = qc.id; bool failed = false;
1533  try { func(qc, doneids); } catch (...) { failed = true; }
1534 
1535  // The camera kernel driver is supposed to pass down the next valid control if the requested one is not
1536  // found, but some drivers do not honor that, so let's move on to the next control manually if needed:
1537  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
1538  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
1539  else if (failed) break;
1540  }
1541  }
1542 }
1543 
1544 // ####################################################################################################
1545 std::string jevois::Engine::camctrlname(unsigned int id, char const * longname) const
1546 {
1547  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
1548  if (camcontrols[i].id == id) return camcontrols[i].shortname;
1549 
1550  // Darn, this control is not in our list, probably something exotic. Compute a name from the control's long name:
1551  return abbreviate(longname);
1552 }
1553 
1554 // ####################################################################################################
1555 unsigned int jevois::Engine::camctrlid(std::string const & shortname)
1556 {
1557  for (size_t i = 0; i < sizeof camcontrols / sizeof camcontrols[0]; ++i)
1558  if (shortname.compare(camcontrols[i].shortname) == 0) return camcontrols[i].id;
1559 
1560  // Not in our list, all right, let's find it then in the camera:
1561  struct v4l2_queryctrl qc = { };
1562  for (int cls = V4L2_CTRL_CLASS_USER; cls <= V4L2_CTRL_CLASS_DETECT; cls += 0x10000)
1563  {
1564  // Enumerate all controls in this class. Looks like there is some spillover between V4L2 classes in the V4L2
1565  // enumeration process, we end up with duplicate controls if we try to enumerate all the classes. Hence the
1566  // doneids set to keep track of the ones already reported:
1567  qc.id = cls | 0x900;
1568  while (true)
1569  {
1570  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL; unsigned int old_id = qc.id; bool failed = false;
1571  try
1572  {
1573  itsCamera->queryControl(qc);
1574  if (abbreviate(reinterpret_cast<char const *>(qc.name)) == shortname) return qc.id;
1575  }
1576  catch (...) { failed = true; }
1577 
1578  // With V4L2_CTRL_FLAG_NEXT_CTRL, the camera kernel driver is supposed to pass down the next valid control if
1579  // the requested one is not found, but some drivers do not honor that, so let's move on to the next control
1580  // manually if needed:
1581  qc.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
1582  if (qc.id == old_id) { ++qc.id; if (qc.id > 100 + (cls | 0x900 | V4L2_CTRL_FLAG_NEXT_CTRL)) break; }
1583  else if (failed) break;
1584  }
1585  }
1586 
1587  LFATAL("Could not find control [" << shortname << "] in the camera");
1588 }
1589 
1590 // ####################################################################################################
1591 std::string jevois::Engine::camCtrlHelp(struct v4l2_queryctrl & qc, std::set<int> & doneids)
1592 {
1593  // See if we have this control:
1594  itsCamera->queryControl(qc);
1595  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
1596 
1597  // If we have already done this control, just return an empty string:
1598  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
1599 
1600  // Control exists, let's also get its current value:
1601  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
1602  itsCamera->getControl(ctrl);
1603 
1604  // Print out some description depending on control type:
1605  std::ostringstream ss;
1606  ss << "- " << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
1607 
1608  switch (qc.type)
1609  {
1610  case V4L2_CTRL_TYPE_INTEGER:
1611  ss << " [int] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
1612  << " def=" << qc.default_value << " curr=" << ctrl.value;
1613  break;
1614 
1615  //case V4L2_CTRL_TYPE_INTEGER64:
1616  //ss << " [int64] value=" << ctrl.value64;
1617  //break;
1618 
1619  //case V4L2_CTRL_TYPE_STRING:
1620  //ss << " [str] min=" << qc.minimum << " max=" << qc.maximum << " step=" << qc.step
1621  // << " curr=" << ctrl.string;
1622  //break;
1623 
1624  case V4L2_CTRL_TYPE_BOOLEAN:
1625  ss << " [bool] default=" << qc.default_value << " curr=" << ctrl.value;
1626  break;
1627 
1628  // This one is not supported by the older kernel on platform:
1629  //case V4L2_CTRL_TYPE_INTEGER_MENU:
1630  //ss << " [intmenu] min=" << qc.minimum << " max=" << qc.maximum
1631  // << " def=" << qc.default_value << " curr=" << ctrl.value;
1632  //break;
1633 
1634  case V4L2_CTRL_TYPE_BUTTON:
1635  ss << " [button]";
1636  break;
1637 
1638  case V4L2_CTRL_TYPE_BITMASK:
1639  ss << " [bitmask] max=" << qc.maximum << " def=" << qc.default_value << " curr=" << ctrl.value;
1640  break;
1641 
1642  case V4L2_CTRL_TYPE_MENU:
1643  {
1644  struct v4l2_querymenu querymenu = { };
1645  querymenu.id = qc.id;
1646  ss << " [menu] values ";
1647  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
1648  {
1649  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
1650  ss << querymenu.index << ':' << querymenu.name << ' ';
1651  }
1652  ss << "curr=" << ctrl.value;
1653  }
1654  break;
1655 
1656  default:
1657  ss << "[unknown type]";
1658  }
1659 
1660  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " [DISABLED]";
1661 
1662  return ss.str();
1663 }
1664 
1665 // ####################################################################################################
1666 std::string jevois::Engine::camCtrlInfo(struct v4l2_queryctrl & qc, std::set<int> & doneids)
1667 {
1668  // See if we have this control:
1669  itsCamera->queryControl(qc);
1670  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
1671 
1672  // If we have already done this control, just return an empty string:
1673  if (doneids.find(qc.id) != doneids.end()) return std::string(); else doneids.insert(qc.id);
1674 
1675  // Control exists, let's also get its current value:
1676  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
1677  itsCamera->getControl(ctrl);
1678 
1679  // Print out some description depending on control type:
1680  std::ostringstream ss;
1681  ss << camctrlname(qc.id, reinterpret_cast<char const *>(qc.name));
1682 
1683  if (qc.flags & V4L2_CTRL_FLAG_DISABLED) ss << " D ";
1684 
1685  switch (qc.type)
1686  {
1687  case V4L2_CTRL_TYPE_INTEGER:
1688  ss << " I " << qc.minimum << ' ' << qc.maximum << ' ' << qc.step
1689  << ' ' << qc.default_value << ' ' << ctrl.value;
1690  break;
1691 
1692  //case V4L2_CTRL_TYPE_INTEGER64:
1693  //ss << " J " << ctrl.value64;
1694  //break;
1695 
1696  //case V4L2_CTRL_TYPE_STRING:
1697  //ss << " S " << qc.minimum << ' ' << qc.maximum << ' ' << qc.step << ' ' << ctrl.string;
1698  //break;
1699 
1700  case V4L2_CTRL_TYPE_BOOLEAN:
1701  ss << " B " << qc.default_value << ' ' << ctrl.value;
1702  break;
1703 
1704  // This one is not supported by the older kernel on platform:
1705  //case V4L2_CTRL_TYPE_INTEGER_MENU:
1706  //ss << " N " << qc.minimum << ' ' << qc.maximum << ' ' << qc.default_value << ' ' << ctrl.value;
1707  //break;
1708 
1709  case V4L2_CTRL_TYPE_BUTTON:
1710  ss << " U";
1711  break;
1712 
1713  case V4L2_CTRL_TYPE_BITMASK:
1714  ss << " K " << qc.maximum << ' ' << qc.default_value << ' ' << ctrl.value;
1715  break;
1716 
1717  case V4L2_CTRL_TYPE_MENU:
1718  {
1719  struct v4l2_querymenu querymenu = { };
1720  querymenu.id = qc.id;
1721  ss << " M " << qc.default_value << ' ' << ctrl.value;
1722  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
1723  {
1724  try { itsCamera->queryMenu(querymenu); } catch (...) { strcpy((char *)(querymenu.name), "fixme"); }
1725  ss << ' ' << querymenu.index << ':' << querymenu.name << ' ';
1726  }
1727  }
1728  break;
1729 
1730  default:
1731  ss << 'X';
1732  }
1733 
1734  return ss.str();
1735 }
1736 
1737 #ifdef JEVOIS_PLATFORM_A33
1738 // ####################################################################################################
1739 void jevois::Engine::startMassStorageMode()
1740 {
1741  // itsMtx must be locked by caller
1742 
1743  if (itsMassStorageMode.load()) { LERROR("Already in mass-storage mode -- IGNORED"); return; }
1744 
1745  // Nuke any module and loader so we have nothing loaded that uses /jevois:
1746  if (itsModule) { removeComponent(itsModule); itsModule.reset(); }
1747  if (itsLoader) itsLoader.reset();
1748 
1749  // Unmount /jevois:
1750  if (std::system("sync")) LERROR("Disk sync failed -- IGNORED");
1751  if (std::system("mount -o remount,ro /jevois")) LERROR("Failed to remount /jevois read-only -- IGNORED");
1752 
1753  // Now set the backing partition in mass-storage gadget:
1754  std::ofstream ofs(JEVOIS_USBSD_SYS);
1755  if (ofs.is_open() == false) LFATAL("Cannot setup mass-storage backing file to " << JEVOIS_USBSD_SYS);
1756  ofs << JEVOIS_USBSD_FILE << std::endl;
1757 
1758  LINFO("Exported JEVOIS partition of microSD to host computer as virtual flash drive.");
1759  itsMassStorageMode.store(true);
1760 }
1761 
1762 // ####################################################################################################
1763 void jevois::Engine::stopMassStorageMode()
1764 {
1765  //itsMassStorageMode.store(false);
1766  LINFO("JeVois virtual USB drive ejected by host -- REBOOTING");
1767  reboot();
1768 }
1769 #endif
1770 
1771 // ####################################################################################################
1773 {
1774  if (std::system("sync")) LERROR("Disk sync failed -- IGNORED");
1775  if (std::system("sync")) LERROR("Disk sync failed -- IGNORED");
1776 #ifdef JEVOIS_PLATFORM_A33
1777  itsCheckingMassStorage.store(false);
1778 #endif
1779  itsRunning.store(false);
1780 
1781 #ifdef JEVOIS_PLATFORM_A33
1782  // Hard reset to avoid possible hanging during module unload, etc:
1783  if ( ! std::ofstream("/proc/sys/kernel/sysrq").put('1')) LERROR("Cannot trigger hard reset -- please unplug me!");
1784  if ( ! std::ofstream("/proc/sysrq-trigger").put('s')) LERROR("Cannot trigger hard reset -- please unplug me!");
1785  if ( ! std::ofstream("/proc/sysrq-trigger").put('b')) LERROR("Cannot trigger hard reset -- please unplug me!");
1786 #endif
1787 
1788  this->quit();
1789  //std::terminate();
1790 }
1791 
1792 // ####################################################################################################
1794 {
1795  // must be locked, camera and gadget must exist:
1796  itsGadget->abortStream();
1797  itsCamera->abortStream();
1798  itsStreaming.store(false);
1799  itsGadget->streamOff();
1800  itsCamera->streamOff();
1801  itsRunning.store(false);
1802 
1803  //std::terminate();
1804 }
1805 
1806 // ####################################################################################################
1807 void jevois::Engine::cmdInfo(std::shared_ptr<UserInterface> s, bool showAll, std::string const & pfx)
1808 {
1809  s->writeString(pfx, "help - print this help message");
1810  s->writeString(pfx, "help2 - print compact help message about current vision module only");
1811  s->writeString(pfx, "info - show system information including CPU speed, load and temperature");
1812  s->writeString(pfx, "setpar <name> <value> - set a parameter value");
1813  s->writeString(pfx, "getpar <name> - get a parameter value(s)");
1814  s->writeString(pfx, "runscript <filename> - run script commands in specified file");
1815  s->writeString(pfx, "setcam <ctrl> <val> - set camera control <ctrl> to value <val>");
1816  s->writeString(pfx, "getcam <ctrl> - get value of camera control <ctrl>");
1817 
1818  if (showAll || camreg::get())
1819  {
1820  s->writeString(pfx, "setcamreg <reg> <val> - set raw camera register <reg> to value <val>");
1821  s->writeString(pfx, "getcamreg <reg> - get value of raw camera register <reg>");
1822  s->writeString(pfx, "setimureg <reg> <val> - set raw IMU register <reg> to value <val>");
1823  s->writeString(pfx, "getimureg <reg> - get value of raw IMU register <reg>");
1824  s->writeString(pfx, "setimuregs <reg> <num> <val1> ... <valn> - set array of raw IMU register values");
1825  s->writeString(pfx, "getimuregs <reg> <num> - get array of raw IMU register values");
1826  s->writeString(pfx, "setdmpreg <reg> <val> - set raw DMP register <reg> to value <val>");
1827  s->writeString(pfx, "getdmpreg <reg> - get value of raw DMP register <reg>");
1828  s->writeString(pfx, "setdmpregs <reg> <num> <val1> ... <valn> - set array of raw DMP register values");
1829  s->writeString(pfx, "getdmpregs <reg> <num> - get array of raw DMP register values");
1830  }
1831 
1832  s->writeString(pfx, "listmappings - list all available video mappings");
1833  s->writeString(pfx, "setmapping <num> - select video mapping <num>, only possible while not streaming");
1834  s->writeString(pfx, "setmapping2 <CAMmode> <CAMwidth> <CAMheight> <CAMfps> <Vendor> <Module> - set no-USB-out "
1835  "video mapping defined on the fly, while not streaming");
1836  s->writeString(pfx, "reload - reload and reset the current module");
1837 
1838  if (showAll || itsCurrentMapping.ofmt == 0 || itsManualStreamon)
1839  {
1840  s->writeString(pfx, "streamon - start camera video streaming");
1841  s->writeString(pfx, "streamoff - stop camera video streaming");
1842  }
1843 
1844  s->writeString(pfx, "ping - returns 'ALIVE'");
1845  s->writeString(pfx, "serlog <string> - forward string to the serial port(s) specified by the serlog parameter");
1846  s->writeString(pfx, "serout <string> - forward string to the serial port(s) specified by the serout parameter");
1847 
1848  if (showAll)
1849  {
1850  // Hide machine-oriented commands by default
1851  s->writeString(pfx, "caminfo - returns machine-readable info about camera parameters");
1852  s->writeString(pfx, "cmdinfo [all] - returns machine-readable info about Engine commands");
1853  s->writeString(pfx, "modcmdinfo - returns machine-readable info about Module commands");
1854  s->writeString(pfx, "paraminfo [hot|mod|modhot] - returns machine-readable info about parameters");
1855  s->writeString(pfx, "serinfo - returns machine-readable info about serial settings (serout serlog serstyle serprec serstamp)");
1856  s->writeString(pfx, "fileget <filepath> - get a file from JeVois to the host. Use with caution!");
1857  s->writeString(pfx, "fileput <filepath> - put a file from the host to JeVois. Use with caution!");
1858  }
1859 
1860 #ifdef JEVOIS_PLATFORM_A33
1861  s->writeString(pfx, "usbsd - export the JEVOIS partition of the microSD card as a virtual USB drive");
1862 #endif
1863  s->writeString(pfx, "sync - commit any pending data write to microSD");
1864  s->writeString(pfx, "date [date and time] - get or set the system date and time");
1865 
1866  s->writeString(pfx, "!<string> - execute <string> as a Linux shell command. Use with caution!");
1867  s->writeString(pfx, "shell <string> - execute <string> as a Linux shell command. Use with caution!");
1868  s->writeString(pfx, "shellstart - execute all subsequent commands as Linux shell commands. Use with caution!");
1869  s->writeString(pfx, "shellstop - stop executing all subsequent commands as Linux shell commands.");
1870 
1871 #ifdef JEVOIS_PRO
1872  s->writeString(pfx, "dnnget <key> - download and install a DNN from JeVois Model Converter");
1873 #endif
1874 
1875 #ifdef JEVOIS_PLATFORM
1876  s->writeString(pfx, "restart - restart the JeVois smart camera");
1877 #endif
1878 
1879 #ifndef JEVOIS_PLATFORM_A33
1880  s->writeString(pfx, "quit - quit this program");
1881 #endif
1882 }
1883 
1884 // ####################################################################################################
1885 void jevois::Engine::modCmdInfo(std::shared_ptr<UserInterface> s, std::string const & pfx)
1886 {
1887  if (itsModule)
1888  {
1889  std::stringstream css; itsModule->supportedCommands(css);
1890  for (std::string line; std::getline(css, line); /* */) s->writeString(pfx, line);
1891  }
1892 }
1893 
1894 // ####################################################################################################
1895 bool jevois::Engine::parseCommand(std::string const & str, std::shared_ptr<UserInterface> s, std::string const & pfx)
1896 {
1897  // itsMtx should be locked by caller
1898 
1899  std::string errmsg;
1900 
1901  // If we are in shell mode, pass any command to the shell except for 'shellstop':
1902  if (itsShellMode)
1903  {
1904  if (str == "shellstop") { itsShellMode = false; return true; }
1905 
1906  std::string ret = jevois::system(str, true);
1907  std::vector<std::string> rvec = jevois::split(ret, "\n");
1908  for (std::string const & r : rvec) s->writeString(pfx, r);
1909  return true;
1910  }
1911 
1912  // Note: ModemManager on Ubuntu sends this on startup, kill ModemManager to avoid:
1913  // 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
1914  //
1915  // AT^SQPORT?
1916  // AT
1917  // AT
1918  // AT
1919  // ~
1920  //
1921  // then later on it insists on trying to mess with us, issuing things like AT, AT+CGMI, AT+GMI, AT+CGMM, AT+GMM,
1922  // AT%IPSYS?, ATE0, ATV1, etc etc
1923 
1924  switch (str.length())
1925  {
1926  case 0:
1927  LDEBUG("Ignoring empty string"); return true;
1928  break;
1929 
1930  case 1:
1931  if (str[0] == '~') { LDEBUG("Ignoring modem config command [~]"); return true; }
1932 
1933  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
1934  // from the arduino to be printed out to the user:
1935  if (str[0] == '#') { sendSerial(str, true); return true; }
1936  break;
1937 
1938  default: // length is 2 or more:
1939 
1940  // Ignore any command that starts with a '~':
1941  if (str[0] == '~') { LDEBUG("Ignoring modem config command [" << str << ']'); return true; }
1942 
1943  // Ignore any command that starts with "AT":
1944  if (str[0] == 'A' && str[1] == 'T') { LDEBUG("Ignoring AT command [" << str <<']'); return true; }
1945 
1946  // If the string starts with "#", then just print it out on the serlog port(s). We use this to allow debug messages
1947  // in the arduino to be printed out to the user:
1948  if (str[0] == '#') { sendSerial(str, true); return true; }
1949 
1950  // If the string starts with "!", this is like the "shell" command, but parsed differently:
1951  std::string cmd, rem;
1952  if (str[0] == '!')
1953  {
1954  cmd = "shell"; rem = str.substr(1);
1955  }
1956  else
1957  {
1958  // Get the first word, i.e., the command:
1959  size_t const idx = str.find(' ');
1960  if (idx == str.npos) cmd = str;
1961  else { cmd = str.substr(0, idx); if (idx < str.length()) rem = str.substr(idx+1); }
1962  }
1963 
1964  // ----------------------------------------------------------------------------------------------------
1965  if (cmd == "help")
1966  {
1967  // Show all commands, first ours, as supported below:
1968  s->writeString(pfx, "GENERAL COMMANDS:");
1969  s->writeString(pfx, "");
1970  cmdInfo(s, false, pfx);
1971  s->writeString(pfx, "");
1972 
1973  // Then the module's custom commands, if any:
1974  if (itsModule)
1975  {
1976  s->writeString(pfx, "MODULE-SPECIFIC COMMANDS:");
1977  s->writeString(pfx, "");
1978  modCmdInfo(s, pfx);
1979  s->writeString(pfx, "");
1980  }
1981 
1982  // Get the help message for our parameters and write it out line by line so the serial fixes the line endings:
1983  std::stringstream pss; constructHelpMessage(pss);
1984  for (std::string line; std::getline(pss, line); /* */) s->writeString(pfx, line);
1985 
1986  // Show all camera controls
1987  s->writeString(pfx, "AVAILABLE CAMERA CONTROLS:");
1988  s->writeString(pfx, "");
1989 
1990  foreachCamCtrl([this,&pfx,&s](struct v4l2_queryctrl & qc, std::set<int> & doneids)
1991  {
1992  try
1993  {
1994  std::string hlp = camCtrlHelp(qc, doneids);
1995  if (hlp.empty() == false) s->writeString(pfx, hlp);
1996  } catch (...) { } // silently ignore errors, e.g., some write-only controls
1997  });
1998  return true;
1999  }
2000 
2001  // ----------------------------------------------------------------------------------------------------
2002  if (cmd == "caminfo")
2003  {
2004  // Machine-readable list of camera parameters:
2005  foreachCamCtrl([this,&pfx,&s](struct v4l2_queryctrl & qc, std::set<int> & doneids)
2006  {
2007  try
2008  {
2009  std::string hlp = camCtrlInfo(qc, doneids);
2010  if (hlp.empty() == false) s->writeString(pfx, hlp);
2011  } catch (...) { } // silently ignore errors, e.g., some write-only controls
2012  });
2013  return true;
2014  }
2015 
2016  // ----------------------------------------------------------------------------------------------------
2017  if (cmd == "cmdinfo")
2018  {
2019  bool showAll = (rem == "all") ? true : false;
2020  cmdInfo(s, showAll, pfx);
2021  return true;
2022  }
2023 
2024  // ----------------------------------------------------------------------------------------------------
2025  if (cmd == "modcmdinfo")
2026  {
2027  modCmdInfo(s, pfx);
2028  return true;
2029  }
2030 
2031  // ----------------------------------------------------------------------------------------------------
2032  if (cmd == "paraminfo")
2033  {
2034  std::map<std::string, std::string> categs;
2035  bool skipFrozen = (rem == "hot" || rem == "modhot") ? true : false;
2036 
2037  if (rem == "mod" || rem == "modhot")
2038  {
2039  // Report only on our module's parameter, if any:
2040  if (itsModule) itsModule->paramInfo(s, categs, skipFrozen, instanceName(), pfx);
2041  }
2042  else
2043  {
2044  // Report on all parameters:
2045  paramInfo(s, categs, skipFrozen, "", pfx);
2046  }
2047 
2048  return true;
2049  }
2050 
2051  // ----------------------------------------------------------------------------------------------------
2052  if (cmd == "serinfo")
2053  {
2054  std::string info = getParamStringUnique("serout") + ' ' + getParamStringUnique("serlog");
2055  if (auto mod = dynamic_cast<jevois::StdModule *>(itsModule.get()))
2056  info += ' ' + mod->getParamStringUnique("serstyle") + ' ' + mod->getParamStringUnique("serprec") +
2057  ' ' + mod->getParamStringUnique("serstamp");
2058  else info += " - - -";
2059 
2060  s->writeString(pfx, info);
2061 
2062  return true;
2063  }
2064 
2065  // ----------------------------------------------------------------------------------------------------
2066  if (cmd == "help2")
2067  {
2068  if (itsModule)
2069  {
2070  // Start with the module's commands:
2071  std::stringstream css; itsModule->supportedCommands(css);
2072  s->writeString(pfx, "MODULE-SPECIFIC COMMANDS:");
2073  s->writeString(pfx, "");
2074  for (std::string line; std::getline(css, line); /* */) s->writeString(pfx, line);
2075  s->writeString(pfx, "");
2076 
2077  // Now the parameters for that module (and its subs) only:
2078  s->writeString(pfx, "MODULE PARAMETERS:");
2079  s->writeString(pfx, "");
2080 
2081  // Keep this in sync with Manager::constructHelpMessage():
2082  std::unordered_map<std::string, // category:description
2083  std::unordered_map<std::string, // --name (type) default=[def]
2084  std::vector<std::pair<std::string, // component name
2085  std::string // current param value
2086  > > > > helplist;
2087  itsModule->populateHelpMessage("", helplist);
2088 
2089  if (helplist.empty())
2090  s->writeString(pfx, "None.");
2091  else
2092  {
2093  for (auto const & c : helplist)
2094  {
2095  // Print out the category name and description
2096  s->writeString(pfx, c.first);
2097 
2098  // Print out the parameter details
2099  for (auto const & n : c.second)
2100  {
2101  std::vector<std::string> tok = jevois::split(n.first, "[\\r\\n]+");
2102  bool first = true;
2103  for (auto const & t : tok)
2104  {
2105  // Add current value info to the first thing we write (which is name, default, etc)
2106  if (first)
2107  {
2108  auto const & v = n.second;
2109  if (v.size() == 1) // only one component using this param
2110  {
2111  if (v[0].second.empty())
2112  s->writeString(pfx, t); // only one comp, and using default val
2113  else
2114  s->writeString(pfx, t + " current=[" + v[0].second + ']'); // using non-default val
2115  }
2116  else if (v.size() > 1) // several components using this param with possibly different values
2117  {
2118  std::string sss = t + " current=";
2119  for (auto const & pp : v)
2120  if (pp.second.empty() == false) sss += '[' + pp.first + ':' + pp.second + "] ";
2121  s->writeString(pfx, sss);
2122  }
2123  else s->writeString(pfx, t); // no non-default value(s) to report
2124 
2125  first = false;
2126  }
2127 
2128  else // just write out the other lines (param description)
2129  s->writeString(pfx, t);
2130  }
2131  }
2132  s->writeString(pfx, "");
2133  }
2134  }
2135  }
2136  else
2137  s->writeString(pfx, "No module loaded.");
2138 
2139  return true;
2140  }
2141 
2142  // ----------------------------------------------------------------------------------------------------
2143  if (cmd == "info")
2144  {
2145  s->writeString(pfx, "INFO: JeVois " JEVOIS_VERSION_STRING);
2146  s->writeString(pfx, "INFO: " + jevois::getSysInfoVersion());
2147  s->writeString(pfx, "INFO: " + jevois::getSysInfoCPU());
2148  s->writeString(pfx, "INFO: " + jevois::getSysInfoMem());
2149  if (itsModule) s->writeString(pfx, "INFO: " + itsCurrentMapping.str());
2150  else s->writeString(pfx, "INFO: " + jevois::VideoMapping().str());
2151  return true;
2152  }
2153 
2154  // ----------------------------------------------------------------------------------------------------
2155  if (cmd == "setpar")
2156  {
2157  size_t const remidx = rem.find(' ');
2158  if (remidx != rem.npos)
2159  {
2160  std::string const desc = rem.substr(0, remidx);
2161  if (remidx < rem.length())
2162  {
2163  std::string const val = rem.substr(remidx+1);
2164  setParamString(desc, val);
2165  return true;
2166  }
2167  }
2168  errmsg = "Need to provide a parameter name and a parameter value in setpar";
2169  }
2170 
2171  // ----------------------------------------------------------------------------------------------------
2172  if (cmd == "getpar")
2173  {
2174  auto vec = getParamString(rem);
2175  for (auto const & p : vec) s->writeString(pfx, p.first + ' ' + p.second);
2176  return true;
2177  }
2178 
2179  // ----------------------------------------------------------------------------------------------------
2180  if (cmd == "setcam")
2181  {
2182  std::istringstream ss(rem); std::string ctrl; int val; ss >> ctrl >> val;
2183  struct v4l2_control c = { }; c.id = camctrlid(ctrl); c.value = val;
2184 
2185  // For ispsensorpreset, need first to set it to non-zero before we set it to zero, otherwise ignored...
2186  if (val == 0 && ctrl == "ispsensorpreset")
2187  {
2188  c.value = 1; itsCamera->setControl(c);
2189  c.value = 0; itsCamera->setControl(c);
2190  }
2191  else itsCamera->setControl(c);
2192 
2193  return true;
2194  }
2195 
2196  // ----------------------------------------------------------------------------------------------------
2197  if (cmd == "getcam")
2198  {
2199  struct v4l2_control c = { }; c.id = camctrlid(rem);
2200  itsCamera->getControl(c);
2201  s->writeString(pfx, rem + ' ' + std::to_string(c.value));
2202  return true;
2203  }
2204 
2205  // ----------------------------------------------------------------------------------------------------
2206  if (cmd == "setcamreg")
2207  {
2208  if (camreg::get())
2209  {
2210  auto cam = std::dynamic_pointer_cast<jevois::Camera>(itsCamera);
2211  if (cam)
2212  {
2213  // Read register and value as strings, then std::stoi to int, supports 0x (and 0 for octal, caution)
2214  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
2215  cam->writeRegister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
2216  return true;
2217  }
2218  else errmsg = "Not using a camera for video input";
2219  }
2220  else errmsg = "Access to camera registers is disabled, enable with: setpar camreg true";
2221  }
2222 
2223  // ----------------------------------------------------------------------------------------------------
2224  if (cmd == "getcamreg")
2225  {
2226  if (camreg::get())
2227  {
2228  auto cam = std::dynamic_pointer_cast<jevois::Camera>(itsCamera);
2229  if (cam)
2230  {
2231  unsigned int val = cam->readRegister(std::stoi(rem, nullptr, 0));
2232  std::ostringstream os; os << std::hex << val;
2233  s->writeString(pfx, os.str());
2234  return true;
2235  }
2236  else errmsg = "Not using a camera for video input";
2237  }
2238  else errmsg = "Access to camera registers is disabled, enable with: setpar camreg true";
2239  }
2240 
2241  // ----------------------------------------------------------------------------------------------------
2242  if (cmd == "setimureg")
2243  {
2244  if (imureg::get())
2245  {
2246  if (itsIMU)
2247  {
2248  // Read register and value as strings, then std::stoi to int, supports 0x (and 0 for octal, caution)
2249  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
2250  itsIMU->writeRegister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
2251  return true;
2252  }
2253  else errmsg = "No IMU driver loaded";
2254  }
2255  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2256  }
2257 
2258  // ----------------------------------------------------------------------------------------------------
2259  if (cmd == "getimureg")
2260  {
2261  if (imureg::get())
2262  {
2263  if (itsIMU)
2264  {
2265  unsigned int val = itsIMU->readRegister(std::stoi(rem, nullptr, 0));
2266  std::ostringstream os; os << std::hex << val;
2267  s->writeString(pfx, os.str());
2268  return true;
2269  }
2270  else errmsg = "No IMU driver loaded";
2271  }
2272  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2273  }
2274 
2275  // ----------------------------------------------------------------------------------------------------
2276  if (cmd == "setimuregs")
2277  {
2278  if (imureg::get())
2279  {
2280  if (itsIMU)
2281  {
2282  // Read register and value as strings, then std::stoi to int, supports 0x (and 0 for octal, caution)
2283  std::vector<std::string> v = jevois::split(rem);
2284  if (v.size() < 3) errmsg = "Malformed arguments, need at least 3";
2285  else
2286  {
2287  unsigned short reg = std::stoi(v[0], nullptr, 0);
2288  size_t num = std::stoi(v[1], nullptr, 0);
2289  if (num > 32) errmsg = "Maximum transfer size is 32 bytes";
2290  else if (num != v.size() - 2) errmsg = "Incorrect number of data bytes, should pass " + v[1] + " values.";
2291  else
2292  {
2293  unsigned char data[32];
2294  for (size_t i = 2; i < v.size(); ++i) data[i-2] = std::stoi(v[i], nullptr, 0) & 0xff;
2295 
2296  itsIMU->writeRegisterArray(reg, data, num);
2297  return true;
2298  }
2299  }
2300  }
2301  else errmsg = "No IMU driver loaded";
2302  }
2303  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2304  }
2305 
2306  // ----------------------------------------------------------------------------------------------------
2307  if (cmd == "getimuregs")
2308  {
2309  if (imureg::get())
2310  {
2311  if (itsIMU)
2312  {
2313  std::istringstream ss(rem); std::string reg, num; ss >> reg >> num;
2314  int n = std::stoi(num, nullptr, 0);
2315 
2316  if (n > 32) errmsg = "Maximum transfer size is 32 bytes";
2317  else
2318  {
2319  unsigned char data[32];
2320  itsIMU->readRegisterArray(std::stoi(reg, nullptr, 0), data, n);
2321 
2322  std::ostringstream os; os << std::hex;
2323  for (int i = 0; i < n; ++i) os << (unsigned int)(data[i]) << ' ';
2324  s->writeString(pfx, os.str());
2325  return true;
2326  }
2327  }
2328  else errmsg = "No IMU driver loaded";
2329  }
2330  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2331  }
2332 
2333  // ----------------------------------------------------------------------------------------------------
2334  if (cmd == "setdmpreg")
2335  {
2336  if (imureg::get())
2337  {
2338  if (itsIMU)
2339  {
2340  // Read register and value as strings, then std::stoi to int, supports 0x (and 0 for octal, caution)
2341  std::istringstream ss(rem); std::string reg, val; ss >> reg >> val;
2342  itsIMU->writeDMPregister(std::stoi(reg, nullptr, 0), std::stoi(val, nullptr, 0));
2343  return true;
2344  }
2345  else errmsg = "No IMU driver loaded";
2346  }
2347  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2348  }
2349 
2350  // ----------------------------------------------------------------------------------------------------
2351  if (cmd == "getdmpreg")
2352  {
2353  if (camreg::get())
2354  {
2355  if (itsIMU)
2356  {
2357  unsigned int val = itsIMU->readDMPregister(std::stoi(rem, nullptr, 0));
2358  std::ostringstream os; os << std::hex << val;
2359  s->writeString(pfx, os.str());
2360  return true;
2361  }
2362  else errmsg = "No IMU driver loaded";
2363  }
2364  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2365  }
2366 
2367  // ----------------------------------------------------------------------------------------------------
2368  if (cmd == "setdmpregs")
2369  {
2370  if (camreg::get())
2371  {
2372  if (itsIMU)
2373  {
2374  // Read register and value as strings, then std::stoi to int, supports 0x (and 0 for octal, caution)
2375  std::vector<std::string> v = jevois::split(rem);
2376  if (v.size() < 3) errmsg = "Malformed arguments, need at least 3";
2377  else
2378  {
2379  unsigned short reg = std::stoi(v[0], nullptr, 0);
2380  size_t num = std::stoi(v[1], nullptr, 0);
2381  if (num > 32) errmsg = "Maximum transfer size is 32 bytes";
2382  else if (num != v.size() - 2) errmsg = "Incorrect number of data bytes, should pass " + v[1] + " values.";
2383  else
2384  {
2385  unsigned char data[32];
2386  for (size_t i = 2; i < v.size(); ++i) data[i-2] = std::stoi(v[i], nullptr, 0) & 0xff;
2387 
2388  itsIMU->writeDMPregisterArray(reg, data, num);
2389  return true;
2390  }
2391  }
2392  }
2393  else errmsg = "No IMU driver loaded";
2394  }
2395  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2396  }
2397 
2398  // ----------------------------------------------------------------------------------------------------
2399  if (cmd == "getdmpregs")
2400  {
2401  if (imureg::get())
2402  {
2403  if (itsIMU)
2404  {
2405  std::istringstream ss(rem); std::string reg, num; ss >> reg >> num;
2406  int n = std::stoi(num, nullptr, 0);
2407 
2408  if (n > 32) errmsg = "Maximum transfer size is 32 bytes";
2409  else
2410  {
2411  unsigned char data[32];
2412  itsIMU->readDMPregisterArray(std::stoi(reg, nullptr, 0), data, n);
2413 
2414  std::ostringstream os; os << std::hex;
2415  for (int i = 0; i < n; ++i) os << (unsigned int)(data[i]) << ' ';
2416  s->writeString(pfx, os.str());
2417  return true;
2418  }
2419  }
2420  else errmsg = "No IMU driver loaded";
2421  }
2422  else errmsg = "Access to IMU registers is disabled, enable with: setpar imureg true";
2423  }
2424 
2425  // ----------------------------------------------------------------------------------------------------
2426  if (cmd == "listmappings")
2427  {
2428  s->writeString(pfx, "AVAILABLE VIDEO MAPPINGS:");
2429  s->writeString(pfx, "");
2430  for (size_t idx = 0; idx < itsMappings.size(); ++idx)
2431  {
2432  std::string idxstr = std::to_string(idx);
2433  if (idxstr.length() < 5) idxstr = std::string(5 - idxstr.length(), ' ') + idxstr; // pad to 5-char long
2434  s->writeString(pfx, idxstr + " - " + itsMappings[idx].str());
2435  }
2436  return true;
2437  }
2438 
2439  // ----------------------------------------------------------------------------------------------------
2440  if (cmd == "setmapping")
2441  {
2442  size_t const idx = std::stoi(rem);
2443 
2444  if (itsStreaming.load() && itsCurrentMapping.ofmt)
2445  errmsg = "Cannot set mapping while streaming: Stop your webcam program on the host computer first.";
2446  else if (idx >= itsMappings.size())
2447  errmsg = "Requested mapping index " + std::to_string(idx) + " out of range [0 .. " +
2448  std::to_string(itsMappings.size()-1) + ']';
2449  else
2450  {
2451  try
2452  {
2453  setFormatInternal(idx);
2454  return true;
2455  }
2456  catch (std::exception const & e) { errmsg = "Error parsing or setting mapping [" + rem + "]: " + e.what(); }
2457  catch (...) { errmsg = "Error parsing or setting mapping [" + rem + ']'; }
2458  }
2459  }
2460 
2461  // ----------------------------------------------------------------------------------------------------
2462  if (cmd == "setmapping2")
2463  {
2464  if (itsStreaming.load() && itsCurrentMapping.ofmt)
2465  errmsg = "Cannot set mapping while streaming: Stop your webcam program on the host computer first.";
2466  else
2467  {
2468  try
2469  {
2470  jevois::VideoMapping m; std::istringstream full("NONE 0 0 0.0 " + rem); full >> m;
2471  setFormatInternal(m);
2472  return true;
2473  }
2474  catch (std::exception const & e) { errmsg = "Error parsing or setting mapping [" + rem + "]: " + e.what(); }
2475  catch (...) { errmsg = "Error parsing or setting mapping [" + rem + ']'; }
2476  }
2477  }
2478 
2479  // ----------------------------------------------------------------------------------------------------
2480  if (cmd == "reload")
2481  {
2482  setFormatInternal(itsCurrentMapping, true);
2483  return true;
2484  }
2485 
2486  // ----------------------------------------------------------------------------------------------------
2487  if (itsCurrentMapping.ofmt == 0 || itsCurrentMapping.ofmt == JEVOISPRO_FMT_GUI || itsManualStreamon)
2488  {
2489  if (cmd == "streamon")
2490  {
2491  // keep this in sync with streamOn(), modulo the fact that here we are already locked:
2492  itsCamera->streamOn();
2493  itsGadget->streamOn();
2494  itsStreaming.store(true);
2495  return true;
2496  }
2497 
2498  if (cmd == "streamoff")
2499  {
2500  // keep this in sync with streamOff(), modulo the fact that here we are already locked:
2501  itsGadget->abortStream();
2502  itsCamera->abortStream();
2503 
2504  itsStreaming.store(false);
2505 
2506  itsGadget->streamOff();
2507  itsCamera->streamOff();
2508  return true;
2509  }
2510  }
2511 
2512  // ----------------------------------------------------------------------------------------------------
2513  if (cmd == "ping")
2514  {
2515  s->writeString(pfx, "ALIVE");
2516  return true;
2517  }
2518 
2519  // ----------------------------------------------------------------------------------------------------
2520  if (cmd == "serlog")
2521  {
2522  sendSerial(rem, true);
2523  return true;
2524  }
2525 
2526  // ----------------------------------------------------------------------------------------------------
2527  if (cmd == "serout")
2528  {
2529  sendSerial(rem, false);
2530  return true;
2531  }
2532 
2533  // ----------------------------------------------------------------------------------------------------
2534 #ifdef JEVOIS_PLATFORM_A33
2535  if (cmd == "usbsd")
2536  {
2537  if (itsStreaming.load())
2538  {
2539  errmsg = "Cannot export microSD over USB while streaming: ";
2540  if (itsCurrentMapping.ofmt) errmsg += "Stop your webcam program on the host computer first.";
2541  else errmsg += "Issue a 'streamoff' command first.";
2542  }
2543  else
2544  {
2545  startMassStorageMode();
2546  return true;
2547  }
2548  }
2549 #endif
2550 
2551  // ----------------------------------------------------------------------------------------------------
2552  if (cmd == "sync")
2553  {
2554  if (std::system("sync")) errmsg = "Disk sync failed";
2555  else return true;
2556  }
2557 
2558  // ----------------------------------------------------------------------------------------------------
2559  if (cmd == "date")
2560  {
2561  std::string dat = jevois::system("/bin/date " + rem);
2562  s->writeString(pfx, "date now " + dat.substr(0, dat.size()-1)); // skip trailing newline
2563  return true;
2564  }
2565 
2566  // ----------------------------------------------------------------------------------------------------
2567  if (cmd == "runscript")
2568  {
2569  std::string const fname = itsModule ? itsModule->absolutePath(rem).string() : rem;
2570 
2571  try { runScriptFromFile(fname, s, true); return true; }
2572  catch (...) { errmsg = "Script " + fname + " execution failed"; }
2573  }
2574 
2575  // ----------------------------------------------------------------------------------------------------
2576  if (cmd == "shell")
2577  {
2578  std::string ret = jevois::system(rem, true);
2579  std::vector<std::string> rvec = jevois::split(ret, "\n");
2580  for (std::string const & r : rvec) s->writeString(pfx, r);
2581  return true;
2582  }
2583 
2584  // ----------------------------------------------------------------------------------------------------
2585  if (cmd == "shellstart")
2586  {
2587  itsShellMode = true;
2588  return true;
2589  // note: shellstop is handled above
2590  }
2591 
2592 #ifdef JEVOIS_PRO
2593  // ----------------------------------------------------------------------------------------------------
2594  if (cmd == "dnnget")
2595  {
2596  if (rem.length() != 4 || std::regex_match(rem, std::regex("^[a-zA-Z0-9]+$")) == false)
2597  errmsg = "Key must be a 4-character alphanumeric string, as emailed to you by the model converter.";
2598  else
2599  {
2600  // Download the zip using curl:
2601  s->writeString(pfx, "Downloading custom DNN model " + rem + " ...");
2602  std::string const zip = rem + ".zip";
2603  std::string ret = jevois::system("/usr/bin/curl " JEVOIS_CUSTOM_DNN_URL "/" + zip + " -o "
2604  JEVOIS_CUSTOM_DNN_PATH "/" + zip, true);
2605  std::vector<std::string> rvec = jevois::split(ret, "\n");
2606  for (std::string const & r : rvec) s->writeString(pfx, r);
2607 
2608  // Check that the file exists:
2609  std::ifstream ifs(JEVOIS_CUSTOM_DNN_PATH "/" + zip);
2610  if (ifs.is_open() == false)
2611  errmsg = "Failed to download. Check network connectivity and available disk space.";
2612  else
2613  {
2614  // Unzip it:
2615  s->writeString(pfx, "Unpacking custom DNN model " + rem + " ...");
2616  ret = jevois::system("/usr/bin/unzip -o " JEVOIS_CUSTOM_DNN_PATH "/" + zip +
2617  " -d " JEVOIS_CUSTOM_DNN_PATH, true);
2618  rvec = jevois::split(ret, "\n"); for (std::string const & r : rvec) s->writeString(pfx, r);
2619 
2620  ret = jevois::system("/bin/rm " JEVOIS_CUSTOM_DNN_PATH "/" + zip, true);
2621  rvec = jevois::split(ret, "\n"); for (std::string const & r : rvec) s->writeString(pfx, r);
2622 
2623  s->writeString(pfx, "Reload your model zoo for changes to take effect.");
2624 
2625  return true;
2626  }
2627  }
2628  }
2629 #endif
2630 
2631  // ----------------------------------------------------------------------------------------------------
2632  if (cmd == "fileget")
2633  {
2634  std::shared_ptr<jevois::Serial> ser = std::dynamic_pointer_cast<jevois::Serial>(s);
2635  if (!ser)
2636  errmsg = "File transfer only supported over USB or Hard serial ports";
2637  else
2638  {
2639  std::string const abspath = itsModule ? itsModule->absolutePath(rem).string() : rem;
2640  ser->fileGet(abspath);
2641  return true;
2642  }
2643  }
2644 
2645  // ----------------------------------------------------------------------------------------------------
2646  if (cmd == "fileput")
2647  {
2648  std::shared_ptr<jevois::Serial> ser = std::dynamic_pointer_cast<jevois::Serial>(s);
2649  if (!ser)
2650  errmsg = "File transfer only supported over USB or Hard serial ports";
2651  else
2652  {
2653  std::string const abspath = itsModule ? itsModule->absolutePath(rem).string() : rem;
2654  ser->filePut(abspath);
2655  if (std::system("sync")) { } // quietly ignore any errors on sync
2656  return true;
2657  }
2658  }
2659 
2660 #ifdef JEVOIS_PLATFORM
2661  // ----------------------------------------------------------------------------------------------------
2662  if (cmd == "restart")
2663  {
2664  s->writeString(pfx, "Restart command received - bye-bye!");
2665 
2666  if (itsStreaming.load())
2667  s->writeString(pfx, "ERR Video streaming is on - you should quit your video viewer before rebooting");
2668 
2669  if (std::system("sync")) s->writeString(pfx, "ERR Disk sync failed -- IGNORED");
2670 
2671 #ifdef JEVOIS_PLATFORM_A33
2672  // Turn off the SD storage if it is there:
2673  std::ofstream(JEVOIS_USBSD_SYS).put('\n'); // ignore errors
2674 
2675  if (std::system("sync")) s->writeString(pfx, "ERR Disk sync failed -- IGNORED");
2676 #endif
2677 
2678  // Hard reboot:
2679  this->reboot();
2680  return true;
2681  }
2682  // ----------------------------------------------------------------------------------------------------
2683 #endif
2684 
2685 #ifndef JEVOIS_PLATFORM_A33
2686  // ----------------------------------------------------------------------------------------------------
2687  if (cmd == "quit")
2688  {
2689  s->writeString(pfx, "Quit command received - bye-bye!");
2690  this->quit();
2691  return true;
2692  }
2693  // ----------------------------------------------------------------------------------------------------
2694 #endif
2695  }
2696 
2697  // If we make it here, we did not parse the command. If we have an error message, that means we had started parsing
2698  // the command but it was buggy, so let's throw. Otherwise, we just return false to indicate that we did not parse
2699  // this command and maybe it is for the Module:
2700  if (errmsg.size()) throw std::runtime_error("Command error [" + str + "]: " + errmsg);
2701  return false;
2702 }
2703 
2704 // ####################################################################################################
2705 void jevois::Engine::runScriptFromFile(std::string const & filename, std::shared_ptr<jevois::UserInterface> ser,
2706  bool throw_no_file)
2707 {
2708  // itsMtx should be locked by caller
2709 
2710  // Try to find the file:
2711  std::ifstream ifs(filename);
2712  if (!ifs) { if (throw_no_file) LFATAL("Could not open file " << filename); else return; }
2713 
2714  // We need to identify a serial to send any errors to, if none was given to us. Let's use the GUI console, or the
2715  // serial in serlog, or, if none is specified there, the first available serial:
2716  if (!ser)
2717  {
2718  if (itsSerials.empty()) LFATAL("Need at least one active serial to run script");
2719 
2720  switch (serlog::get())
2721  {
2722  case jevois::engine::SerPort::Hard:
2723  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::Hard) { ser = s; break; }
2724  break;
2725 
2726  case jevois::engine::SerPort::USB:
2727  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::USB) { ser = s; break; }
2728  break;
2729 
2730  default: break;
2731  }
2732 
2733 #ifdef JEVOIS_PRO
2734  if (itsGUIhelper)
2735  for (auto & s : itsSerials) if (s->type() == jevois::UserInterface::Type::GUI) { ser = s; break; }
2736 #endif
2737 
2738  if (!ser) ser = itsSerials.front();
2739  }
2740 
2741  // Ok, run the script, plowing through any errors:
2742  size_t linenum = 0;
2743  for (std::string line; std::getline(ifs, line); /* */)
2744  {
2745  ++linenum;
2746 
2747  // Strip any extra whitespace at end, which could be a CR if the file was edited in Windows:
2748  line = jevois::strip(line);
2749 
2750  // Skip comments and empty lines:
2751  if (line.length() == 0 || line[0] == '#') continue;
2752 
2753  // Go and parse that line:
2754  try
2755  {
2756  bool parsed = false;
2757  try { parsed = parseCommand(line, ser); }
2758  catch (std::exception const & e)
2759  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + e.what()); }
2760  catch (...)
2761  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": Bogus command ["+line+"] ignored"); }
2762 
2763  if (parsed == false)
2764  {
2765  if (itsModule)
2766  {
2767  try { itsModule->parseSerial(line, ser); }
2768  catch (std::exception const & me)
2769  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum) + ": " + me.what()); }
2770  catch (...)
2771  { ser->writeString("ERR " + filename + ':' + std::to_string(linenum)+": Bogus command ["+line+"] ignored"); }
2772  }
2773  else ser->writeString("ERR Unsupported command [" + line + "] and no module");
2774  }
2775  }
2776  catch (...) { jevois::warnAndIgnoreException(); }
2777  }
2778 }
2779 
2780 // ####################################################################################################
2781 #ifdef JEVOIS_PRO
2782 // ####################################################################################################
2784 {
2785  ImGui::Columns(2, "camctrl");
2786 
2787  foreachCamCtrl([this](struct v4l2_queryctrl & qc, std::set<int> & doneids)
2788  {
2789  try { camCtrlGUI(qc, doneids); } catch (...) { }
2790  });
2791 
2792  ImGui::Columns(1);
2793 }
2794 
2795 // ####################################################################################################
2796 void jevois::Engine::camCtrlGUI(struct v4l2_queryctrl & qc, std::set<int> & doneids)
2797 {
2798  // See if we have this control:
2799  itsCamera->queryControl(qc);
2800  qc.id &= ~V4L2_CTRL_FLAG_NEXT_CTRL;
2801 
2802  // If we have already done this control, just return:
2803  if (doneids.find(qc.id) != doneids.end()) return; else doneids.insert(qc.id);
2804 
2805  // Control exists, let's also get its current value:
2806  struct v4l2_control ctrl = { }; ctrl.id = qc.id;
2807  itsCamera->getControl(ctrl);
2808 
2809  // Instantiate widgets depending on control type:
2810  ImGui::AlignTextToFramePadding();
2811  ImGui::TextUnformatted(reinterpret_cast<char const *>(qc.name));
2812  ImGui::NextColumn();
2813 
2814  // Grey out the item if it is disabled:
2815  if (qc.flags & V4L2_CTRL_FLAG_DISABLED)
2816  {
2817  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
2818  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
2819  }
2820 
2821  // We need a unique ID for each ImGui widget, and we will use no visible widget name:
2822  static char wname[16]; snprintf(wname, 16, "##c%d", ctrl.id);
2823  bool reset = false; // will set to true if we want a reset button
2824 
2825  switch (qc.type)
2826  {
2827  case V4L2_CTRL_TYPE_INTEGER:
2828  case V4L2_CTRL_TYPE_INTEGER_MENU:
2829  {
2830  // Do a slider if range is reasonable, otherwise typein:
2831  long range = long(qc.maximum) - long(qc.minimum);
2832  if (range > 1 && range < 5000)
2833  {
2834  if (ImGui::SliderInt(wname, &ctrl.value, qc.minimum, qc.maximum)) itsCamera->setControl(ctrl);
2835  reset = true;
2836  }
2837  else
2838  {
2839  if (ImGui::InputInt(wname, &ctrl.value, qc.step, qc.step * 2)) itsCamera->setControl(ctrl);
2840  reset = true;
2841  }
2842  }
2843  break;
2844 
2845  //case V4L2_CTRL_TYPE_INTEGER64:
2846  //{
2847  // double val = ctrl.value64;
2848  // if (ImGui::InputDouble(wname, &val)) { ctrl.value64 = long(val + 0.4999); itsCamera->setControl(ctrl); }
2849  //}
2850  //break;
2851 
2852  //case V4L2_CTRL_TYPE_STRING:
2853  // if (ImGui::InputText(wname, ctrl.string, sizeof(ctrl.string))) itsCamera->setControl(ctrl);
2854  // break;
2855 
2856  case V4L2_CTRL_TYPE_BOOLEAN:
2857  {
2858  bool checked = (ctrl.value != 0);
2859  if (ImGui::Checkbox(wname, &checked)) { ctrl.value = checked ? 1 : 0; itsCamera->setControl(ctrl); }
2860  }
2861  break;
2862 
2863 
2864  case V4L2_CTRL_TYPE_BUTTON:
2865  static char bname[16]; snprintf(bname, 16, "Go##%d", ctrl.id);
2866  if (ImGui::Button(bname)) { ctrl.value = 1; itsCamera->setControl(ctrl); }
2867  break;
2868 
2869  case V4L2_CTRL_TYPE_BITMASK:
2870  ///ss << " K " << qc.maximum << ' ' << qc.default_value << ' ' << ctrl.value;
2871  break;
2872 
2873  case V4L2_CTRL_TYPE_MENU:
2874  {
2875  struct v4l2_querymenu querymenu = { };
2876  querymenu.id = qc.id;
2877  char * items[qc.maximum - qc.minimum + 1];
2878 
2879  for (querymenu.index = qc.minimum; querymenu.index <= (unsigned int)qc.maximum; ++querymenu.index)
2880  {
2881  try { itsCamera->queryMenu(querymenu); } catch (...) { strncpy((char *)querymenu.name, "fixme", 32); }
2882  items[querymenu.index] = new char[32];
2883  strncpy(items[querymenu.index], (char const *)querymenu.name, 32);
2884  }
2885 
2886  int idx = ctrl.value - qc.minimum;
2887  if (ImGui::Combo(wname, &idx, items, qc.maximum - qc.minimum + 1))
2888  { ctrl.value = qc.minimum + idx; itsCamera->setControl(ctrl); }
2889 
2890  for (int i = qc.minimum; i <= qc.maximum; ++i) delete [] items[i];
2891  }
2892  break;
2893 
2894  default: break;
2895  }
2896 
2897  // Add a reset button if desired:
2898  if (reset)
2899  {
2900  static char rname[16]; snprintf(rname, 16, "Reset##%d", ctrl.id);
2901  ImGui::SameLine();
2902  if (ImGui::Button(rname)) { ctrl.value = qc.default_value; itsCamera->setControl(ctrl); }
2903  }
2904 
2905  // Restore any grey out:
2906  if (qc.flags & V4L2_CTRL_FLAG_DISABLED)
2907  {
2908  ImGui::PopItemFlag();
2909  ImGui::PopStyleVar();
2910  }
2911 
2912  // Ready for next row:
2913  ImGui::NextColumn();
2914 }
2915 
2916 // ####################################################################################################
2917 #endif // JEVOIS_PRO
2918 
2919 // ####################################################################################################
2921 {
2922  LDEBUG(comp->instanceName() << " -> " << std::hex << pyinst);
2923  std::lock_guard<std::mutex> _(itsPyRegMtx);
2924  auto itr = itsPythonRegistry.find(pyinst);
2925  if (itr != itsPythonRegistry.end()) LFATAL("Trying to register twice -- ABORT");
2926  itsPythonRegistry.insert(std::make_pair(pyinst, comp));
2927 }
2928 
2929 // ####################################################################################################
2931 {
2932  LDEBUG(comp->instanceName());
2933  std::lock_guard<std::mutex> _(itsPyRegMtx);
2934  auto itr = itsPythonRegistry.begin(), stop = itsPythonRegistry.end();
2935  while (itr != stop) if (itr->second == comp) itr = itsPythonRegistry.erase(itr); else ++itr;
2936 }
2937 
2938 // ####################################################################################################
2940 {
2941  LDEBUG(std::hex << pyinst);
2942  std::lock_guard<std::mutex> _(itsPyRegMtx);
2943  auto itr = itsPythonRegistry.find(pyinst);
2944  if (itr == itsPythonRegistry.end()) LFATAL("Python instance not registered -- ABORT");
2945  return itr->second;
2946 }
jevois::Engine::getDefaultVideoMappingIdx
size_t getDefaultVideoMappingIdx() const
Allow access to the default video mapping index.
Definition: Engine.C:1498
JEVOISPRO_FMT_GUI
#define JEVOISPRO_FMT_GUI
JeVois-Pro zero-copy display of camera input frame (to be used as output mode in VideoMapping)
Definition: Utils.H:31
jevois::MovieOutput
Video output to a movie file, using OpenCV video encoding.
Definition: MovieOutput.H:33
jevois::Component::instanceName
const std::string & instanceName() const
The instance name of this component.
Definition: Component.C:50
jevois::imu::get
Data collection mode RAW means that the latest available raw data is returned each time get() is called
jevois::UserInterface::readSome
virtual bool readSome(std::string &str)=0
Read some bytes if available, and return true and a string when one is complete (RETURN pressed)
jevois::OutputFrame
Exception-safe wrapper around a raw image to be sent over USB.
Definition: OutputFrame.H:52
jevois::Engine::foreachVideoMapping
void foreachVideoMapping(std::function< void(VideoMapping const &m)> &&func)
Run a function on every video mapping.
Definition: Engine.C:1502
jevois::Engine::quit
void quit()
Terminate the program.
Definition: Engine.C:1793
jevois::VideoDisplayGUI
Video output to local screen with basic GUI.
Definition: VideoDisplayGUI.H:36
Module.H
Async.H
PythonSupport.H
LDEBUG
#define LDEBUG(msg)
Convenience macro for users to print out console or syslog messages, DEBUG level.
Definition: Log.H:173
jevois::Manager
Manager of a hierarchy of Component objects.
Definition: Manager.H:73
JEVOIS_TIMED_LOCK
#define JEVOIS_TIMED_LOCK(mtx)
Helper macro to create a timed_lock_guard object.
Definition: Log.H:328
jevois::Gadget
JeVois gadget driver - exposes a uvcvideo interface to host computer connected over USB.
Definition: Gadget.H:65
jevois::split
std::vector< std::string > split(std::string const &input, std::string const &regex="\\s+")
Split string into vector of tokens using a regex to specify what to split on; default regex splits by...
Definition: Utils.C:270
jevois::UserInterface::Type::Hard
@ Hard
jevois::VideoDisplayGL
Video output to local screen.
Definition: VideoDisplayGL.H:50
jevois::PythonModule
Wrapper module to allow users to develop new modules written in Python.
Definition: PythonModule.H:359
jevois::getSysInfoMem
std::string getSysInfoMem()
Get memory info.
Definition: SysInfo.C:66
MovieInput.H
jevois::Component
A component of a model hierarchy.
Definition: Component.H:181
jevois::UserInterface::writeString
virtual void writeString(std::string const &str)=0
Write a string.
jevois::Watchdog
Simple watchdog class.
Definition: Watchdog.H:29
V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
#define V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE
jevois::Engine::camera
std::shared_ptr< Camera > camera() const
Get a pointer to our Camera (may be null, especially if not using a camera but, eg,...
Definition: Engine.C:1442
StdioInterface.H
jevois::MovieInput
Movie input, can be used as a replacement for Camera to debug algorithms using a fixed video sequence...
Definition: MovieInput.H:35
jevois::drawErrorImage
void drawErrorImage(std::string const &errmsg, RawImage &videoerrimg)
Display an error message into a RawImage.
Definition: Log.C:300
jevois::Manager::preInit
void preInit() override
Calls parseCommandLine()
Definition: Manager.C:49
jevois::Engine::numVideoMappings
size_t numVideoMappings() const
Return the number of video mappings.
Definition: Engine.C:1450
jevois::Engine::unRegisterPythonComponent
void unRegisterPythonComponent(Component *comp)
Unregister a component as linked to some python code, used by dynamic params created in python.
Definition: Engine.C:2930
jevois::VideoOutputNone
No-op VideoOutput derivative for when there is no video output.
Definition: VideoOutputNone.H:28
jevois::Engine::setFormat
void setFormat(size_t idx)
Callback for when the user selects a new output video format.
Definition: Engine.C:923
JEVOIS_TRACE
#define JEVOIS_TRACE(level)
Trace object.
Definition: Log.H:296
jevois::Engine::reboot
void reboot()
Request a reboot.
Definition: Engine.C:1772
LERROR
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level.
Definition: Log.H:211
jevois::Engine::getDefaultVideoMapping
const VideoMapping & getDefaultVideoMapping() const
Allow access to the default video mapping.
Definition: Engine.C:1494
jevois::DynamicLoader
Class to open shared object (.so) files and load functions contained in them.
Definition: DynamicLoader.H:69
jevois::getFileString
std::string getFileString(char const *fname, int skip=0)
Read one line from a file and return it as a string.
Definition: Utils.C:541
jevois::Serial::filePut
void filePut(std::string const &abspath)
Receive a file from the host and write it to the local microSD.
Definition: Serial.C:443
jevois::getSysInfoVersion
std::string getSysInfoVersion()
Get O.S. version info.
Definition: SysInfo.C:74
jevois
Definition: Concepts.dox:1
jevois::Manager::postInit
void postInit() override
Checks for the –help flag.
Definition: Manager.C:58
MovieOutput.H
F
float F
Definition: GUIhelper.C:2373
jevois::sensorHasIMU
bool sensorHasIMU(CameraSensor s)
Check whether sensor has an IMU (inertial measurement unit)
Definition: CameraSensor.C:279
jevois::Engine::streamOn
void streamOn()
Start streaming on video from camera, processing, and USB.
Definition: Engine.C:884
jevois::tolower
std::string tolower(std::string const &str)
Convert string to lowercase.
Definition: Utils.C:377
jevois::getSysInfoCPU
std::string getSysInfoCPU()
Get CPU info: frequency, thermal, load.
Definition: SysInfo.C:24
Engine.H
Log.H
jevois::system
std::string system(std::string const &cmd, bool errtoo=true)
Execute a command and grab stdout output to a string.
Definition: Utils.C:461
jevois::logSetEngine
void logSetEngine(Engine *e)
Set an Engine so that all log messages will be forwarded to its serial ports.
Definition: Log.C:124
jevois::Engine::clearErrors
void clearErrors()
Clear all errors currently displayed in the JeVois-Pro GUI.
Definition: Engine.C:1376
GUIconsole.H
SysInfo.H
jevois::Component::setParamVal
std::vector< std::string > setParamVal(std::string const &paramdescriptor, T const &val)
Set a parameter value.
jevois::Engine::findVideoMapping
const VideoMapping & 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:1510
success
#define success()
VideoOutputNone.H
jevois::Component::Engine
friend class Engine
Definition: Component.H:518
jevois::Engine::streamOff
void streamOff()
Stop streaming on video from camera, processing, and USB.
Definition: Engine.C:895
jevois::StdModule::sendSerialMarkStart
void sendSerialMarkStart()
Send a message MARK START to indicate the beginning of processing.
Definition: Module.C:520
jevois::Engine::nextDemo
void nextDemo()
When in demo mode, switch to next demo.
Definition: Engine.C:587
jevois::IMUspi
IMU with SPI interface, such as the ICM20948 IMU on the JeVois-Pro IMX290 camera sensor board.
Definition: IMUspi.H:28
jevois::Engine::requestSetFormat
void requestSetFormat(int idx)
Use this to request a format change from within process()
Definition: Engine.C:916
jevois::fccstr
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:45
Serial.H
jevois::warnAndIgnoreException
std::string warnAndIgnoreException(std::string const &prefix="")
Convenience function to catch an exception, issue some LERROR (depending on type),...
Definition: Log.C:236
jevois::Engine::getVideoMapping
const VideoMapping & getVideoMapping(size_t idx) const
Allow access to our video mappings which are parsed from file at construction.
Definition: Engine.C:1454
jevois::VideoDisplay
Video output to local screen.
Definition: VideoDisplay.H:34
jevois::Engine::runScriptFromFile
void runScriptFromFile(std::string const &filename, std::shared_ptr< UserInterface > ser, bool throw_no_file)
Run a script from file.
Definition: Engine.C:2705
jevois::Engine::reportError
void reportError(std::string const &err)
Definition: Engine.C:1367
LFATAL
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level.
IMUi2c.H
jevois::python::setEngine
void setEngine(jevois::Engine *e)
Initialize Python, numpy, and allow python modules to send serial outputs through the JeVois Engine.
Definition: PythonSupport.C:94
jevois::Engine::mainLoop
int mainLoop()
Main loop: grab, process, send over USB. Should be called by main application thread.
Definition: Engine.C:1079
jevois::Engine::onParamChange
void onParamChange(engine::serialdev const &param, std::string const &newval) override
Parameter callback.
jevois::stringStartsWith
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:294
jevois::Engine::imu
std::shared_ptr< IMU > imu() const
Get a pointer to our IMU (may be null)
Definition: Engine.C:1438
Camera.H
PythonModule.H
jevois::UserInterface::Type::USB
@ USB
jevois::Engine::getVideoMappingIdx
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:1463
jevois::to_string
std::string to_string(T const &val)
Convert from type to string.
jevois::InputFrame
Exception-safe wrapper around a raw camera input frame.
Definition: InputFrame.H:50
jevois::Component::freezeAllParams
void freezeAllParams()
Freeze all parameters.
Definition: Component.C:441
jevois::IMUi2c
IMU with I2C interface shared with camera sensor, such as ICM20948 on JeVois-A33 AR0135 camera sensor...
Definition: IMUi2c.H:28
jevois::StdModule::sendSerialMarkStop
void sendSerialMarkStop()
Send a message MARK STOP to indicate the end of processing.
Definition: Module.C:528
jevois::Engine::preInit
void preInit() override
Override of Manager::preInit()
Definition: Engine.C:604
jevois::Engine::registerPythonComponent
void registerPythonComponent(Component *comp, void *pyinst)
Register a component as linked to some python code, used by dynamic params created in python.
Definition: Engine.C:2920
jevois::Engine::getPythonComponent
Component * getPythonComponent(void *pyinst) const
Get the component registered with a given python instance.
Definition: Engine.C:2939
Utils.H
jevois::Engine::postInit
void postInit() override
Override of Manager::postInit()
Definition: Engine.C:628
IMUspi.H
DynamicLoader.H
jevois::Engine::parseCommand
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:1895
h
int h
Definition: GUIhelper.C:2373
jevois::Engine::abortDemo
void abortDemo()
When in demo mode, abort demo mode.
Definition: Engine.C:591
jevois::Engine::~Engine
~Engine()
Destructor.
Definition: Engine.C:814
jevois::Engine::sendSerial
void sendSerial(std::string const &str, bool islog=false)
Send a string to all serial ports.
Definition: Engine.C:1322
jevois::Engine::module
std::shared_ptr< Module > module() const
Get a pointer to our current module (may be null)
Definition: Engine.C:1434
jevois::Engine::reloadVideoMappings
void reloadVideoMappings()
Re-load video mappings from videomappings.cfg.
Definition: Engine.C:615
jevois::async_little
std::future< std::invoke_result_t< std::decay_t< Function >, std::decay_t< Args >... > > async_little(Function &&f, Args &&... args)
Async execution using a thread pool.
jevois::StdModule
Base class for a module that supports standardized serial messages.
Definition: Module.H:232
VideoDisplayGUI.H
jevois::Camera
JeVois camera driver class - grabs frames from a Video4Linux camera sensor.
Definition: Camera.H:62
jevois::Engine::getCurrentVideoMapping
const VideoMapping & getCurrentVideoMapping() const
Get the current video mapping.
Definition: Engine.C:1446
VideoDisplayGL.H
LINFO
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:194
jevois::strip
std::string strip(std::string const &str)
Strip white space (including CR, LF, tabs, etc) from the end of a string.
Definition: Utils.C:308
jevois::Serial::fileGet
void fileGet(std::string const &abspath)
Send a file from the local microSD to the host computer.
Definition: Serial.C:419
jevois::UserInterface::Type::GUI
@ GUI
IMU.H
jevois::UserInterface::type
virtual Type type() const =0
Derived classes must implement this and return their interface type.
GUIhelper.H
jevois::Engine::drawCameraGUI
void drawCameraGUI()
Draw all camera controls into our GUI.
Definition: Engine.C:2783
Gadget.H
VideoDisplay.H
V4L2_CTRL_CLASS_DETECT
#define V4L2_CTRL_CLASS_DETECT
Definition: Engine.C:62