diff options
| author | Nikita Kostovsky <nikita@kostovsky.me> | 2025-11-14 21:05:12 +0100 |
|---|---|---|
| committer | Nikita Kostovsky <nikita@kostovsky.me> | 2025-11-14 21:05:12 +0100 |
| commit | 5df63c0bc7e3d6f1850d04f5bafbae2dd6fa619e (patch) | |
| tree | 7b98d59baec4aac62cab374e95795a2ce6b88d03 /src/protocols | |
| parent | 36ef6a75e3418d88227e84ab175c0057e860c151 (diff) | |
organize things a bit, populate ICamera
Diffstat (limited to 'src/protocols')
| -rw-r--r-- | src/protocols/httpserver.cpp | 219 | ||||
| -rw-r--r-- | src/protocols/httpserver.h | 47 | ||||
| -rw-r--r-- | src/protocols/iprotocol.cpp | 5 | ||||
| -rw-r--r-- | src/protocols/iprotocol.h | 19 | ||||
| -rw-r--r-- | src/protocols/protocolbase.cpp | 3 | ||||
| -rw-r--r-- | src/protocols/protocolbase.h | 10 |
6 files changed, 303 insertions, 0 deletions
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 <QHttpServer> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> + +// 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<QHttpServer>()} +{ + 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<std::nullptr_t> logTime = std::make_shared<std::nullptr_t>(nullptr, [t]() { + // qDebug() << "HttpServer::GET_pixels: elapsed" << t.nsecsElapsed() / 1000 << "(us)"; + // }); + static constexpr bool logTime{false}; + if constexpr (logTime) { + const std::shared_ptr<std::nullptr_t> + 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<VeyeIMX287m *>(sharedCam.get()); + auto cam = dynamic_cast<VeyeIMX287m *>(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<rapidjson::StringBuffer> 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<qint64>(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()}; +} diff --git a/src/protocols/httpserver.h b/src/protocols/httpserver.h new file mode 100644 index 0000000..86b6ad6 --- /dev/null +++ b/src/protocols/httpserver.h @@ -0,0 +1,47 @@ +#pragma once + +// qt +#include <QHostAddress> +#include <QHttpServerResponse> +// #include <QObject> + +// orpheus +#include "iprotocol.h" + +class ICamera; +class QHttpServer; + +class HttpServer : public ProtocolBase +{ + // Q_OBJECT + +private: + struct Stats + { + uint64_t GET_pixels_us{0}; + } m_stats{0}; + +public: + static constexpr auto DefaultAddress = QHostAddress::Any; + static constexpr uint16_t DefaultPort{8080}; + +public: + explicit HttpServer(ICamera *camera, + // QObject *parent = nullptr, + const QHostAddress &address = DefaultAddress, + const uint16_t port = DefaultPort); + ~HttpServer() override = default; + + // TODO: methods starting with GET_/POST_ will be routed automatically +public: + QHttpServerResponse GET_pixels(); + + QHttpServerResponse POST_params(const QHttpServerRequest &request); + QHttpServerResponse GET_params(); + +private: + QHostAddress m_address{DefaultAddress}; + uint16_t m_port{DefaultPort}; + + std::shared_ptr<QHttpServer> m_server; +}; diff --git a/src/protocols/iprotocol.cpp b/src/protocols/iprotocol.cpp new file mode 100644 index 0000000..f85d990 --- /dev/null +++ b/src/protocols/iprotocol.cpp @@ -0,0 +1,5 @@ +#include "iprotocol.h" + +ProtocolBase::ProtocolBase(ICamera *camera) + : m_camera{camera} +{} diff --git a/src/protocols/iprotocol.h b/src/protocols/iprotocol.h new file mode 100644 index 0000000..3827550 --- /dev/null +++ b/src/protocols/iprotocol.h @@ -0,0 +1,19 @@ +#pragma once + +class ICamera; + +class IProtocol +{ +public: + virtual ~IProtocol() = default; +}; + +class ProtocolBase : public IProtocol +{ +public: + explicit ProtocolBase(ICamera *camera); + ~ProtocolBase() override = default; + +protected: + ICamera *m_camera{nullptr}; +}; diff --git a/src/protocols/protocolbase.cpp b/src/protocols/protocolbase.cpp new file mode 100644 index 0000000..9116d25 --- /dev/null +++ b/src/protocols/protocolbase.cpp @@ -0,0 +1,3 @@ +#include "protocolbase.h" + +ProtocolBase::ProtocolBase() {} diff --git a/src/protocols/protocolbase.h b/src/protocols/protocolbase.h new file mode 100644 index 0000000..fb41dca --- /dev/null +++ b/src/protocols/protocolbase.h @@ -0,0 +1,10 @@ +#ifndef PROTOCOLBASE_H +#define PROTOCOLBASE_H + +class ProtocolBase +{ +public: + ProtocolBase(); +}; + +#endif // PROTOCOLBASE_H |
