summaryrefslogtreecommitdiff
path: root/src/protocols
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
parent36ef6a75e3418d88227e84ab175c0057e860c151 (diff)
organize things a bit, populate ICamera
Diffstat (limited to 'src/protocols')
-rw-r--r--src/protocols/httpserver.cpp219
-rw-r--r--src/protocols/httpserver.h47
-rw-r--r--src/protocols/iprotocol.cpp5
-rw-r--r--src/protocols/iprotocol.h19
-rw-r--r--src/protocols/protocolbase.cpp3
-rw-r--r--src/protocols/protocolbase.h10
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