From 6aa7df37fb0194dd74bb1797f896dba4737c77b9 Mon Sep 17 00:00:00 2001
From: am0ebe <am0ebe@gmx.de>
Date: Tue, 20 Feb 2024 17:33:02 +0100
Subject: [PATCH] [WIP] see below + not tested | IPrint interface + Cam class +
 ...

BIG 1. IPrint "interface" class containg signals,methods for printing to console + error handling
BIG 2. Cam class with signals,methods for camera control -> mv all cam stuff out of core -> core only handles cam list

- cam(int) as getter/setter. rm curCam,camIdx
- add setduration
- add getNumber + getLine to CMD
- fix destdir in profile
---
 obj/recorder-cmd.pro |   4 +-
 src/cam.cpp          | 233 ++++++++++++++++++++++++++++++++++
 src/cam.h            |  63 ++++++++++
 src/camobserver.cpp  |   2 -
 src/cmd/console.cpp  |  35 +++++-
 src/cmd/console.h    |   8 +-
 src/cmd/main.cpp     |  20 +--
 src/core.cpp         | 293 ++++++++++++++++---------------------------
 src/core.h           |  68 ++++------
 src/iprinter.cpp     |  65 ++++++++++
 src/iprinter.h       |  33 +++++
 src/utils.cpp        |  19 ++-
 src/utils.h          |  10 +-
 13 files changed, 603 insertions(+), 250 deletions(-)
 create mode 100644 src/cam.cpp
 create mode 100644 src/cam.h
 create mode 100644 src/iprinter.cpp
 create mode 100644 src/iprinter.h

diff --git a/obj/recorder-cmd.pro b/obj/recorder-cmd.pro
index b6ee423..26b22bd 100644
--- a/obj/recorder-cmd.pro
+++ b/obj/recorder-cmd.pro
@@ -29,7 +29,7 @@ QMAKE_CXXFLAGS += -Wno-deprecated-enum-enum-conversion # ignore opencv warnings.
 # cp ../b/vimbax/api/lib/libVmb{C,CPP,ImageTransform}.so $${VIMBA_LIB_DIR}
 
 TARGET = cmd
-DEST_DIR = ../bin
+DESTDIR = ../bin
 
