// cpp and linux #include #include #include #include #include #include #include #include #include // qt #include #include #include #include #include #include #include #include #include #include #include #include #include // orpheus #include "LibCamera.h" #include "calibration.h" #include "camera/veyeimx287m.h" #include "dumps.h" #include "imagealgos.h" #include "profile.h" #include "protocols/httpserver.h" // #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; #define try_apply_config() \ if (!applyConfig(config)) { \ camera->release(); \ cm->stop(); \ \ return EXIT_FAILURE; \ } ScanningModeFlags scanningModeFlags{ScanningModeFlags::None}; QElapsedTimer calibrationTimer; extern volatile int32_t positionSteps; requested_params_t requested_params; namespace { Pixels pixels; std::vector calibrationPixels; QMutex calibrationPixelsMutex; } // namespace using namespace std::chrono_literals; namespace { CalibrationTablePtr calibrationTableZ; CalibrationTablePtr calibrationTableX; } // namespace auto printPixels = [](const auto &pixels) { for (size_t i = (img_width - 10) / 2; i < img_width - ((img_width - 10) / 2); ++i) { std::cout << pixels[i] << " "; } std::cout << std::endl; }; bool initLaser(); bool initCam(); int main(int argc, char *argv[]) { auto sigHandler = [](int s) { std::cout << "got signal " << s << std::endl; std::signal(s, SIG_DFL); }; std::signal(SIGINT, sigHandler); std::signal(SIGTERM, sigHandler); QCoreApplication app(argc, argv); QList> initializers; qDebug() << "size of raw profile" << sizeof(Pixels); if (false) { // open binary calibration table if (true) { initializers << QtConcurrent::run([&]() { if (!openCalibrationTable( "/home/user/dumps/binz.calibration_table", // "/tmp/binz.calibration_table", ::calibrationTableZ)) { exit(EXIT_FAILURE); } }); initializers << QtConcurrent::run([&]() { if (!openCalibrationTable( "/home/user/dumps/binx.calibration_table", // "/tmp/binx.calibration_table", ::calibrationTableX)) { exit(EXIT_FAILURE); } }); } if (false) { auto rawProfiles = openDump("/home/user/dumps/binx"); qDebug() << "raw x-profiles count is" << rawProfiles.size(); // qDebug() << "height" << calibrationColumnHeight; auto filteredRawProfiles = filter(std::move(rawProfiles)); qDebug() << "filtered x-profiles count is" << filteredRawProfiles.count(); ::calibrationTableX = calibrateX(std::move(filteredRawProfiles)); interpolate(::calibrationTableX); } // load binary calibration dumps and calibrate if (false) { if (true) { auto rawProfiles = openDump("/home/user/dumps/binz"); qDebug() << "raw z-profiles count is" << rawProfiles.size(); auto filteredRawProfiles = filter(std::move(rawProfiles)); qDebug() << "filtered z-profiles count is" << filteredRawProfiles.count(); ::calibrationTableZ = calibrateZ(std::move(filteredRawProfiles), requested_params.stepsPerMm); interpolate(::calibrationTableZ); if (!dump(::calibrationTableZ, "/home/user/dumps/binz.calibration_table")) { qApp->exit(EXIT_FAILURE); } } qDebug() << "--------------------------------------------------------"; if (true) { auto rawProfiles = openDump("/home/user/dumps/binx"); qDebug() << "raw x-profiles count is" << rawProfiles.size(); auto filteredRawProfiles = filter(std::move(rawProfiles)); qDebug() << "filtered x-profiles count is" << filteredRawProfiles.count(); ::calibrationTableX = calibrateX(std::move(filteredRawProfiles)); interpolate(::calibrationTableX); if (!dump(::calibrationTableX, "/home/user/dumps/binx.calibration_table")) { qApp->exit(EXIT_FAILURE); } } } } QElapsedTimer t; t.start(); // FIXME: don't use one var for everything int ret; const auto cameras = VeyeIMX287m::search(); if (cameras.empty()) { std::cerr << "No cameras were identified on the system." << std::endl; return EXIT_FAILURE; } auto camera = cameras.at(0); std::cout << "connect everything" << std::endl; for (auto &i : initializers) i.waitForFinished(); std::cout << "loaded calibration tables" << std::endl; if (!camera->startStream()) { return EXIT_FAILURE; } QHttpServer qHttpServer; auto httpGetImage = [&]() -> QHttpServerResponse { auto cam = dynamic_cast(camera.get()); if (!cam) { qDebug() << "NO CAM"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } static Image img; if (!cam->getImage(img)) { qDebug() << "cannot get image"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } pgm_save(&img); std::lock_guard lg(pgm_image_mtx); return QHttpServerResponse{QByteArray((const char *) pgm_image, pgm_image_size), QHttpServerResponse::StatusCode::Ok}; }; qHttpServer.route("/v1/sensor/image", httpGetImage); qHttpServer.route("/v1/sensor/image2", httpGetImage); qHttpServer.route("/v1/pixels", [&]() -> QHttpServerResponse { // std::cout << "http: pixels" << std::endl; // return QHttpServerResponse::StatusCode::ServiceUnavailable; auto cam = dynamic_cast(camera.get()); if (!cam) { qDebug() << "NO CAM"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } Image img; // yeaah // ::img = &img; if (!cam->getImage(img)) { qDebug() << "cannot get image"; return QHttpServerResponse::StatusCode::ServiceUnavailable; } ::pixels = std::move(img.pixels()); ++cam->processedCounter; // qt json does not allow to limit double precision const auto lines = pixelsToLines(::pixels); const auto nan2zero = [](const auto &value) { return qIsNaN(value) ? 0 : value; }; rapidjson::Document jd; jd.SetObject(); auto &al = jd.GetAllocator(); 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); QString res{(const char *) buffer.GetString()}; // qDebug() << "size:" << res.size(); // qDebug().noquote() << "ret pix"; return QHttpServerResponse{res}; }); qHttpServer.route("/v1/profile", [&]() -> QHttpServerResponse { // std::cout << "http: profile" << std::endl; return QHttpServerResponse::StatusCode::ServiceUnavailable; std::lock_guard lg(pgm_image_mtx); if (!::calibrationTableZ || !::calibrationTableX) return QHttpServerResponse::StatusCode::ServiceUnavailable; const Profile profile(::pixels, ::calibrationTableZ, ::calibrationTableX); const QJsonObject json{{"profile", QJsonObject(profile)}}; return QHttpServerResponse(QJsonDocument(json).toJson()); }); qHttpServer .route("/v1/commands/resetEncoder", [&](const QHttpServerRequest &request) -> QHttpServerResponse { std::cout << "http: resetEncoder" << std::endl; if (request.method() != QHttpServerRequest::Method::Post) { return QHttpServerResponse::StatusCode::NotFound; } qDebug() << "reset encoder"; positionSteps = 0; return QHttpServerResponse::StatusCode::Ok; }); qHttpServer .route("/v1/commands/startCalibration", [&](const QHttpServerRequest &request) -> QHttpServerResponse { std::cout << "http: startCalibration" << std::endl; if (request.method() != QHttpServerRequest::Method::Post) { return QHttpServerResponse::StatusCode::NotFound; } qDebug() << "start calibration"; // TODO: use flags scanningModeFlags = ScanningModeFlags::Calibration; calibrationTimer.start(); return QHttpServerResponse::StatusCode::Ok; }); qHttpServer .route("/v1/commands/gCode", [&](const QHttpServerRequest &request) -> QHttpServerResponse { std::cout << "http: gCode" << std::endl; if (request.method() != QHttpServerRequest::Method::Post) { return QHttpServerResponse::StatusCode::NotFound; } const auto command = request.body(); qDebug() << "send gCode:" << command; // printerClient.sendCommand(command); return QHttpServerResponse::StatusCode::Ok; }); qHttpServer.route( "/v1/sensor/params", [&](const QHttpServerRequest &request) -> QHttpServerResponse { // std::cout << "http: params" << std::endl; switch (request.method()) { case QHttpServerRequest::Method::Get: { std::lock_guard lg(pgm_image_mtx); QJsonObject json; return QHttpServerResponse(QJsonDocument(json).toJson()); } case QHttpServerRequest::Method::Post: { // qDebug() << "request body:" << request.body(); auto json = QJsonDocument::fromJson(request.body()).object(); if (json.contains(exposureTimeKey)) { const int32_t value{json[exposureTimeKey].toInt()}; if (value == 0) return QHttpServerResponse::StatusCode:: RequestRangeNotSatisfiable; // qDebug() << "set new exposure time:" << value; // requested_params.exposureTime = value; if (!camera->set_exposureTime( std::chrono::microseconds(value))) { qDebug() << "cannot set exp"; return QHttpServerResponse::StatusCode:: RequestRangeNotSatisfiable; } } if (json.contains(gainKey)) { const auto value = json[gainKey].toDouble(); if (value == 0) return QHttpServerResponse::StatusCode:: RequestRangeNotSatisfiable; if (!camera->set_gain(value)) return QHttpServerResponse::StatusCode:: RequestRangeNotSatisfiable; } if (json.contains(laserLevelKey)) { const int32_t value{json[laserLevelKey].toInt()}; if (!camera->setLaserLevel(value)) return QHttpServerResponse::StatusCode:: RequestRangeNotSatisfiable; requested_params.laserLevel = value; } return QHttpServerResponse(request.body()); } default: { return QHttpServerResponse( QByteArray("unsupported http method")); } } }); qDebug() << "listen: " << qHttpServer.listen(QHostAddress::Any, 8081); //////////////////////////////////////////////////////////////////////////// std::clog << std::flush; std::cerr << std::flush; std::cout << "ok for now" << std::endl << std::flush; auto result = app.exec(); return result; } bool initLaser() { const QLatin1String pwmChip{"pwmchip2"}; const uint16_t pwmChannel{1}; const QLatin1String pwmSystemRoot{"/sys/class/pwm"}; const QString pwmChipRoot{pwmSystemRoot + "/" + pwmChip}; const QString pwmExportFile{pwmChipRoot + "/export"}; QFile f{pwmExportFile}; if (!f.open(QFile::WriteOnly)) { qWarning() << "cannot open" << f.fileName() << "for writing"; qWarning() << "error:" << f.errorString(); return false; } QTextStream s{&f}; s << pwmChannel; const QString pwm{QLatin1String("pwm%1").arg(QString::number(pwmChannel))}; const QString pwmRoot{pwmChipRoot + "/" + pwm}; const QString periodFilename{pwmRoot + "/period"}; f.close(); f.setFileName(periodFilename); if (!f.open(QFile::WriteOnly)) { qWarning() << "cannot open" << f.fileName() << "for writing"; qWarning() << "error:" << f.errorString(); return false; } const unsigned periodHz{50'000}; s << periodHz; const QString dutyCycleFilename{pwmRoot + "/duty_cycle"}; f.close(); f.setFileName(dutyCycleFilename); if (!f.open(QFile::WriteOnly)) { qWarning() << "cannot open" << f.fileName() << "for writing"; qWarning() << "error:" << f.errorString(); return false; } const unsigned dutyCycle{3'000}; s << dutyCycle; const QString enableFilename{pwmRoot + "/enable"}; f.close(); f.setFileName(enableFilename); if (!f.open(QFile::WriteOnly)) { qWarning() << "cannot open" << f.fileName() << "for writing"; qWarning() << "error:" << f.errorString(); return false; } const int enable{1}; s << enable; return true; } bool initCam() { const v4l2_memory memtype = V4L2_MEMORY_MMAP; v4l2_buf_type type = (enum v4l2_buf_type) - 1; const char *devname{"/dev/video0"}; int fd{open(devname, O_RDWR)}; if (fd < 0) { printf("Error opening device %s: %s (%d).\n", devname, strerror(errno), errno); fflush(stdout); return false; } std::cout << devname << ": opened" << std::endl; v4l2_capability cap; memset(&cap, 0, sizeof cap); const auto ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); if (ret < 0) { printf("cannot query cap for %s: %s (%d).\n", devname, strerror(errno), errno); fflush(stdout); return false; } std::cout << devname << ": got caps" << std::endl; const auto caps = cap.capabilities & V4L2_CAP_DEVICE_CAPS ? cap.device_caps : cap.capabilities; const bool has_video = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_VIDEO_OUTPUT); const bool has_meta = caps & (V4L2_CAP_META_CAPTURE | V4L2_CAP_META_OUTPUT); const bool has_capture = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE); const bool has_output = caps & (V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_META_OUTPUT); const bool has_mplane = caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE); printf("Device `%s' on `%s' (driver '%s') supports%s%s%s%s %s mplanes.\n", cap.card, cap.bus_info, cap.driver, has_video ? " video," : "", has_meta ? " meta-data," : "", has_capture ? " capture," : "", has_output ? " output," : "", has_mplane ? "with" : "without"); const auto buf_type = [caps, devname]() -> int { if (caps & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE" << std::endl; return V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; } else if (caps & V4L2_CAP_VIDEO_OUTPUT_MPLANE) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE" << std::endl; return V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; } else if (caps & V4L2_CAP_VIDEO_CAPTURE) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_VIDEO_CAPTURE" << std::endl; return V4L2_BUF_TYPE_VIDEO_CAPTURE; } else if (caps & V4L2_CAP_VIDEO_OUTPUT) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_VIDEO_OUTPUT" << std::endl; return V4L2_BUF_TYPE_VIDEO_OUTPUT; } else if (caps & V4L2_CAP_META_CAPTURE) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_META_CAPTURE" << std::endl; return V4L2_BUF_TYPE_META_CAPTURE; } else if (caps & V4L2_CAP_META_OUTPUT) { std::cout << devname << ": buf_type: V4L2_BUF_TYPE_META_OUTPUT" << std::endl; return V4L2_BUF_TYPE_META_OUTPUT; } else { printf("Device supports neither capture nor output.\n"); return -EINVAL; } }(); if (buf_type < 0) { return false; } if (false) { v4l2_format fmt; memset(&fmt, 0, sizeof fmt); fmt.type = buf_type; if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) { printf("Unable to get format: %s (%d).\n", strerror(errno), errno); return false; } const auto width = fmt.fmt.pix_mp.width; const auto height = fmt.fmt.pix_mp.height; const auto num_planes = fmt.fmt.pix_mp.num_planes; struct v4l2_plane_pix_format plane_fmt[VIDEO_MAX_PLANES] = {0}; printf("Video format: (%08x) %ux%u field %d, %u planes: \n", fmt.fmt.pix_mp.pixelformat, fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height, fmt.fmt.pix_mp.field, fmt.fmt.pix_mp.num_planes); for (int i = 0; i < fmt.fmt.pix_mp.num_planes; i++) { plane_fmt[i].bytesperline = fmt.fmt.pix_mp.plane_fmt[i].bytesperline; plane_fmt[i].sizeimage = fmt.fmt.pix_mp.plane_fmt[i].bytesperline ? fmt.fmt.pix_mp.plane_fmt[i].sizeimage : 0; printf(" * Stride %u, buffer size %u\n", fmt.fmt.pix_mp.plane_fmt[i].bytesperline, fmt.fmt.pix_mp.plane_fmt[i].sizeimage); } } unsigned int padding = 0; unsigned int nbufs = 8; // if () close(fd); return true; }