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