#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(std::shared_ptr camera, const QHostAddress &address, const uint16_t port) : IProtocol{camera} , INIT_FIELD(address) , INIT_FIELD(port) // , m_server{std::make_shared()} { } bool HttpServer::start() { m_server = std::make_shared(); // TODO: move these vars outside const auto apiPrefix = QStringLiteral("/v1"); const auto pixelsPath = apiPrefix + "/pixels"; qDebug().noquote() << Q_FUNC_INFO << ": pixelsPath: " << pixelsPath; // TODO: get rid of lamdas, there should be a better way 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); }); m_server->route(apiPrefix + QStringLiteral("/sensor/image"), QHttpServerRequest::Method::Get, [this]() { return GET_image(); }); m_server->route(apiPrefix + QStringLiteral("/sensor/image2"), QHttpServerRequest::Method::Get, [this]() { return GET_image(); }); const auto result = m_server->listen(m_address, m_port); qDebug().noquote() << Q_FUNC_INFO << ": listen: " << result; if (!result) { m_server.reset(); } return result; } void HttpServer::stop() { m_server.reset(); } QHttpServerResponse HttpServer::GET_image() { Image img; if (!m_camera->getImage(&img)) { qCritical() << "cannot get image"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } pgm_save(&img); std::lock_guard lg(pgm_image_mtx); return QHttpServerResponse{QByteArray{(const char *) (pgm_image), static_cast( pgm_image_size)}, QHttpServerResponse::StatusCode::Ok}; } 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) { if (!m_camera) { qWarning() << "NO CAM"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } // yeaah // ::img = &img; // if (!cam->getImage(img)) { if (!m_camera->getImage(&img)) { qWarning() << "cannot get image"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } // ::pixels = std::move(img.pixels()); // ++cam->processedCounter; // FIXME: take into account // ++m_camera->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()}; }