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