From 5df63c0bc7e3d6f1850d04f5bafbae2dd6fa619e Mon Sep 17 00:00:00 2001 From: Nikita Kostovsky Date: Fri, 14 Nov 2025 21:05:12 +0100 Subject: organize things a bit, populate ICamera --- src/protocols/httpserver.cpp | 219 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 src/protocols/httpserver.cpp (limited to 'src/protocols/httpserver.cpp') diff --git a/src/protocols/httpserver.cpp b/src/protocols/httpserver.cpp new file mode 100644 index 0000000..41226aa --- /dev/null +++ b/src/protocols/httpserver.cpp @@ -0,0 +1,219 @@ +#include "httpserver.h" + +// qt +#include +#include +#include +#include + +// orpheus +#include "camera/veyeimx287m.h" +#include "constants.h" +#include "image.h" +#include "imagealgos.h" +#include "macro.h" + +// rapidjson +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" + +extern uint8_t pgm_image[64 + img_width * img_height * sizeof(uint8_t)]; +extern size_t pgm_image_size; +extern std::mutex pgm_image_mtx; + +HttpServer::HttpServer(ICamera *camera, + // QObject *parent, + const QHostAddress &address, + const uint16_t port) + : ProtocolBase{camera} + , INIT_FIELD(address) + , INIT_FIELD(port) + , m_server{std::make_shared()} +{ + const auto apiPrefix = QStringLiteral("/v1"); + const auto pixelsPath = apiPrefix + "/pixels"; + qDebug().noquote() << Q_FUNC_INFO << ": pixelsPath: " << pixelsPath; + + m_server->route(pixelsPath, [this]() { return GET_pixels(); }); + m_server->route(apiPrefix + QStringLiteral("/sensor/params"), + QHttpServerRequest::Method::Get, + [this]() { return GET_params(); }); + m_server->route(apiPrefix + QStringLiteral("/sensor/params"), + QHttpServerRequest::Method::Post, + [this](const QHttpServerRequest &request) { + return POST_params(request); + }); + + qDebug().noquote() << Q_FUNC_INFO << ": listen: " << m_server->listen(m_address, m_port); +} + +QHttpServerResponse HttpServer::GET_pixels() +{ + QElapsedTimer t; + t.start(); + // std::shared_ptr logTime = std::make_shared(nullptr, [t]() { + // qDebug() << "HttpServer::GET_pixels: elapsed" << t.nsecsElapsed() / 1000 << "(us)"; + // }); + static constexpr bool logTime{false}; + if constexpr (logTime) { + const std::shared_ptr + timeLogger{nullptr, [t](auto unused_ptr) { + qDebug() << "HttpServer::GET_pixels: elapsed" << t.nsecsElapsed() / 1000 + << "(us)"; + }}; + } + + Image img; + { + // const auto sharedCam = m_camera.lock(); + // FIME: don't cast anything, use interface + // auto cam = dynamic_cast(sharedCam.get()); + auto cam = dynamic_cast(m_camera); + + if (!cam) { + qWarning() << "NO CAM"; + return QHttpServerResponse::StatusCode::ServiceUnavailable; + } + + // yeaah + // ::img = &img; + if (!cam->getImage(img)) { + qWarning() << "cannot get image"; + return QHttpServerResponse::StatusCode::ServiceUnavailable; + } + + // ::pixels = std::move(img.pixels()); + ++cam->processedCounter; + } + + // FIXME: not thread-safe, don't use this static shared_ptr at all + const auto pixels = img.sharedPixels(); + // const auto lines = pixelsToLines(::pixels); + const auto lines = pixelsToLines(*pixels); + + // qt json does not allow to limit double precision, so using rapidjson + rapidjson::Document jd; + jd.SetObject(); + auto &al = jd.GetAllocator(); + + const auto nan2zero = [](const auto &value) { return qIsNaN(value) ? 0 : value; }; + + rapidjson::Value rjPixels{rapidjson::kArrayType}; + + for (size_t i = 0; i < img_width; ++i) { + rjPixels.PushBack(nan2zero(pixels->pixels[i]), al); + } + + rapidjson::Value rjLines{rapidjson::kArrayType}; + + for (const auto &l : lines) { + rapidjson::Value rjLineP1{rapidjson::kArrayType}; + rjLineP1.PushBack(nan2zero(l.p1().x()), al).PushBack(nan2zero(l.p1().y()), al); + rapidjson::Value rjLineP2{rapidjson::kArrayType}; + rjLineP2.PushBack(nan2zero(l.p2().x()), al).PushBack(nan2zero(l.p2().y()), al); + rapidjson::Value rjLinePoints{rapidjson::kArrayType}; + rjLinePoints.PushBack(rjLineP1, al).PushBack(rjLineP2, al); + + rjLines.PushBack(rjLinePoints, al); + } + + // jd.AddMember("encoderPosition", qint64{encoder.position()}); + // FIXME: get prom pixels struct + jd.AddMember("measurementCounter", img.counters.measurementCounter, al); + jd.AddMember("timestampUs", img.counters.timestampUs, al); + jd.AddMember("pixels", rjPixels.Move(), al); + jd.AddMember("lines", rjLines.Move(), al); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + writer.SetMaxDecimalPlaces(2); + jd.Accept(writer); + const QString res{(const char *) buffer.GetString()}; + // qDebug() << "size:" << res.size(); + // qDebug().noquote() << "ret pix"; + + return QHttpServerResponse{res}; +} + +QHttpServerResponse HttpServer::POST_params(const QHttpServerRequest &request) +{ + const auto json = QJsonDocument::fromJson(request.body()).object(); + qDebug().noquote() << __func__ << ": " << json; + + const auto jsonError = [](const auto message) -> const QString { + return QJsonDocument{QJsonObject{{QStringLiteral("error"), message}}} + .toJson(); + }; + const auto invalidValue = + [jsonError](const auto fieldName) -> const QString { + return jsonError(fieldName + QStringLiteral(" has invalid value")); + }; + + const auto autoExposure = json["autoExposure"]; + const auto exposureTime = json["exposureTime"]; + const auto autoGain = json["autoGain"]; + const auto gain = json["gain"]; + + if (!autoExposure.isNull()) { + if (autoExposure.isBool()) { + m_camera->set_autoExposure(autoExposure.toBool()); + } else { + return {invalidValue("autoExposure"), + QHttpServerResponse::StatusCode::BadRequest}; + } + } + + if (!exposureTime.isNull()) { + if (exposureTime.isDouble()) { + m_camera->set_exposureTime( + std::chrono::microseconds{exposureTime.toInt()}); + } else { + return {invalidValue("exposureTime"), + QHttpServerResponse::StatusCode::BadRequest}; + } + } + + if (!autoGain.isNull()) { + if (autoGain.isBool()) { + m_camera->set_autoGain(autoGain.toBool()); + } else { + return {invalidValue("autoGain"), + QHttpServerResponse::StatusCode::BadRequest}; + } + } + + if (!gain.isNull()) { + if (gain.isDouble()) { + m_camera->set_gain(gain.toDouble()); + } else { + return {invalidValue("gain"), + QHttpServerResponse::StatusCode::BadRequest}; + } + } + + return QHttpServerResponse::StatusCode::Ok; +} + +QHttpServerResponse HttpServer::GET_params() +{ + const auto val_or_null = [](const auto &optional) -> QJsonValue { + return optional ? QJsonValue{*optional} : QJsonValue::Null; + }; + + const auto chrono_val_or_null = [](const auto &optional) -> QJsonValue { + return optional ? static_cast(optional->count()) + : QJsonValue::Null; + }; + + const auto json = QJsonObject{ + {"autoExposure", val_or_null(m_camera->get_autoExposure())}, + {"exposureTime", chrono_val_or_null(m_camera->get_exposureTime())}, + {"autoGain", val_or_null(m_camera->get_autoGain())}, + {"gain", val_or_null(m_camera->get_gain())}, + }; + + qDebug().noquote() << __func__ << ": " << json; + + return QHttpServerResponse{QJsonDocument{json}.toJson()}; +} -- cgit v1.2.3-70-g09d2