-QMAKE_CLEAN += .cache/clangd/index/* .qmake.stash $${DEST_DIR}/*jpg
+QMAKE_CLEAN += .cache/clangd/index/* .qmake.stash $${DESTDIR}/*jpg
 #cant rm dir here, so added in sublime-project file
diff --git a/src/cam.cpp b/src/cam.cpp
new file mode 100644
index 0000000..b5f4f5e
--- /dev/null
+++ b/src/cam.cpp
@@ -0,0 +1,233 @@
+#include "cam.h"
+#include "VmbCPP/SharedPointerDefines.h"
+#include "utils.h"
+#include "frameobserver.h"
+
+#include <chrono>
+#include <memory> //shared_ptr
+#include <unistd.h>
+
+#include <QTimer>
+#include <QDebug>
+
+using State = Cam::State;
+using namespace VmbCPP;
+
+Cam::Cam(CameraPtr pCamera) :
+IPrinter(),
+_cam(pCamera),
+_name(""),
+_state(disconnected),
+_frames(FramePtrVector(threadsPerCam())),
+_payloadSize(0LL),
+_pixelFormat("")
+{
+	name();
+
+	//vmb (core) -> ui (controller.console)
+	// QObject::connect(&core, SIGNAL(error(const QString&, const int&)), controller.console, SLOT(error(const QString&, const int&)), Qt::DirectConnection);
+	// QObject::connect(&core, SIGNAL(error(const int&)), controller.console, SLOT(error(const int&)), Qt::DirectConnection);
+	// QObject::connect(&core, SIGNAL(info(const QString&)), controller.console, SLOT(print(const QString&)), Qt::DirectConnection);
+	// QObject::connect(&core, SIGNAL(info(const QStringList&)), controller.console, SLOT(print(const QStringList&)), Qt::DirectConnection);
+}
+
+// Copy constructor
+Cam::Cam(const Cam& other) :
+IPrinter(),
+_cam(other._cam),
+_name(other._name),
+_state(other._state),
+_frames(other._frames),
+_payloadSize(other._payloadSize),
+_pixelFormat(other._pixelFormat)
+{}
+
+Cam::~Cam()
+{
+	qDebug() << __FUNCTION__ << "():" << __LINE__ << "name:" << _name;
+	// if( _state == recording )
+		// stopRecording();
+
+	// if( _state == opened )
+		// close();
+	// close() should be called automatically, when the last shared_ptr to the camera is destroyed
+
+	// for ( auto cam : cameras )
+	// 	cam->Close();
+
+}
+
+
+QString Cam::name()
+{
+	if( _name.isEmpty() )
+	{
+		std::string str;
+		f(_cam->GetName( str ));
+		_name = QString::fromStdString(str);
+	}
+	return _name;
+}
+
+State Cam::state() const
+{
+	return _state;
+}
+
+bool Cam::open()
+{
+	if( ! f(_cam->Open( VmbAccessModeFull ),"open cam #"+_name) )
+		_state = opened;
+
+	return _state == opened;
+}
+
+void Cam::close()
+{
+	_state = closed;
+}
+
+/* Does:
+	get Payloadsize
+	get Pixelformat - eg RGB8
+	Allocate memory for frame buffer
+	Register frame observer / callback for each frame
+	Announce frame to the API
+	Start the capture engine (API)
+	Put frame into the frame queue
+	Start the acquisition engine ( _curCam)
+
+	TODO if( !_curCam->isOpen() ) return; >> listener
+	2d get frameID
+*/
+void Cam::startRecording(std::chrono::seconds dur)
+{
+	try
+	{
+		FeaturePtr pFeature;
+
+		g( _cam->GetFeatureByName ("PayloadSize", pFeature ),"get PayloadSize");
+		g( pFeature->GetValue (_payloadSize));
+		print("PayloadSize",_payloadSize);
+
+		g( _cam->GetFeatureByName("PixelFormat", pFeature));
+		g( pFeature->GetValue(_pixelFormat));
+		print("PixelFormat",_pixelFormat);
+
+		for( auto& frame : _frames )
+		{
+			frame.reset(new Frame(_payloadSize));
+			g( frame->RegisterObserver( IFrameObserverPtr( new FrameObserver(_cam) )),"register frame observer");
+			g( _cam->AnnounceFrame(frame), "announce frame");
+		}
+
+		g( _cam->StartCapture(), "start capture");
+
+		for( auto& frame : _frames )
+			g( _cam->QueueFrame(frame),"QueueFrame");
+
+		g( _cam->GetFeatureByName ("AcquisitionStart", pFeature ),"get AcquisitionStart");
+		g( pFeature->RunCommand(),"run AcquisitionStart");
+	}
+	catch (const std::runtime_error& xx)
+	{
+		qDebug() << "Exception caught: " << xx.what();
+	}
+
+	_state = recording;
+
+	// Convert to milliseconds
+	auto dur_ms = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+	QTimer::singleShot(dur_ms, this, SLOT(stopRecording()));
+}
+
+void Cam::startRecording(std::chrono::minutes dur)
+{
+	// Convert to seconds
+	auto dur_s = std::chrono::duration_cast<std::chrono::seconds>(dur);
+	startRecording(dur_s);
+}
+
+/* Does:
+	Stop the capture engine (API) + discard all pending callbacks
+	Flush the frame queue
+	Revoke all frames from the API
+	Unregister the frame observer / callback
+*/
+void Cam::stopRecording()
+{
+	FeaturePtr pFeature;
+	try
+	{
+		g(_cam->GetFeatureByName ("AcquisitionStop", pFeature ),"get AcquisitionStop");
+		g(pFeature->RunCommand(),"run AcquisitionStop");
+		g(_cam->EndCapture(),"end capture");
+		g(_cam->FlushQueue(),"flush queue");
+		g(_cam->RevokeAllFrames(),"revoke all frames");
+		for( FramePtr frame : _frames )
+			g(frame->UnregisterObserver(),"unregister observer");
+	}
+	catch (const std::runtime_error& xx)
+	{
+		qDebug() << "Exception caught: " << xx.what();
+	}
+
+	_state = opened;
+}
+
+// id,name,model,serial,interfaceID,state
+QString Cam::info()
+{
+	// qDebug() << "Camera Name: " << _name;
+	// qDebug() << "Camera State: " << _state;
+	QString camInfo;
+	try
+	{
+		std::string str;
+		g(_cam->GetID( str ));
+		camInfo += QString::fromStdString(str) + DELIM;
+
+		g(_cam->GetName( str ));
+		_name = QString::fromStdString(str);
+		camInfo += _name + DELIM;
+
+		g(_cam->GetModel( str ));
+		camInfo += QString::fromStdString(str) + DELIM;
+
+		g(_cam->GetSerialNumber( str ));
+		camInfo += QString::fromStdString(str) + DELIM;
+
+		g(_cam->GetInterfaceID( str ));
+		camInfo += QString::fromStdString(str) + DELIM;
+
+		camInfo += stateToString(_state);
+	}
+	catch(const std::exception& e)
+	{
+		qDebug() << __LINE__ << "-" << __PRETTY_FUNCTION__ << "exception:" << e.what();
+	}
+
+	return camInfo;
+}
+
+QString Cam::stateToString(const State& state)
+{
+	switch (state)
+	{
+		case disconnected:
+			return "disconnected";
+		case closed:
+			return "closed";
+		case opened:
+			return "opened";
+		case recording:
+			return "recording";
+		default:
+			return "unknown";
+	}
+}
+
+void Cam::onCameraDisconnected()
+{
+	_state = disconnected;
+}
diff --git a/src/cam.h b/src/cam.h
new file mode 100644
index 0000000..7ec7a19
--- /dev/null
+++ b/src/cam.h
@@ -0,0 +1,63 @@
+#pragma once
+#include "iprinter.h"
+
+#include <QObject>
+#include <QString>
+
+#include <chrono>
+#include <vector>
+#include <memory> //shared_ptr
+
+#include <VmbCPP/Camera.h>
+// #include <VmbCPP/SharedPointerDefines.h>
+
+using VmbCPP::CameraPtr;
+using VmbCPP::FramePtr;
+using VmbCPP::FeaturePtr;
+using VmbCPP::FramePtrVector;
+
+class Cam;
+using CamPtr = std::shared_ptr<Cam>;
+using CamPtrVector = std::vector<CamPtr>;
+
+class Cam : public IPrinter
+{
+	Q_OBJECT
+
+public:
+	enum State {
+		disconnected,
+		closed,
+		opened,
+		recording
+	};
+
+	// const QString& name, const int& nThreads
+	Cam(CameraPtr pCamera);
+	Cam(const Cam&);
+	~Cam();
+
+	QString name();
+	State state() const;
+	static QString stateToString(const State&);
+
+	bool open();
+	void close();
+	void startRecording(std::chrono::minutes dur);
+	void startRecording(std::chrono::seconds dur); //use as literals "1s, 1min"
+	void stopRecording();
+	QString info();
+	inline CameraPtr getCameraPtr() const { return _cam; }
+
+public slots:
+	void onCameraDisconnected();
+
+private:
+	CameraPtr _cam;
+	QString _name;
+	State _state;
+	FramePtrVector _frames;
+	VmbInt64_t _payloadSize;
+	std::string _pixelFormat;
+};
+
diff --git a/src/camobserver.cpp b/src/camobserver.cpp
index 7a86566..c24a2be 100644
--- a/src/camobserver.cpp
+++ b/src/camobserver.cpp
@@ -5,10 +5,8 @@
 
 #include <QDebug>
 
