summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikita Kostovsky <luntik2012@gmail.com>2025-01-12 17:57:45 +0100
committerNikita Kostovsky <luntik2012@gmail.com>2025-01-12 17:57:45 +0100
commit201d98f63131242bb8871ed0c4a3ae9ebd4ef030 (patch)
tree763a52710b3c8021c62b93535692b53de90265e5
parent4782e81c32392c78025aae1acb4b2ed1d1395908 (diff)
start refactoring. non-working commit
-rw-r--r--CMakeLists.txt6
-rw-r--r--imagealgos.cpp17
-rw-r--r--imagealgos.h2
-rw-r--r--macro.h2
-rw-r--r--main.cpp471
-rw-r--r--printerclient.cpp3
-rw-r--r--printerclient.h7
-rw-r--r--profile.cpp12
-rw-r--r--src/calibration.cpp28
-rw-r--r--src/calibration.h2
-rw-r--r--src/camera/ov9281.cpp385
-rw-r--r--src/camera/ov9281.h68
-rw-r--r--src/image.cpp110
-rw-r--r--src/image.h21
-rw-r--r--src/pixels.cpp7
-rw-r--r--src/pixels.h3
-rw-r--r--src/typedefs.h18
17 files changed, 837 insertions, 325 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 43cd7c7..9c1b165 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,9 +17,9 @@ set(CMAKE_SYSROOT ${TARGET_SYSROOT})
set(CMAKE_LIBRARY_PATH ${CMAKE_SYSROOT}/usr/lib/aarch64-linux-gnu)
# arch:
-# set(ENV{PKG_CONFIG_PATH} $PKG_CONFIG_PATH:/usr/lib/aarch64-linux-gnu/pkgconfig)
+set(ENV{PKG_CONFIG_PATH} $PKG_CONFIG_PATH:/usr/lib/aarch64-linux-gnu/pkgconfig)
# gentoo:
-set(ENV{PKG_CONFIG_PATH} $PKG_CONFIG_PATH:/usr/lib/aarch64-rpi5-linux-gnu/pkgconfig)
+# set(ENV{PKG_CONFIG_PATH} $PKG_CONFIG_PATH:/usr/lib/aarch64-rpi5-linux-gnu/pkgconfig)
set(ENV{PKG_CONFIG_LIBDIR} /usr/lib/pkgconfig:/usr/share/pkgconfig/:${TARGET_SYSROOT}/usr/lib/aarch64-linux-gnu/pkgconfig:${TARGET_SYSROOT}/usr/lib/pkgconfig)
set(ENV{PKG_CONFIG_SYSROOT_DIR} ${CMAKE_SYSROOT})
@@ -98,6 +98,8 @@ qt_add_executable(apporpheus
src/dumps.h src/dumps.cpp
src/calibration.h src/calibration.cpp
profile.h profile.cpp
+ src/camera/ov9281.h src/camera/ov9281.cpp
+ src/image.h src/image.cpp
)
target_link_libraries(app${PROJECT_NAME}
PRIVATE
diff --git a/imagealgos.cpp b/imagealgos.cpp
index 98d5fb0..6bd4d1e 100644
--- a/imagealgos.cpp
+++ b/imagealgos.cpp
@@ -94,23 +94,6 @@ void unpack_16bit(uint8_t const *src, Image const &image, uint16_t *dest)
stop_timer(unpack_16bit);
}
-void rotate(Image &image)
-{
- start_timer(rotate);
-
- using namespace std;
-
- for (size_t i = 0; i < img_height; ++i)
- {
- for (size_t j = 0; j < img_width; ++j)
- {
- image.rotated_cw[j][i] = image.data[img_height - i][j];
- }
- }
-
- stop_timer(rotate);
-}
-
template<class T, size_t N>
constexpr size_t mysize(T (&)[N]) { return N; }
diff --git a/imagealgos.h b/imagealgos.h
index 60844b8..4094f87 100644
--- a/imagealgos.h
+++ b/imagealgos.h
@@ -3,6 +3,7 @@
#include <QLineF>
#include <QList>
+#include "image.h"
#include "pixels.h"
size_t pgm_save(Image *img, FILE *outfile, bool really_save = false);
@@ -10,7 +11,6 @@ size_t pgm_save(Image *img, FILE *outfile, bool really_save = false);
void unpack_10bit(uint8_t const *src, Image const &image, uint16_t *dest);
void unpack_16bit(uint8_t const *src, Image const &image, uint16_t *dest);
-void rotate(Image & image);
Pixels process_columns(Image & image);
QList<QLineF> pixelsToLines(const Pixels& rawProfile);
diff --git a/macro.h b/macro.h
index 8be6df4..aeca79d 100644
--- a/macro.h
+++ b/macro.h
@@ -15,3 +15,5 @@
// << std::chrono::duration_cast<std::chrono::microseconds>( \
// end_ ## name - begin_ ## name) \
// << std::endl;
+
+#define INIT_FIELD(name) m_##name(name)
diff --git a/main.cpp b/main.cpp
index 431e82d..5f2ba82 100644
--- a/main.cpp
+++ b/main.cpp
@@ -9,6 +9,7 @@
#include "LibCamera.h"
#include "calibration.h"
+#include "camera/ov9281.h"
#include "dumps.h"
#include "fuck_intel.h"
#include "genetic_algos.h"
@@ -59,10 +60,10 @@ QMutex calibrationPixelsMutex;
using namespace std::chrono_literals;
-static std::shared_ptr<libcamera::Camera> camera;
-std::unique_ptr<libcamera::CameraConfiguration> config;
-static std::map<int, std::pair<void*, unsigned int>> mappedBuffers_;
-std::vector<std::unique_ptr<libcamera::Request>> requests;
+// static std::shared_ptr<libcamera::Camera> camera;
+// std::unique_ptr<libcamera::CameraConfiguration> config;
+// static std::map<int, std::pair<void*, unsigned int>> mappedBuffers_;
+// std::vector<std::unique_ptr<libcamera::Request>> requests;
libcamera::ControlList lastControls;
namespace
@@ -71,12 +72,12 @@ CalibrationTablePtr calibrationTableZ;
CalibrationTablePtr calibrationTableX;
} // namespace
-static bool applyConfig(
- const std::unique_ptr<libcamera::CameraConfiguration>& config
-);
-static void onRequestCompleted(libcamera::Request* completed_request);
-static void printControls();
-static QList<Pixels> filter(const QList<Pixels>& rawProfiles);
+// static bool applyConfig(
+// const std::unique_ptr<libcamera::CameraConfiguration>& config
+// );
+// static void onRequestCompleted(libcamera::Request* completed_request);
+// static void printControls();
+// static QList<Pixels> filter(const QList<Pixels>& rawProfiles);
auto printPixels = [](const auto& pixels) {
for (size_t i = (img_width - 10) / 2;
@@ -88,6 +89,21 @@ auto printPixels = [](const auto& pixels) {
std::cout << std::endl;
};
+void onNewPixels(std::shared_ptr<Pixels> pixels)
+{
+ if (!*pixels)
+ {
+ qDebug() << "got empty pixels";
+ }
+
+ ::pixels = *pixels;
+
+ if (!::pixels)
+ {
+ qDebug() << "empty pixels after copy";
+ }
+};
+
bool initLaser();
int main(int argc, char* argv[])
@@ -98,7 +114,8 @@ int main(int argc, char* argv[])
qDebug() << "size of raw profile" << sizeof(Pixels);
if (true)
{
- if (false) {
+ if (true)
+ {
// z
// if (!openCalibrationTable(
// "/home/user/dumps/binz.calibration_table",
@@ -166,14 +183,15 @@ int main(int argc, char* argv[])
std::cout << std::endl;
}
- exit(EXIT_SUCCESS);
+ // exit(EXIT_SUCCESS);
interpolate(::calibrationTableX);
// calibrationTableToImage(::calibrationTableX)
// .save("/home/user/dumps/imageX_interpolated.png");
}
- if (true) {
+ if (false)
+ {
auto rawProfiles = openDump("/home/user/dumps/binz");
// auto rawProfiles = openDump("/home/user/dumps/z");
qDebug() << "raw z-profiles count is" << rawProfiles.size();
@@ -211,7 +229,8 @@ int main(int argc, char* argv[])
qDebug() << "--------------------------------------------------------";
- if (true) {
+ if (false)
+ {
auto rawProfiles = openDump("/home/user/dumps/binx");
qDebug() << "raw x-profiles count is" << rawProfiles.size();
// qDebug() << "height" << calibrationColumnHeight;
@@ -269,27 +288,40 @@ int main(int argc, char* argv[])
std::make_unique<libcamera::CameraManager>();
cm->start();
- const auto cameras = cm->cameras();
-
- // openDump();
+ // const auto cameras = cm->cameras();
+ const auto cameras = OV9281::search(cm);
if (cameras.empty())
{
- std::cout << "No cameras were identified on the system." << std::endl;
+ std::cerr << "No cameras were identified on the system." << std::endl;
cm->stop();
return EXIT_FAILURE;
}
+ auto camera = cameras.at(0);
+
+ camera->printControls();
+
+ camera->newPixels.connect(&onNewPixels);
+
+ if (!camera->startStream())
+ {
+ cm->stop();
+
+ return EXIT_FAILURE;
+ }
+
+ /*
std::string cameraId = cameras[0]->id();
std::cout << "using " << cameraId << std::endl;
- /*
- * Note that `camera` may not compare equal to `cameras[0]`.
- * In fact, it might simply be a `nullptr`, as the particular
- * device might have disappeared (and reappeared) in the meantime.
- */
+ //
+ // Note that `camera` may not compare equal to `cameras[0]`.
+ // In fact, it might simply be a `nullptr`, as the particular
+ // device might have disappeared (and reappeared) in the meantime.
+ //
// std::shared_ptr<Camera> camera = cm->get(cameraId);
camera = cm->get(cameraId);
@@ -304,8 +336,7 @@ int main(int argc, char* argv[])
// FIXME: nullptr
// std::unique_ptr<CameraConfiguration> config =
// camera->generateConfiguration( { StreamRole::Viewfinder } );
- /*std::unique_ptr<CameraConfiguration> */ config =
- camera->generateConfiguration({libcamera::StreamRole::Raw});
+ config = camera->generateConfiguration({libcamera::StreamRole::Raw});
if (config->empty())
{
@@ -481,8 +512,7 @@ int main(int argc, char* argv[])
{
camera->queueRequest(request.get());
}
-
- printControls();
+*/
// std::this_thread::sleep_for(2s);
// TODO: move to thread
@@ -490,11 +520,13 @@ int main(int argc, char* argv[])
QHttpServer qHttpServer;
qHttpServer.route("/v1/sensor/image", [&]() {
+ std::cout << "http: image" << std::endl;
std::lock_guard<std::mutex> lg(pgm_image_mtx);
// qDebug() << "image";
return QByteArray((const char*)pgm_image, pgm_image_size);
});
qHttpServer.route("/v1/sensor/image2", [&]() {
+ std::cout << "http: image2" << std::endl;
std::lock_guard<std::mutex> lg(pgm_image_mtx);
// qDebug() << "image";
return QByteArray((const char*)pgm_image, pgm_image_size);
@@ -504,6 +536,7 @@ int main(int argc, char* argv[])
// return "123";
// });
qHttpServer.route("/v1/pixels", [&]() {
+ std::cout << "http: pixels" << std::endl;
std::lock_guard<std::mutex> lg(pgm_image_mtx);
QJsonArray pixels;
@@ -540,6 +573,7 @@ int main(int argc, char* argv[])
});
qHttpServer.route("/v1/profile", [&]() {
+ std::cout << "http: profile" << std::endl;
std::lock_guard<std::mutex> lg(pgm_image_mtx);
const Profile profile(::pixels,
@@ -554,7 +588,9 @@ int main(int argc, char* argv[])
qHttpServer
.route("/v1/commands/resetEncoder",
[&](const QHttpServerRequest& request) -> QHttpServerResponse {
- if (request.method() != QHttpServerRequest::Method::Post) {
+ std::cout << "http: resetEncoder" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
return QHttpServerResponse::StatusCode::NotFound;
}
@@ -565,91 +601,92 @@ int main(int argc, char* argv[])
return QHttpServerResponse::StatusCode::Ok;
});
- qHttpServer.route(
- "/v1/commands/startCalibration",
- [&](const QHttpServerRequest& request) -> QHttpServerResponse {
- if (request.method() != QHttpServerRequest::Method::Post)
- {
- return QHttpServerResponse::StatusCode::NotFound;
- }
+ qHttpServer
+ .route("/v1/commands/startCalibration",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: startCalibration" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
- qDebug() << "start calibration";
+ qDebug() << "start calibration";
- // TODO: use flags
- scanningModeFlags = ScanningModeFlags::Calibration;
- calibrationTimer.start();
+ // TODO: use flags
+ scanningModeFlags = ScanningModeFlags::Calibration;
+ calibrationTimer.start();
- return QHttpServerResponse::StatusCode::Ok;
- }
- );
+ return QHttpServerResponse::StatusCode::Ok;
+ });
- qHttpServer.route(
- "/v1/commands/gCode",
- [&](const QHttpServerRequest& request) -> QHttpServerResponse {
- if (request.method() != QHttpServerRequest::Method::Post)
- {
- return QHttpServerResponse::StatusCode::NotFound;
- }
+ qHttpServer
+ .route("/v1/commands/gCode",
+ [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: gCode" << std::endl;
+ if (request.method() != QHttpServerRequest::Method::Post)
+ {
+ return QHttpServerResponse::StatusCode::NotFound;
+ }
- const auto command = request.body();
+ const auto command = request.body();
- qDebug() << "send gCode:" << command;
+ qDebug() << "send gCode:" << command;
- // printerClient.sendCommand(command);
+ // printerClient.sendCommand(command);
- return QHttpServerResponse::StatusCode::Ok;
- }
- );
+ return QHttpServerResponse::StatusCode::Ok;
+ });
- qHttpServer.route(
- "/v1/commands/startCalibration",
- [&](const QHttpServerRequest& request) -> QHttpServerResponse {
- if (request.method() != QHttpServerRequest::Method::Post)
- {
- return QHttpServerResponse::StatusCode::NotFound;
- }
+ // qHttpServer
+ // .route("/v1/commands/startCalibration",
+ // [&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ // std::cout << "http: startCalibration" << std::endl;
+ // if (request.method() != QHttpServerRequest::Method::Post)
+ // {
+ // return QHttpServerResponse::StatusCode::NotFound;
+ // }
- const auto command = request.body();
+ // const auto command = request.body();
- qDebug() << "send gCode:" << command;
+ // qDebug() << "send gCode:" << command;
- // printerClient.sendCommand(command);
+ // // printerClient.sendCommand(command);
- return QHttpServerResponse::StatusCode::Ok;
- }
- );
+ // return QHttpServerResponse::StatusCode::Ok;
+ // });
qHttpServer.route(
"/v1/sensor/params",
[&](const QHttpServerRequest& request) -> QHttpServerResponse {
+ std::cout << "http: params" << std::endl;
switch (request.method())
{
case QHttpServerRequest::Method::Get: {
std::lock_guard<std::mutex> lg(pgm_image_mtx);
QJsonObject json;
- const libcamera::ControlIdMap& ctrlIdMap =
- camera->controls().idmap();
+ // const libcamera::ControlIdMap& ctrlIdMap =
+ // camera->controls().idmap();
- qDebug() << "readParams:" << lastControls.size();
- qDebug() << request.method();
+ // qDebug() << "readParams:" << lastControls.size();
+ // qDebug() << request.method();
- for (const auto& [id, value] : lastControls)
- {
- const libcamera::ControlId* controlId = ctrlIdMap.at(id);
- auto name = QString::fromStdString(controlId->name());
- const auto valueStr =
- QString::fromStdString(value.toString());
- qDebug()
- << "\t param:" << controlId->id() << name << valueStr;
+ // for (const auto& [id, value] : lastControls)
+ // {
+ // const libcamera::ControlId* controlId = ctrlIdMap.at(id);
+ // auto name = QString::fromStdString(controlId->name());
+ // const auto valueStr =
+ // QString::fromStdString(value.toString());
+ // qDebug()
+ // << "\t param:" << controlId->id() << name << valueStr;
- name[0] = name[0].toLower();
- json[name] = valueStr;
- }
+ // name[0] = name[0].toLower();
+ // json[name] = valueStr;
+ // }
- json[laserLevelKey] = requested_params.laserLevel;
+ // json[laserLevelKey] = requested_params.laserLevel;
- qDebug() << "response body:" << json;
+ // qDebug() << "response body:" << json;
// QHttpServerResponse
return QHttpServerResponse(QJsonDocument(json).toJson());
@@ -715,8 +752,7 @@ int main(int argc, char* argv[])
);
}
}
- }
- );
+ });
qDebug() << "listen: " << qHttpServer.listen(QHostAddress::Any, 8081);
@@ -745,97 +781,24 @@ int main(int argc, char* argv[])
future.cancel();
future.waitForFinished();
- for (auto& [fd, mem] : mappedBuffers_)
- {
- munmap(mem.first, mem.second);
- }
+ // for (auto& [fd, mem] : mappedBuffers_)
+ // {
+ // munmap(mem.first, mem.second);
+ // }
// FIXME: crash somewhere here. proper libcamera finishing needed
- requests.clear();
- mappedBuffers_.clear();
+ // requests.clear();
+ // mappedBuffers_.clear();
- camera->stop();
- config.reset();
- allocator->free(stream);
- allocator.reset();
- camera->release();
- camera.reset();
+ // camera->stop();
+ // config.reset();
+ // allocator->free(stream);
+ // allocator.reset();
+ // camera->release();
+ // camera.reset();
cm->stop();
return result;
-
- // time_t start_time = time(0);
- // int frame_count = 0;
-
- // LibCamera cam;
- // uint32_t width = 1280;
- // uint32_t height = 800;
- // uint32_t stride;
- // char key;
-
- // ret = cam.initCamera();
-
- // if (ret != EXIT_SUCCESS)
- // {
- // std::cerr << "cannot open camera" << std::endl;
-
- // return EXIT_FAILURE;
- // }
-
- // cam.configureStill(width, height, formats::R8, 1, 0);
- // // ControlList controls_;
- // int64_t frame_time = 1000000 / 10;
- // // Set frame rate
- // // controls_.set( controls::FrameDurationLimits, libcamera::Span<const
- // int64_t, 2>(
- // // { frame_time, frame_time } ));
- // // Adjust the brightness of the output images, in the range -1.0 to 1.0
- // // controls_.set(controls::Brightness, 0.5);
- // // Adjust the contrast of the output image, where 1.0 = normal contrast
- // // controls_.set(controls::Contrast, 1.5);
- // // Set the exposure time
- // // controls_.set(controls::ExposureTime, 20000);
- // // cam.set(controls_);
-
- // std::cout << std::flush;
-
- // // NOTE: already checked
- // if (ret == EXIT_SUCCESS) {
- // bool flag;
- // LibcameraOutData frameData;
- // cam.startCamera();
- // cam.VideoStream(&width, &height, &stride);
-
- // while (true) {
- // flag = cam.readFrame(&frameData);
- // if (!flag)
- // continue;
-
- // // key = waitKey(1);
- // // if (key == 'q') {
- // // break;
- // // } else if (key == 'f') {
- // // ControlList controls;
- // // controls.set(controls::AfMode, controls::AfModeAuto);
- // // controls.set(controls::AfTrigger, 0);
- // // cam.set(controls);
- // // }
-
- // frame_count++;
- // if ((time(0) - start_time) >= 1){
- // printf("fps: %d\n", frame_count);
- // frame_count = 0;
- // start_time = time(0);
- // }
- // cam.returnFrameBuffer(frameData);
- // }
-
- // cam.stopCamera();
- // }
-
- // cam.closeCamera();
-
- // return EXIT_SUCCESS;
}
/*
@@ -843,6 +806,7 @@ int main(int argc, char* argv[])
* important not to block the thread for a long time, as this blocks internal
* processing of the camera pipelines, and can affect realtime performance.
*/
+/*
void onRequestCompleted(libcamera::Request* completed_request)
{
using namespace libcamera;
@@ -879,13 +843,6 @@ void onRequestCompleted(libcamera::Request* completed_request)
const FrameMetadata& metadata = buffer->metadata();
- // if (verbose)
- // {
- // std::cout << " seq: " << std::setw(6) << std::setfill('0')
- // << metadata.sequence
- // << " bytesused: ";
- // }
-
for (size_t i = 0; i < buffer->planes().size(); ++i)
{
const FrameBuffer::Plane& plane = buffer->planes()[i];
@@ -913,7 +870,7 @@ void onRequestCompleted(libcamera::Request* completed_request)
// unpack_16bit((uint8_t*)img.data, img, (uint16_t*)&unpacked);
// img.data = unpacked;
// img.dataSize = img.width * img.height * sizeof(uint16_t);
- rotate(img);
+ img.rotate();
Pixels pixels = process_columns(img);
::pixels = pixels;
@@ -1040,120 +997,72 @@ void onRequestCompleted(libcamera::Request* completed_request)
// qDebug() << "-------------------------------------------";
}
+*/
-static bool applyConfig(
- const std::unique_ptr<libcamera::CameraConfiguration>& config
-)
-{
- using namespace libcamera;
-
- auto status = config->validate();
-
- // WARNING: unsafe
- libcamera::StreamConfiguration& streamConfig = config->at(0);
-
- switch (status)
- {
- case CameraConfiguration::Status::Valid:
- std::cout << "config is valid" << std::endl;
- break;
- case CameraConfiguration::Status::Adjusted:
- std::cout << "\tpixelFormat: " << streamConfig.pixelFormat.toString()
- << std::endl;
- std::cout << "\tbufferCount: " << streamConfig.bufferCount << std::endl;
- std::cout << "\torientation: " << config->orientation << std::endl;
- break;
- case CameraConfiguration::Status::Invalid:
- std::cout << "config is invalid, quit." << std::endl;
-
- return false;
- }
-
- return true;
-}
-
-static void printControls()
-{
- using namespace libcamera;
- const libcamera::ControlInfoMap& control_map = camera->controls();
+// static bool applyConfig(
+// const std::unique_ptr<libcamera::CameraConfiguration>& config
+// )
+// {
+// using namespace libcamera;
- // for (const auto & [id, info]: control_map)
- for (const std::pair<const ControlId*, ControlInfo>& pair : control_map)
- {
- const ControlId* const& id = pair.first;
- const ControlInfo& info = pair.second;
+// auto status = config->validate();
- std::cout << "\tc " << id->name() << " (" << id->id()
- << "): " << info.toString()
- << (info.def().isNone()
- ? ""
- : " (dflt:" + info.def().toString() + ")");
+// // WARNING: unsafe
+// libcamera::StreamConfiguration& streamConfig = config->at(0);
- if (!info.values().size())
- {
- std::cout << std::endl;
- continue;
- }
+// switch (status)
+// {
+// case CameraConfiguration::Status::Valid:
+// std::cout << "config is valid" << std::endl;
+// break;
+// case CameraConfiguration::Status::Adjusted:
+// std::cout << "\tpixelFormat: " << streamConfig.pixelFormat.toString()
+// << std::endl;
+// std::cout << "\tbufferCount: " << streamConfig.bufferCount << std::endl;
+// std::cout << "\torientation: " << config->orientation << std::endl;
+// break;
+// case CameraConfiguration::Status::Invalid:
+// std::cout << "config is invalid, quit." << std::endl;
- std::cout << " - [";
+// return false;
+// }
- for (const auto& v : info.values())
- {
- std::cout << " " << v.toString();
- }
+// return true;
+// }
- std::cout << " ]\n";
- }
-}
-
-static QList<Pixels> filter(const QList<Pixels>& rawProfiles)
-{
- QList<Pixels> result;
+// static void printControls()
+// {
+// using namespace libcamera;
+// const libcamera::ControlInfoMap& control_map = camera->controls();
- QList<Pixels>::const_iterator it = rawProfiles.constBegin();
+// // for (const auto & [id, info]: control_map)
+// for (const std::pair<const ControlId*, ControlInfo>& pair : control_map)
+// {
+// const ControlId* const& id = pair.first;
+// const ControlInfo& info = pair.second;
- // for (size_t i = 0; i < 10; ++i)
- // {
- // std::cout << "pos - " << rawProfiles.at(i).counters.encoderPosition
- // << std::endl;
- // }
+// std::cout << "\tc " << id->name() << " (" << id->id()
+// << "): " << info.toString()
+// << (info.def().isNone()
+// ? ""
+// : " (dflt:" + info.def().toString() + ")");
- // ++it;
- while (it != rawProfiles.constEnd())
- {
- Pixels sum = *it;
- // std::cout << "current pos is " << sum.counters.encoderPosition
- // << std::endl;
+// if (!info.values().size())
+// {
+// std::cout << std::endl;
+// continue;
+// }
- // printPixels(sum.pixels);
+// std::cout << " - [";
- size_t count{1};
- ++it;
+// for (const auto& v : info.values())
+// {
+// std::cout << " " << v.toString();
+// }
- // std::cout << "here\n";
- while (it != rawProfiles.constEnd() &&
- it->counters.encoderPosition == sum.counters.encoderPosition)
- {
- // std::cout << "here2\n";
- // std::cout << "\tadd to pos " << it->counters.encoderPosition
- // << std::endl;
- sum += *it;
- // std::cout << "\tadded" << std::endl;
- ++count;
- ++it;
- }
-
- // std::cout << "here3\n";
- sum /= float(count);
- // printPixels(sum.pixels);
-
- result << sum;
-
- // return result;
- }
-
- return result;
-}
+// std::cout << " ]\n";
+// }
+// }
bool initLaser()
{
diff --git a/printerclient.cpp b/printerclient.cpp
index ac269b3..4fed38f 100644
--- a/printerclient.cpp
+++ b/printerclient.cpp
@@ -19,8 +19,7 @@ QString getFirstTtyUSB()
}
PrinterClient::PrinterClient(QObject* parent)
- : QObject{parent}
- // , m_serialPort{new QSerialPort{"/dev/ttyUSB0", this}}
+ : QObject{parent} // , m_serialPort{new QSerialPort{"/dev/ttyUSB0", this}}
, m_serialPort{new QSerialPort{getFirstTtyUSB(), this}}
{
if (!m_serialPort->setBaudRate(QSerialPort::Baud115200)) {
diff --git a/printerclient.h b/printerclient.h
index 08bf0c2..d1266dd 100644
--- a/printerclient.h
+++ b/printerclient.h
@@ -6,8 +6,7 @@
class QSerialPort;
-class PrinterClient
- : public QObject
+class PrinterClient : public QObject
{
// Q_OBJECT
@@ -16,8 +15,8 @@ public:
// ~PrinterClient() override = default;
// ~PrinterClient
-// signals:
-// void newData(const QString output);
+ // signals:
+ // void newData(const QString output);
public:
void sendCommand(const QString command);
diff --git a/profile.cpp b/profile.cpp
index 31d88cd..0c4967c 100644
--- a/profile.cpp
+++ b/profile.cpp
@@ -11,6 +11,14 @@ Profile::Profile(
const CalibrationTablePtr calibrationTableX)
: m_counters(pixels.counters)
{
+ if (!calibrationTableZ || !calibrationTableX)
+ {
+ std::cerr << __func__ << ": got invalid calibration tables"
+ << std::endl;
+
+ return;
+ }
+
static bool done{false};
if (!done) {
@@ -71,8 +79,8 @@ Profile::Profile(
// TODO: use only NaN (or zero?) everywhere
// NOTE: QJsonValue converts NaN to zero
if (qFuzzyIsNull(z) || std::isnan(z)) {
- qDebug() << "got nan z for discrete" << pixelDiscrete << leftMmZ
- << rightMmZ;
+ // qDebug() << "got nan z for discrete" << pixelDiscrete << leftMmZ
+ // << rightMmZ;
m_pointsMm.at(i) = {std::nan(""), std::nan("")};
continue;
}
diff --git a/src/calibration.cpp b/src/calibration.cpp
index 73bd786..ff37e73 100644
--- a/src/calibration.cpp
+++ b/src/calibration.cpp
@@ -191,6 +191,34 @@ QImage calibrationTableToImage(const CalibrationTablePtr& calibrationTable)
return result;
}
+QList<Pixels> filter(const QList<Pixels>& rawProfiles)
+{
+ QList<Pixels> result;
+
+ QList<Pixels>::const_iterator it = rawProfiles.constBegin();
+
+ while (it != rawProfiles.constEnd())
+ {
+ Pixels sum = *it;
+
+ size_t count{1};
+ ++it;
+
+ while (it != rawProfiles.constEnd() &&
+ it->counters.encoderPosition == sum.counters.encoderPosition)
+ {
+ sum += *it;
+ ++count;
+ ++it;
+ }
+
+ sum /= float(count);
+ result << sum;
+ }
+
+ return result;
+}
+
CalibrationTablePtr calibrateZ(const QList<Pixels>& rawProfiles,
const uint32_t& stepsPerMm)
{
diff --git a/src/calibration.h b/src/calibration.h
index ddb66a6..c2e5d73 100644
--- a/src/calibration.h
+++ b/src/calibration.h
@@ -21,6 +21,8 @@ bool openCalibrationTable(const QString &filename, CalibrationTablePtr &table);
void dumpCalibrationPixels(std::vector<Pixels> &&calibrationPixels);
bool dump(const CalibrationTablePtr &table, const QString &filename);
+QList<Pixels> filter(const QList<Pixels> &rawProfiles);
+
CalibrationTablePtr calibrateX(const QList<Pixels> &rawProfiles);
CalibrationTablePtr calibrateZ(const QList<Pixels> &rawProfiles,
const uint32_t &stepsPerMm);
diff --git a/src/camera/ov9281.cpp b/src/camera/ov9281.cpp
new file mode 100644
index 0000000..bdb9f89
--- /dev/null
+++ b/src/camera/ov9281.cpp
@@ -0,0 +1,385 @@
+#include "ov9281.h"
+
+#include <iostream>
+#include <thread>
+
+#include <string.h>
+#include <sys/mman.h>
+
+#include <libcamera/camera.h>
+#include <libcamera/camera_manager.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/framebuffer_allocator.h>
+#include <libcamera/request.h>
+
+#include "image.h"
+#include "macro.h"
+#include "pixels.h"
+#include "rotaryencoder.h"
+#include "typedefs.h"
+
+OV9281::OV9281(const std::shared_ptr<libcamera::Camera> &camera)
+ : INIT_FIELD(camera)
+{
+ std::cout << __func__ << ":\tid: " << m_camera->id();
+}
+
+OV9281::~OV9281()
+{
+ for (auto &[fd, mem] : m_mappedBuffers)
+ {
+ munmap(mem.first, mem.second);
+ }
+
+ m_camera->release();
+}
+
+bool OV9281::init()
+{
+ if (m_camera->acquire() != EXIT_SUCCESS)
+ {
+ std::cerr << __func__ << ": " << m_camera->id()
+ << ": cannot acquire camera." << std::endl;
+
+ return false;
+ }
+
+ m_config = m_camera->generateConfiguration({libcamera::StreamRole::Raw});
+
+ if (m_config->empty())
+ {
+ std::cerr << __func__ << ": " << m_camera->id()
+ << ": cannot generate configuration" << std::endl;
+
+ return false;
+ }
+
+ m_config->orientation = libcamera::Orientation::Rotate90;
+
+ if (m_config->empty())
+ {
+ std::cerr << __func__ << ": " << m_camera->id() << ": config is empty"
+ << std::endl;
+
+ return false;
+ }
+
+ libcamera::StreamConfiguration &streamConfig = m_config->at(0);
+
+ streamConfig.pixelFormat = OV9281::pixelFormat;
+ streamConfig.bufferCount = OV9281::bufferCount;
+
+ if (!validateConfig())
+ {
+ std::cerr << __func__ << ": " << m_camera->id()
+ << ": cannot apply default config" << std::endl;
+
+ return false;
+ }
+
+ return true;
+}
+
+bool OV9281::validateConfig()
+{
+ using namespace libcamera;
+
+ auto status = m_config->validate();
+
+ // WARNING: unsafe
+
+ switch (status)
+ {
+ case CameraConfiguration::Status::Valid: {
+ std::cout << __func__ << ": " << "config is valid" << std::endl;
+ break;
+ }
+ case CameraConfiguration::Status::Adjusted: {
+ if (m_config->empty())
+ {
+ std::cerr << __func__ << ": " << "config is adjusted, but empty"
+ << std::endl;
+ return false;
+ }
+
+ libcamera::StreamConfiguration &streamConfig = m_config->at(0);
+ std::cout << __func__
+ << ":\tpixelFormat: " << streamConfig.pixelFormat.toString()
+ << std::endl;
+ std::cout << __func__ << ":\tbufferCount: " << streamConfig.bufferCount
+ << std::endl;
+ std::cout << __func__ << ":\torientation: " << m_config->orientation
+ << std::endl;
+ break;
+ }
+ case CameraConfiguration::Status::Invalid: {
+ std::cerr << __func__ << ":\tconfig is invalid" << std::endl;
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool OV9281::applyConfig()
+{
+ // FIXME: may crassh even on success (e.g. by setting pixelFormat to "8")
+ if (m_camera->configure(m_config.get()) != EXIT_SUCCESS)
+ {
+ std::cerr << __func__ << ":\tcannot apply config" << std::endl;
+
+ return false;
+ }
+
+ return true;
+}
+
+void OV9281::onRequestCompleted(libcamera::Request *completed_request)
+{
+ using namespace libcamera;
+
+ if (completed_request->status() == Request::RequestCancelled)
+ {
+ std::cerr << __func__ << ":\trequest canceled" << std::endl;
+
+ return;
+ }
+
+ const auto &buffers = completed_request->buffers();
+
+ for (auto [stream, buffer] : buffers)
+ {
+ const auto &streamConfig = stream->configuration();
+ const auto &imageSize = streamConfig.size;
+ const auto &pixelFormat = streamConfig.pixelFormat;
+ const auto &stride = streamConfig.stride;
+
+ const FrameMetadata &metadata = buffer->metadata();
+
+ for (size_t i = 0; i < buffer->planes().size(); ++i)
+ {
+ const FrameBuffer::Plane &plane = buffer->planes()[i];
+ const FrameMetadata::Plane &metaplane = buffer->metadata()
+ .planes()[i];
+
+ size_t size = std::min(metaplane.bytesused, plane.length);
+ void *data = m_mappedBuffers[plane.fd.get()].first;
+
+ auto img = std::make_shared<Image>();
+
+ img->width = imageSize.width;
+ img->height = imageSize.height;
+
+ memcpy(img->data, data, size);
+ img->dataSize = size;
+ img->stride = stride;
+ img->pixelFormat = pixelFormat;
+ img->counters.measurementCounter = metadata.sequence;
+ img->counters.timestampUs = metadata.timestamp / 1000;
+ img->counters.encoderPosition = RotaryEncoder::instance()->position();
+
+ img->rotate();
+
+ auto pixels = img->pixels();
+#ifdef emit
+#undef emit
+ newPixels.emit(pixels);
+#define emit
+#endif
+ }
+ }
+
+ const libcamera::ControlList &metadata = completed_request->metadata();
+ const ControlInfoMap &control_map = m_camera->controls();
+ // const ControlIdMap & ctrlIdMap = control_map.idmap();
+
+ auto frameDurationCtrl = control_map.find(&controls::FrameDurationLimits);
+ double fps = frameDurationCtrl == control_map.end() ?
+ std::numeric_limits<double>::quiet_NaN() :
+ (1e6 / frameDurationCtrl->second.min().get<int64_t>());
+
+ auto exp = metadata.get(controls::ExposureTime);
+ auto ag = metadata.get(controls::AnalogueGain);
+ auto ae = metadata.get(controls::AeEnable);
+ // auto br= metadata.get(controls::Brightness);
+ static auto lastControls = completed_request->controls();
+
+ completed_request->reuse(Request::ReuseBuffers);
+ completed_request->controls().set(libcamera::controls::AeEnable, false);
+ completed_request->controls()
+ .set(libcamera::controls::draft ::NoiseReductionMode,
+ libcamera::controls::draft ::NoiseReductionModeEnum ::
+ NoiseReductionModeHighQuality);
+
+ completed_request->controls().set(libcamera::controls::ExposureTime,
+ m_exposureTime);
+
+ m_camera->queueRequest(completed_request);
+}
+
+std::vector<std::shared_ptr<OV9281>> OV9281::search(
+ std::unique_ptr<libcamera::CameraManager> &manager)
+{
+ std::vector<std::shared_ptr<OV9281>> result;
+
+ for (const auto &camera : manager->cameras())
+ {
+ auto ov9281 = std::shared_ptr<OV9281>(new OV9281(camera));
+
+ if (!ov9281->init())
+ {
+ continue;
+ }
+
+ result.push_back(ov9281);
+ }
+
+ return result;
+}
+
+bool OV9281::startStream()
+{
+ if (m_config->empty())
+ {
+ std::cerr << __func__ << ":\tconfig is empty" << std::endl;
+ return false;
+ }
+
+ auto &streamConfig = m_config->at(0);
+ m_allocator = std::make_unique<libcamera::FrameBufferAllocator>(m_camera);
+ auto stream = streamConfig.stream();
+ auto ret = m_allocator->allocate(stream);
+
+ // TODO: check if zero
+ if (ret < 0)
+ {
+ std::cerr << __func__ << ":\tcan't allocate buffers: " << strerror(ret)
+ << std::endl;
+ return false;
+ }
+
+ auto allocatedCount = ret;
+ std::cout << __func__ << ":\tallocated " << allocatedCount
+ << " buffers for stream" << std::endl;
+
+ const auto &buffers = m_allocator->buffers(stream);
+
+ for (const auto &buffer : buffers)
+ {
+ auto request = m_camera->createRequest();
+
+ if (!request)
+ {
+ std::cerr << __func__ << ":\tcan't create request" << std::endl;
+
+ return false;
+ }
+
+ // TODO: try multiple buffers per request and compare performance
+ ret = request->addBuffer(stream, buffer.get());
+
+ if (ret < 0)
+ {
+ std::cerr << __func__
+ << ":\tcan't set buffer for request: " << strerror(ret)
+ << std::endl;
+
+ return false;
+ }
+
+ for (const auto &plane : buffer->planes())
+ {
+ void *memory = mmap(NULL,
+ plane.length,
+ PROT_READ,
+ MAP_SHARED,
+ plane.fd.get(),
+ 0);
+ m_mappedBuffers[plane.fd.get()] = std::make_pair(memory,
+ plane.length);
+ }
+
+ std::int64_t lowerUS = 1 * 1000 * 1000 / desiredFPS;
+ std::int64_t higherUS = lowerUS;
+ std::int64_t value_pair[2] = {higherUS / 2, higherUS};
+ request->controls().set(libcamera::controls::AnalogueGain, 1.0);
+ request->controls().set(libcamera::controls::ExposureTime, 100);
+ request->controls().set(libcamera::controls::FrameDurationLimits,
+ libcamera::Span<const std::int64_t, 2>(
+ value_pair));
+
+ m_requests.push_back(std::move(request));
+ }
+
+ m_camera->requestCompleted.connect(this, &OV9281::onRequestCompleted);
+
+ // FIXME: memleak
+ std::unique_ptr<libcamera::ControlList> camcontrols{
+ new libcamera::ControlList()};
+
+ {
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(500ms);
+ }
+
+ ret = m_camera->start(camcontrols.get());
+
+ if (ret)
+ {
+ std::cerr << __func__ << ":\tfailed to start camera: " << strerror(ret)
+ << std::endl;
+
+ return false;
+ }
+
+ for (auto &request : m_requests)
+ {
+ ret = m_camera->queueRequest(request.get());
+
+ if (ret)
+ {
+ std::cerr << __func__
+ << ":\tfailed to queue request: " << strerror(ret)
+ << std::endl;
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void OV9281::printControls()
+{
+ using namespace libcamera;
+ const libcamera::ControlInfoMap &control_map = m_camera->controls();
+
+ // for (const auto & [id, info]: control_map)
+ for (const std::pair<const ControlId *, ControlInfo> &pair : control_map)
+ {
+ const ControlId *const &id = pair.first;
+ const ControlInfo &info = pair.second;
+
+ std::cout << "\tc " << id->name() << " (" << id->id()
+ << "): " << info.toString()
+ << (info.def().isNone() ?
+ "" :
+ " (dflt:" + info.def().toString() + ")");
+
+ if (!info.values().size())
+ {
+ std::cout << std::endl;
+ continue;
+ }
+
+ std::cout << " - [";
+
+ for (const auto &v : info.values())
+ {
+ std::cout << " " << v.toString();
+ }
+
+ std::cout << " ]\n";
+ }
+}
diff --git a/src/camera/ov9281.h b/src/camera/ov9281.h
new file mode 100644
index 0000000..f70db2f
--- /dev/null
+++ b/src/camera/ov9281.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <libcamera/base/signal.h>
+#include <libcamera/formats.h>
+
+namespace libcamera {
+class Camera;
+class CameraConfiguration;
+class CameraManager;
+class FrameBufferAllocator;
+class Request;
+} // namespace libcamera
+
+class Image;
+class Pixels;
+
+class OV9281
+{
+public:
+ ~OV9281();
+
+public:
+ static std::vector<std::shared_ptr<OV9281>> search(
+ std::unique_ptr<libcamera::CameraManager> &manager);
+
+ // public functions
+public:
+ bool startStream();
+ void printControls();
+
+ // signals
+public:
+ // TODO: image->pixels in separate thread
+ // TODO: respect sender/receiver threads
+ libcamera::Signal<std::shared_ptr<Pixels>> newPixels;
+
+private:
+ explicit OV9281(const std::shared_ptr<libcamera::Camera> &camera);
+
+ // private functions
+private:
+ bool init();
+ bool validateConfig();
+ bool applyConfig();
+
+ void onRequestCompleted(libcamera::Request *completed_request);
+
+ // constants
+private:
+ static inline constexpr auto pixelFormat{libcamera::formats::R16};
+ static inline constexpr unsigned int bufferCount{2};
+ static inline constexpr size_t desiredFPS{144};
+
+ // member variables
+private:
+ std::shared_ptr<libcamera::Camera> m_camera{nullptr};
+ std::unique_ptr<libcamera::CameraConfiguration> m_config{nullptr};
+ std::map<int, std::pair<void *, unsigned int>> m_mappedBuffers;
+ std::vector<std::unique_ptr<libcamera::Request>> m_requests;
+ std::unique_ptr<libcamera::FrameBufferAllocator> m_allocator{nullptr};
+
+ // TODO: set exposureTime from outside
+ int32_t m_exposureTime{1000};
+};
diff --git a/src/image.cpp b/src/image.cpp
new file mode 100644
index 0000000..a9280a4
--- /dev/null
+++ b/src/image.cpp
@@ -0,0 +1,110 @@
+#include "image.h"
+
+#include "macro.h"
+#include "pixels.h"
+
+float process_column(const uint16_t (&column)[])
+{
+ start_timer(process_column);
+
+ float result = std::numeric_limits<float>::quiet_NaN();
+
+ constexpr uint32_t signalThreshold = 900; // = SKO * sqrt(patternSize)
+ static constexpr uint32_t patternOffset = patternSize -
+ ((patternSize % 2 == 1) ? 1 : 0);
+ const uint32_t correlationSize = img_height - patternSize +
+ ((patternSize % 2 == 1) ? 1 : 0);
+ uint32_t correlation[img_height];
+ uint32_t integralSum[img_height];
+ uint32_t maxSum = signalThreshold * 50;
+ uint32_t x1 = 0;
+ int32_t y1 = 0;
+ int32_t y2 = 0;
+
+ memset(correlation, 0, img_height * sizeof(correlation[0]));
+ integralSum[0] = 0;
+
+ for (uint32_t i = 1; i < img_height; ++i)
+ {
+ // if (column[i] < 100)
+ // {
+ // column[i] = 0;
+ // }
+
+ integralSum[i] = column[i] / 256 + integralSum[i - 1];
+ }
+
+ for (uint32_t i = 0; i < correlationSize; ++i)
+ correlation[i + patternSize / 2] = column[i + patternSize / 2] / 256 *
+ (integralSum[i + patternOffset] -
+ integralSum[i]);
+
+ for (uint32_t i = 3; i < img_height - 2; ++i)
+ {
+ const auto sum = correlation[i - 1] + correlation[i] +
+ correlation[i + 1];
+
+ if (sum > maxSum)
+ {
+ const int32_t rioux0 = int32_t(correlation[i - 2 - 1] +
+ correlation[i - 1 - 1]) -
+ int32_t(correlation[i + 1 - 1] +
+ correlation[i + 2 - 1]);
+
+ if (rioux0 < 0)
+ {
+ const int32_t rioux1 = int32_t(correlation[i - 2] +
+ correlation[i - 1]) -
+ int32_t(correlation[i + 1] +
+ correlation[i + 2]);
+
+ if (rioux1 >= 0)
+ {
+ x1 = i - 1;
+ y1 = rioux0;
+ y2 = rioux1;
+ maxSum = sum;
+ }
+ }
+ }
+ }
+
+ result = (y2 != y1) ? (float(x1) - (float(y1) / (y2 - y1)))
+ : std::numeric_limits<float>::quiet_NaN();
+
+ return result;
+}
+
+void Image::rotate()
+{
+ start_timer(rotate);
+
+ using namespace std;
+
+ for (size_t i = 0; i < img_height; ++i)
+ {
+ for (size_t j = 0; j < img_width; ++j)
+ {
+ rotated_cw[j][i] = data[img_height - i][j];
+ }
+ }
+
+ stop_timer(rotate);
+}
+
+std::shared_ptr<Pixels> Image::pixels() const
+{
+ auto result = std::make_shared<Pixels>();
+ result->counters = counters;
+
+ start_timer(process_columns);
+
+ for (size_t i = 0; i < width; i++)
+ {
+ result->pixels[i] = process_column(rotated_cw[i]);
+ }
+
+ stop_timer(process_columns);
+
+ return result;
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644
index 0000000..2fff020
--- /dev/null
+++ b/src/image.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "constants.h"
+#include "typedefs.h"
+
+class Pixels;
+
+struct Image
+{
+ int width{0};
+ int height{0};
+ uint16_t data[img_height][img_width] = {{0}};
+ uint16_t rotated_cw[img_width][img_height] = {{0}};
+ size_t dataSize{0};
+ unsigned int stride{0};
+ libcamera::PixelFormat pixelFormat{0};
+ Counters counters{};
+
+ void rotate();
+ std::shared_ptr<Pixels> pixels() const;
+};
diff --git a/src/pixels.cpp b/src/pixels.cpp
index b314e6a..5aac4a6 100644
--- a/src/pixels.cpp
+++ b/src/pixels.cpp
@@ -93,3 +93,10 @@ bool Pixels::save(const QString& filename)
return true;
}
+
+Pixels::operator bool() const
+{
+ return std::find_if(pixels.cbegin(), pixels.cend(), [](const auto& p) {
+ return !qFuzzyIsNull(p) && !std::isnan(p);
+ }) != pixels.cend();
+}
diff --git a/src/pixels.h b/src/pixels.h
index 6095220..1d5effb 100644
--- a/src/pixels.h
+++ b/src/pixels.h
@@ -6,6 +6,7 @@
#include <QString>
+#include "constants.h"
#include "typedefs.h"
struct Pixels
@@ -29,4 +30,6 @@ struct Pixels
* \return - true on success, false otherwise
*/
[[nodiscard]] bool save(const QString& filename);
+
+ operator bool() const;
};
diff --git a/src/typedefs.h b/src/typedefs.h
index 2af2921..7ed8411 100644
--- a/src/typedefs.h
+++ b/src/typedefs.h
@@ -2,8 +2,6 @@
#include <libcamera/pixel_format.h>
-#include "constants.h"
-
struct Counters
{
uint32_t timestampUs{0};
@@ -11,22 +9,10 @@ struct Counters
int32_t encoderPosition{0};
};
-struct Image
-{
- int width;
- int height;
- uint16_t data[img_height][img_width];
- uint16_t rotated_cw[img_width][img_height];
- size_t dataSize;
- unsigned int stride;
- libcamera::PixelFormat pixelFormat;
- Counters counters{};
-};
-
struct requested_params_t
{
- int32_t exposureTime = {1000};
- int32_t laserLevel = {3000};
+ int32_t exposureTime{1000};
+ int32_t laserLevel{3000};
uint32_t stepsPerMm{200};
};