summaryrefslogtreecommitdiff
path: root/src/protocols/httpserver.cpp
diff options
context:
space:
mode:
authorNikita Kostovsky <nikita@kostovsky.me>2025-11-14 21:05:12 +0100
committerNikita Kostovsky <nikita@kostovsky.me>2025-11-14 21:05:12 +0100
commit5df63c0bc7e3d6f1850d04f5bafbae2dd6fa619e (patch)
tree7b98d59baec4aac62cab374e95795a2ce6b88d03 /src/protocols/httpserver.cpp
parent36ef6a75e3418d88227e84ab175c0057e860c151 (diff)
organize things a bit, populate ICamera
Diffstat (limited to 'src/protocols/httpserver.cpp')
-rw-r--r--src/protocols/httpserver.cpp219
1 files changed, 219 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()};
+}