-
 using namespace VmbCPP;
 
-//2D XXX Code to determine the open state
 void CamObserver::CameraListChanged(CameraPtr pCam, UpdateTriggerType reason)
 {
 	if( UpdateTriggerPluggedIn == reason || UpdateTriggerPluggedOut == reason )
diff --git a/src/cmd/console.cpp b/src/cmd/console.cpp
index f86bf4d..bec3933 100644
--- a/src/cmd/console.cpp
+++ b/src/cmd/console.cpp
@@ -16,6 +16,7 @@ void Console::listenKeys()
 	std::string line;
 	forever
 	{
+
 		std::getline(std::cin, line);
 		emit keyPressed(line[0]);
 		if( line[0] == 'q' )
@@ -23,6 +24,26 @@ void Console::listenKeys()
 	}
 }
 
+// read line from cin
+QString Console::getLine()
+{
+	std::string line;
+	std::getline(std::cin, line);
+	// emit lineEntered(QString::fromStdString(line));
+	return QString::fromStdString(line);
+}
+
+// read number from cin
+int Console::getNumber()
+{
+	int number;
+	std::cin >> number;
+	// emit numberEntered(number);
+	return number;
+}
+
+
+
 void Console::error(const int& errCode )
 {
 	if( errCode )
@@ -68,14 +89,15 @@ void Console::printHelp()
 	str << "HELP!"
 	<< "-------------------------------"
 	<< "h: print this help"
-	<< "1-9: set camera"
+	<< "1-9: select camera"
+	<< "d: change duration of recording"
 	<< "o: open current camera"
-	// XXX add new func
 	<< "r: start recording"
 	<< "t: stop recording"
 	<< "l: list available cameras"
 	<< "v: print versions"
 	<< "q: quit"
+	// XXX add new func
 	<< "-------------------------------";
 
 	print(str);
@@ -106,10 +128,11 @@ void Controller::keyPress(const QChar& key)
 	qDebug().noquote() << __FUNCTION__ << ": " << key;
 	if ( key.isDigit() )
 	{
-		emit setCam(key.digitValue()-1);
+		emit cam(key.digitValue()-1);
 	}
 	else
 	{
+		int dur;
 		switch (key.unicode())
 		{
 			// XXX add new func
@@ -117,6 +140,12 @@ void Controller::keyPress(const QChar& key)
 				console->printHelp();
 				break;
 
+			case 'd':
+				console->print("Enter duration in seconds: ");
+				dur = console->getNumber(); // XXX not thread safe? + interferes with forever loop?
+				emit setDuration(dur);
+				break;
+
 			case 'o':
 				emit openCam();
 				break;
diff --git a/src/cmd/console.h b/src/cmd/console.h
index 8c30a81..b0d1c09 100644
--- a/src/cmd/console.h
+++ b/src/cmd/console.h
@@ -2,6 +2,7 @@
 
 #include <QObject>
 #include <QThread>
+#include <QString>
 
 // derived from qthread example
 // https://doc.qt.io/qt-6/qthread.html
@@ -12,6 +13,8 @@ class Console : public QObject
 
 	public slots:
 		void listenKeys();
+		QString getLine();
+		int getNumber();
 		void error(const QString&,const int&);
 		void error(const int&);
 		void print(const QString&);
@@ -23,6 +26,8 @@ class Console : public QObject
 
 	signals:
 		void keyPressed(const QChar &key);
+		// void lineEntered(const QString&);
+		// void numberEntered(const int&);
 
 };
 
@@ -46,9 +51,10 @@ class Controller : public QObject
 		// to core
 		void listCams();
 		void openCam();
-		void setCam(int);
+		void cam(int);
 		void startRecording();
 		void stopRecording();
+		void setDuration(int);
 
 		// void calibrateCam();
 		// void loadSettings();
diff --git a/src/cmd/main.cpp b/src/cmd/main.cpp
index 485267b..ff19440 100644
--- a/src/cmd/main.cpp
+++ b/src/cmd/main.cpp
@@ -1,6 +1,7 @@
 #include "console.h"
 #include "../core.h"
 #include "../utils.h"
+#include "qobjectdefs.h"
 
 // #include "qnamespace.h"
 #include <QCoreApplication>
@@ -17,23 +18,26 @@ int main(int argc, char *argv[])
 	Core core;
 
 	//ui (controller) -> vmb (core)
-	QObject::connect(&controller, &Controller::listCams, &core, &Core::listCams);
-	QObject::connect(&controller, &Controller::openCam, &core, &Core::openCam);
-	QObject::connect(&controller, &Controller::setCam, &core, &Core::setCam);
-	QObject::connect(&controller, &Controller::startRecording, &core, &Core::startRecording);
-	QObject::connect(&controller, &Controller::stopRecording, &core, &Core::stopRecording);
+	QObject::connect(&controller, &Controller::listCams,			&core, &Core::listCams);
+	QObject::connect(&controller, &Controller::openCam, 			&core, &Core::openCam);
+	QObject::connect(&controller, &Controller::cam, 				&core, &Core::cam);
+	QObject::connect(&controller, &Controller::startRecording, 	&core, &Core::startRecording);
+	QObject::connect(&controller, &Controller::stopRecording,	&core, &Core::stopRecording);
+	QObject::connect(&controller, &Controller::setDuration, 		&core, &Core::setDuration);
+
+
+
 	// XXX add new func
 
 	//vmb (core) -> ui (controller.console)
 	QObject::connect(&core, SIGNAL(error(const QString&, const int&)), controller.console, SLOT(error(const QString&, const int&)), Qt::DirectConnection);
 	QObject::connect(&core, SIGNAL(error(const int&)), controller.console, SLOT(error(const int&)), Qt::DirectConnection);
-	QObject::connect(&core, SIGNAL(print(const QString&)), controller.console, SLOT(print(const QString&)), Qt::DirectConnection);
-	QObject::connect(&core, SIGNAL(print(const QStringList&)), controller.console, SLOT(print(const QStringList&)), Qt::DirectConnection);
+	QObject::connect(&core, SIGNAL(info(const QString&)), controller.console, SLOT(print(const QString&)), Qt::DirectConnection);
+	QObject::connect(&core, SIGNAL(info(const QStringList&)), controller.console, SLOT(print(const QStringList&)), Qt::DirectConnection);
 	// sa Controller() for delegating to Console
 
 	QTimer::singleShot(0, &controller, SIGNAL(operate()));
 	// QTimer::singleShot(10, controller.console, SLOT(printVersion()), Qt::DirectConnection); // for testing
-	QTimer::singleShot(30, &core, SLOT(listCams())); // for testing
 	// QTimer::singleShot(200, &core, SLOT(openCam())); // for testing
 
 	return a.exec();
diff --git a/src/core.cpp b/src/core.cpp
index d8ef3ac..4ba5ace 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -1,8 +1,10 @@
 #include "core.h"
-#include "frameobserver.h"
+#include "VmbCPP/Interface.h"
+
 #include "camobserver.h"
 #include "utils.h"
 
+#include <chrono>
 #include <iostream>
 #include <vector>
 
@@ -17,259 +19,176 @@
 
 
 using namespace VmbCPP;
+using namespace std::chrono_literals; //seconds, minutes, hours
 
 Core::Core() :
-	apiStarted( false ),
-	sys( VmbSystem::GetInstance() ), // Create and get Vimba singleton
-	cameras( CameraPtrVector() ), // Holds camera handles
-	camIdx(-1),
-	curCam(nullptr),
-	payloadSize(0),
-	frames(FramePtrVector(3)),
-	// frames(FramePtrVector(QThread::idealThreadCount())),
-	pixelFormat("")
+	IPrinter(),
+	_apiStarted( false ),
+	_sys( VmbSystem::GetInstance() ), // create and get Vimba singleton
+	_cameras( CamPtrVector() ),
+	_recDuration( 6s ) //default
 {
 	QTimer::singleShot(0, this, SLOT(init())); //delayed init to allow connections to be established -> print/error signals!
 }
 
 void Core::init()
 {
-	if ( f(sys.Startup(), "init API") )
+	if ( f(_sys.Startup(), "init API") )
 		exit(VmbErrorApiNotStarted);
 
-	apiStarted = true;
+	_apiStarted = true;
 
 	auto camObserver = new CamObserver();
-	connect( camObserver, SIGNAL(cameraListChanged()), this, SLOT(listCams()) );
-	f(sys.RegisterCameraListObserver(ICameraListObserverPtr(camObserver)), "register cam observer");
+	connect( camObserver, SIGNAL(cameraListChanged()), this, SLOT(listCams()) ); //listcams calls updateCams!
+	f(_sys.RegisterCameraListObserver(ICameraListObserverPtr(camObserver)), "register cam observer");
+
+	listCams();
 }
 
 Core::~Core()
 {
 	qDebug() << __FUNCTION__ << "():" << __LINE__;
 
-	for ( auto cam : cameras )
-		cam->Close();
-
-	sys.Shutdown();
+	_sys.Shutdown();
 }
 
-void Core::listCams()
+// update & sync camera vectors
+void Core::updateCameras()
 {
-	if( !apiStarted )
+	if( !_apiStarted )
 		return;
 
-	if ( f(sys.GetCameras( cameras ), "get cameras") )
+	CameraPtrVector vmbCameras;
+	if ( f(_sys.GetCameras( vmbCameras ), "update cameras") )
 		return;
 
-	emitPrint( QString("found %1 camera(s)").arg(cameras.size()));
-	if (cameras.size())
+	if( (int)vmbCameras.size() != ncam() ) //update number of cameras
+		error(QString("Expected %1 cams!").arg(ncam()),errLessCamsThanExpected); //XXX warning
+
+	// vector< shared_ptr<Camera> > vmbCameras
+	// vector< shared_ptr<Cam> > _cameras
+
+	// Add new cameras
+	for (const auto& vcam : vmbCameras)
 	{
-		if( camIdx == -1UL || camIdx >= cameras.size() )
+		bool found = false;
+		for (const auto& cam : _cameras)
 		{
-			setCam(0);
+			if (cam->getCameraPtr() == vcam)
+			{
+				found = true;
+				break;
+			}
 		}
-
-		// id,name,model,serial,interfaceID,state
-		QStringList allCamInfo;
-		auto idx=0UL;
-		for ( auto camera : cameras  )
+		if (!found)
 		{
-			std::string str;
-			QString curCamInfo;
-
-			curCamInfo += QString::number(idx+1) + ". ";
-
-			if (idx == camIdx )
-				curCamInfo += "[*]" + DELIM;
-			else
-				curCamInfo += "[ ]" + DELIM;
-
-
-			if( f((camera->GetID( str )))) continue;
-			curCamInfo += QString::fromStdString(str) + DELIM;
-
-			if( f((camera->GetName( str )))) continue;
-			curCamInfo += QString::fromStdString(str) + DELIM;
-
-			if( f((camera->GetModel( str )))) continue;
-			curCamInfo += QString::fromStdString(str) + DELIM;
-
-			if( f((camera->GetSerialNumber( str )))) continue;
-			curCamInfo += QString::fromStdString(str) + DELIM;
-
-			if( f((camera->GetInterfaceID( str )))) continue;
-			curCamInfo += QString::fromStdString(str) + DELIM;
-
-			// curCamInfo += "closed";//xxx
-
-			allCamInfo << curCamInfo;
-			idx++;
+			_cameras.push_back(std::make_shared<Cam>(vcam));
+			print("cam connected");
+			//vcam->GetName(), threadsPerCam
 		}
-
-		emitPrint("found Cameras",cameras.size());
-		emit print(allCamInfo);
 	}
-	else
+
+	// Remove cameras that are no longer present
+	auto it = _cameras.begin();
+	while (it != _cameras.end())
 	{
-		emit error(errNoCams);
+		bool found = false;
+		for (const auto& vcam : vmbCameras)
+		{
+			if ((*it)->getCameraPtr() == vcam)
+			{
+				found = true;
+				break;
+			}
+		}
+		if (!found)
+		{
+			it = _cameras.erase(it);
+			print("cam disconnected");
+		}
+		else
+		{
+			++it;
+		}
 	}
-}
 
-bool Core::f(const VmbErrorType& ret, const QString& msg)
+	// Update states of cameras
+	// for (const auto& cam : _cameras) {
+		// Update state of 'cam' based on corresponding camera's state in 'vmbcameras'
+	// }
+
+} // now ~Camera() should be called on removed cams. >> camObserver
+
+// update + print camlist
+void Core::listCams()
 {
-	// vimba function wrapper to test return type and potentially emit error message or successmsg
-	// returns true on error
-	// qDebug() << __LINE__ << "-" << __PRETTY_FUNCTION__ << " ret:" << ret << " msg:" << msg;
+	if( !_apiStarted )
+		return;
 
-	if ( VmbErrorSuccess == ret )
-	{
-		if (! msg.isEmpty())
-			emitPrint(msg);
+	updateCameras();
 
-		return false;
-	}
-	else
+	QStringList allCamInfo;
+	auto idx=0;
+	for ( auto camera : _cameras  )
 	{
-		if (! msg.isEmpty())
-			emitPrint("Fail! " + msg);
-		emit error(ret);
-		return true;
+		QString camInfo = QString::number(++idx) + ". " + camera->info();
+		allCamInfo << camInfo;
 	}
-}
 
-void Core::g(const VmbErrorType& ret, const QString& msg)
-{
-	//vimba function wrapper - throws exception on error - to declutter error handling code
-	if( f(ret,msg) )
-		throw std::runtime_error("Error: " + msg.toStdString());
-}
-
-void Core::emitPrint(const QString& name, const VmbInt64_t& val=0)
-{
-	if( val != 0 )
-		emit print(QString("%1: %2").arg(name).arg(val));
-	else
-		emit print(name);
-}
+	if ( ! _cameras.size() )
+		emit error(errNoCams);
 
-void Core::emitPrint(const QString& name, const std::string& val)
-{
-	if( val.empty() )
-		emit print(name);
-	else
-		emit print(name + ": " + QString::fromStdString(val));
+	print(allCamInfo);
 }
 
 bool Core::openCam()
 {
-	if(!curCam)
-	{
-		emit error(errCamIdx);
-		return false;
-	}
+	if( cam() )
+		return cam()->open();
 
-	return f(curCam->Open( VmbAccessModeFull ),"open cam #"+QString::number(camIdx+1));
+	return false;
 }
 
-CameraPtr Core::cam()
+/*
+current camera getter + setter
+-1 is dflt and means "return current camera"
+other idx's update idx and return new curCam
+*/
+CamPtr Core::cam(const unsigned long &idx)
 {
-	return curCam;
-}
+	static unsigned long curIdx = 123;
+	if( idx != -1UL )
+		curIdx = idx;
 
-void Core::setCam(const int& newCamIdx)
-{
 	try
 	{
-		camIdx = newCamIdx;
-		curCam = cameras.at(newCamIdx);
-		emitPrint("set cam",newCamIdx+1);
+		print("set cam",idx+1);
+		return _cameras.at(curIdx);
 	}
 	catch (const std::out_of_range& oor)
 	{
 		emit error(errCamIdx);
+		return nullptr;
 	}
 }
 
-/* Does:
-	get Payloadsize
-	get Pixelformat - eg RGB8
-	Allocate memory for frame buffer
-	Register frame observer / callback for each frame
-	Announce frame to the API
-	Start the capture engine (API)
-	Put frame into the frame queue
-	Start the acquisition engine ( curCam)
-
-	TODO if( !curCam->isOpen() ) return; >> listener
-	2d get frameID
-*/
 void Core::startRecording()
 {
-	if( !curCam )
+	if( cam() == nullptr )
 		return;
 
-	FeaturePtr pFeature;
-
-	try
-	{
-		g( curCam->GetFeatureByName ("PayloadSize", pFeature ),"get PayloadSize");
-		g( pFeature->GetValue (payloadSize));
-		emitPrint("PayloadSize",payloadSize);
-
-		g( curCam->GetFeatureByName("PixelFormat", pFeature));
-		g( pFeature->GetValue(pixelFormat));
-		emitPrint("PixelFormat",pixelFormat);
-
-		for( auto& frame : frames )
-		{
-			frame.reset(new Frame(payloadSize));
-			g( frame->RegisterObserver( IFrameObserverPtr( new FrameObserver(curCam) )),"register frame observer");
-			g( curCam->AnnounceFrame(frame), "announce frame");
-		}
-
-		g( curCam->StartCapture(), "start capture");
-
-		for( auto& frame : frames )
-			g( curCam->QueueFrame(frame),"QueueFrame");
-
-		g( curCam->GetFeatureByName ("AcquisitionStart", pFeature ),"get AcquisitionStart");
-		g( pFeature->RunCommand(),"run AcquisitionStart");
-	}
-	catch (const std::runtime_error& xx)
-	{
-		// ... handle exception
-		qDebug() << "Exception caught: " << xx.what();
-	}
-
-	// QTimer::singleShot(6000, this, SLOT(stopRecording()));
+	cam()->startRecording( _recDuration ); //XXX dur from user input
 }
 
-/* Does:
-	Stop the capture engine (API) + discard all pending callbacks
-	Flush the frame queue
-	Revoke all frames from the API
-	Unregister the frame observer / callback
-*/
 void Core::stopRecording()
 {
-	if( !curCam )
+	if( cam() == nullptr )
 		return;
 
-	FeaturePtr pFeature;
-	try
-	{
-		g(curCam->GetFeatureByName ("AcquisitionStop", pFeature ),"get AcquisitionStop");
-		g(pFeature->RunCommand(),"run AcquisitionStop");
-		g(curCam->EndCapture(),"end capture");
-		g(curCam->FlushQueue(),"flush queue");
-		g(curCam->RevokeAllFrames(),"revoke all frames");
-		for( FramePtr frame : frames )
-			g(frame->UnregisterObserver(),"unregister observer");
-	}
-	catch (const std::runtime_error& xx)
-	{
-		// ... handle exception
-		qDebug() << "Exception caught: " << xx.what();
-	}
+	cam()->stopRecording();
+}
+
+void Core::setDuration(int dur)
+{
+	_recDuration = seconds(dur);
 }
diff --git a/src/core.h b/src/core.h
index 8607a0f..cb539ba 100644
--- a/src/core.h
+++ b/src/core.h
@@ -1,61 +1,43 @@
 #pragma once
 
-// #include "qglobal.h"
+#include "cam.h"
+#include "iprinter.h"
+
 #include <QObject>
 
-#include <VmbCPP/SharedPointerDefines.h>
 #include <VmbCPP/Interface.h>
-#include <VmbCPP/Camera.h>
+#include <chrono>
 
 namespace VmbCPP { class VmbSystem; }
 using VmbCPP::VmbSystem;
-using VmbCPP::CameraPtr;
-using VmbCPP::FeaturePtr;
-using VmbCPP::FramePtrVector;
-using VmbCPP::CameraPtrVector;
+using std::chrono::seconds;
 
-class Core : public QObject
+class Core : public IPrinter
 {
-	Q_OBJECT
-
-	public:
-		Core();
-		~Core();
-
-	public slots:
-		void listCams();
-		bool openCam();
-		void setCam(const int &);
-		CameraPtr cam(); // make getter setter -> rm curCam and camIdx //or make Camera class wrapper
-
-		void startRecording();
-		void stopRecording();
+Q_OBJECT
 
-	private slots:
-		void init();
+public:
+	Core();
+	~Core();
 
-	signals:
-		void print(const QString &);
-		void print(const QStringList&);
-		void error(const int&);
-		void error(const QString&,const int& errCode=0);
+public slots:
+	void listCams();
+	bool openCam();
 
-	private:
-		bool f(const VmbErrorType&, const QString& = "");
-		void g(const VmbErrorType&, const QString& = "");
+	CamPtr cam(const unsigned long &idx=-1);
 
-		void emitPrint(const QString&, const VmbInt64_t& v);
-		void emitPrint(const QString&, const std::string& v="");
+	void startRecording();
+	void stopRecording(); //XXX also stop singleshot-timer or it will try to close an already closed cam
+	void setDuration(int);
 
-		void getCamInfo( const CameraPtr & );
+private slots:
+	void init();
 
-		bool apiStarted;
-		VmbSystem& sys;
-		CameraPtrVector cameras;
-		ulong camIdx;
-		CameraPtr curCam;
+private:
+	void updateCameras();
 
-		VmbInt64_t payloadSize;
-		FramePtrVector frames;
-		std::string pixelFormat;
+	bool _apiStarted;
+	VmbSystem& _sys;
+	CamPtrVector _cameras;
+	seconds _recDuration;
 };
diff --git a/src/iprinter.cpp b/src/iprinter.cpp
new file mode 100644
index 0000000..5ba8515
--- /dev/null
+++ b/src/iprinter.cpp
@@ -0,0 +1,65 @@
+#include "iprinter.h"
+
+IPrinter::IPrinter(QObject *parent) : QObject(parent)
+{
+}
+
+IPrinter::~IPrinter()
+{
+}
+
+void IPrinter::print(const QStringList& name)
+{
+	emit info(name);
+}
+
+
+void IPrinter::print(const QString& name)
+{
+	emit info(name);
+}
+
+void IPrinter::print(const QString& name, const VmbInt64_t& val)
+{
+	if( val != 0 )
+		emit info(QString("%1: %2").arg(name).arg(val));
+	else
+		emit info(name);
+}
+
+void IPrinter::print(const QString& name, const std::string& val)
+{
+	if( val.empty() )
+		emit info(name);
+	else
+		emit info(name + ": " + QString::fromStdString(val));
+}
+
+bool IPrinter::f(const VmbErrorType& ret, const QString& msg)
+{
+	// vimba function wrapper to test return type and potentially emit error message or successmsg
+	// returns true on error
+	// qDebug() << __LINE__ << "-" << __PRETTY_FUNCTION__ << " ret:" << ret << " msg:" << msg;
+
+	if ( VmbErrorSuccess == ret )
+	{
+		if (! msg.isEmpty())
+			emit info(msg);
+
+		return false;
+	}
+	else
+	{
+		if (! msg.isEmpty())
+			emit info("Fail! " + msg);
+		emit error(ret);
+		return true;
+	}
+}
+
+void IPrinter::g(const VmbErrorType& ret, const QString& msg)
+{
+	//vimba function wrapper - throws exception on error - to declutter error handling code
+	if( f(ret,msg) )
+		throw std::runtime_error("Error: " + msg.toStdString());
+}
diff --git a/src/iprinter.h b/src/iprinter.h
new file mode 100644
index 0000000..8cc20b0
--- /dev/null
+++ b/src/iprinter.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "VmbC/VmbCommonTypes.h"
+
+#include <QObject>
+#include <QString>
+#include <QStringList>
+
+// common functionality "interface" for printing, error handling and connecting messages to CMD
+class IPrinter : public QObject
+{
+	Q_OBJECT
+
+signals:
+	void info(const QString &);
+	void info(const QStringList&);
+	void error(const int&);
+	void error(const QString&,const int& errCode=0);
+
+public:
+	explicit IPrinter(QObject *parent = nullptr);
+	IPrinter(const IPrinter&) = delete; //cuz of err with non-trivial copy ctor which cant be implicitly created... if cp'ing is needed implement it!
+	virtual ~IPrinter();
+
+	bool f(const VmbErrorType&, const QString& = ""); // check vmb error and print
+	void g(const VmbErrorType&, const QString& = ""); // check vmb error and throw
+
+	//print
+	void print(const QString&);
+	void print(const QStringList&);
+	void print(const QString&, const VmbInt64_t&);
+	void print(const QString&, const std::string&);
+};
diff --git a/src/utils.cpp b/src/utils.cpp
index 5eb357d..50085f8 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -1,21 +1,34 @@
 #include "utils.h"
+
 #include "opencv2/core/version.hpp"
 
 #include <random>
+#include <iostream>
 
 #include "VmbCPP/VmbCPP.h"
+
 #include <QString>
 #include <QDebug>
 #include <QDir>
+#include <QThread>
 
-#include <iostream>
 
 const int APP_VERSION_MAJOR = 0;
 const int APP_VERSION_MINOR = 0;
-const int APP_VERSION_PATCH = 8;
+const int APP_VERSION_PATCH = 9;
 
 using namespace VmbCPP;
 
+int ncam()
+{
+	return 1; //XXX read in from config file
+}
+
+int threadsPerCam()
+{
+	return std::ceil(static_cast<double>(QThread::idealThreadCount()) / ncam() ); //round up!
+}
+
 const QString getVersion()
 {
 	return QString("# Recorder: %1.%2.%3").arg(APP_VERSION_MAJOR).arg(APP_VERSION_MINOR).arg(APP_VERSION_PATCH);
@@ -65,6 +78,8 @@ const QString errorCodeToMessage( VmbError_t err )
 		case errCamIdx:                 msg += "Camera index out of range."; break;
 		case errNoCams:                 msg += "no cameras found."; break;
 		case errRegisterCamObserver:    msg += "cant register camobserver."; break;
+		case errLessCamsThanExpected:   msg += "less cameras found, than expected"; break;
+
 		// ... add more
 	}
 	return msg;
diff --git a/src/utils.h b/src/utils.h
index 84a68a8..d2cebb0 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -1,17 +1,22 @@
 #ifndef UTILS_H
 #define UTILS_H
 
-#include <QString>
 #include "VmbC/VmbCommonTypes.h" //VmbError_t -> cant really fwd-decl typedef...??
 
+#include <QString>
+
 class QDir;
 
+
 const QStringList getVersions();
 const QString getVersion();
 const QString errorCodeToMessage( VmbError_t );
 QString randomString();
 bool maybeCreateDir(QDir);
 
+int threadsPerCam();
+int ncam();
+
 const QChar DELIM='|';
 
 typedef enum camtronErr
@@ -19,7 +24,8 @@ typedef enum camtronErr
 	errCamIdx	=	10,		//!< camera index out of range
 	errNoCams	=	11,		//!< no cameras found
 	errRegisterCamObserver	=	12,		//!< cant register camobserver
-			// errUn		=	12,		//!< Unexpected fault in VmbC or driver
+	errLessCamsThanExpected	=	13,		//!< no cameras found
 } camtronErr;
 
+
 #endif
-- 
GitLab