#include "Application.h" #include "AsyncEvent.h" #include "BoostLog.h" #include "CategoryLogSinkBackend.h" #include "CdcUpdater.h" #include "Configuration.h" #include "Database.h" #include "DateTime.h" #include "DeviceDiscovery.h" #include "ImageDecoder.h" #include "StringUtility.h" #include "VideoFrameProvider.h" #include "VideoPlayer.h" #include #include #include #include #include #include #include #include #include #include constexpr uint32_t ImageSliceSize = (4000 - 32); Application::Application(int &argc, char **argv) : m_app(std::make_shared(argc, argv)) { m_app->setApplicationName(APPLICATION_NAME); m_app->setApplicationVersion(QString("v%1_%2 build: %3 %4").arg(APP_VERSION, GIT_COMMIT_ID, __DATE__, __TIME__)); QFont font; font.setPointSize(16); m_app->setFont(font); m_verifyTimer = new QTimer(this); m_verifyTimer->setSingleShot(true); connect(m_verifyTimer, &QTimer::timeout, this, &Application::onVerifyTimeout); m_database = std::make_shared(); QTimer::singleShot(0, this, [this]() { if (!m_database->open("database.db")) { LOG(error) << "open database failed."; } }); m_videoFrameProvider = new VideoFrameProvider(); qmlRegisterSingletonInstance("Analyser", 1, 0, "App", this); if (!std::filesystem::exists(JpgPath)) { std::filesystem::create_directory(JpgPath); } if (!std::filesystem::exists(YuvPath)) { std::filesystem::create_directory(YuvPath); } } void Application::onNewEnrollResult(uint16_t userid) { m_palmId = userid; QTimer::singleShot(0, this, [this, userid]() { emit newStatusTip(Info, QString("%1,录入成功,ID:%2").arg(m_palmUsername).arg(userid)); }); } void Application::onNewVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed) { m_palmUsername = username; m_palmId = userid; QTimer::singleShot(0, this, [this, userid, username, elapsed]() { emit newStatusTip(Info, QString("%1,识别耗时: %2ms") .arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username) .arg(elapsed)); }); } void Application::initializeLogger() { auto backend = boost::make_shared("GUI", [this](const std::string &log) { Amass::executeAtObjectThread(this, [this, log]() { emit newLog(QString::fromStdString(log)); }); }); using SynchronousCategorySink = boost::log::sinks::synchronous_sink; auto sink = boost::make_shared(backend); boost::log::core::get()->add_sink(sink); } int Application::exec() { QQmlApplicationEngine engine; engine.addImageProvider("videoframe", m_videoFrameProvider); const QUrl url(QStringLiteral("qrc:/qt/qml/Analyser/qml/Main.qml")); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, m_app.get(), []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return m_app->exec(); } QStringList Application::availableSerialPorts() const { QStringList ret; auto ports = QSerialPortInfo::availablePorts(); for (auto &port : ports) { if (port.description() == "蓝牙链接上的标准串行") continue; ret << port.portName(); } return ret; } QStringList Application::availableUsbVideoCameras() const { QStringList ret; DeviceDiscovery d; auto devices = d.devices(); for (auto &device : devices) { ret << QString::fromStdString(device); } return ret; } bool Application::open(const QString &portName, int baudRate) { m_communication = std::make_shared(); connect(m_communication.get(), &ModuleCommunication::commandStarted, this, &Application::onCommandStarted); connect(m_communication.get(), &ModuleCommunication::commandFinished, this, &Application::onCommandFinished); connect(m_communication.get(), &ModuleCommunication::newEnrollResult, this, &Application::onNewEnrollResult); connect(m_communication.get(), &ModuleCommunication::newVerifyResult, this, &Application::onNewVerifyResult); connect(m_communication.get(), &ModuleCommunication::newPalmFeature, this, &Application::onNewPalmFeature); connect(m_communication.get(), &ModuleCommunication::newImageInfo, this, &Application::onNewImageInfo); connect(m_communication.get(), &ModuleCommunication::newImageSliceData, this, &Application::onNewImageSliceData); connect(m_communication.get(), &ModuleCommunication::errorOccurred, this, &Application::onErrorOccurred); emit connectedChanged(); auto status = m_communication->open(portName, baudRate); emit newStatusTip(status ? Tip : Error, status ? "串口打开成功" : "串口打开失败"); if (status) { QTimer::singleShot(0, this, [this]() { m_communication->requestVersion(); }); } return status; } bool Application::openUVC(const QString &deviceName) { m_videoPlayer = std::make_shared(); m_videoPlayer->setFrameCallback([this](const QImage &image) { Amass::executeAtObjectThread(this, [this, image]() { m_videoFrameProvider->setImage(image); emit newVideoFrame(); }); }); m_videoPlayer->setErrorCallback([this](int error, const std::string &message) { LOG_CAT(info, GUI) << "UVC错误: " << message; Amass::executeAtObjectThread(this, [this, error]() { if (error == -EIO) { closeUVC(); } }); }); bool status = m_videoPlayer->open(deviceName.toStdString()); if (!status) { m_videoPlayer.reset(); } emit uvcOpenedChanged(); emit newStatusTip(status ? Tip : Error, status ? "UVC打开成功" : "UVC打开失败"); return status; } void Application::close() { m_communication.reset(); emit connectedChanged(); m_persistenceModeStarted = false; m_verifyTimer->stop(); emit isVerifyingChanged(); } void Application::closeUVC() { m_videoFrameProvider->reset(); emit newVideoFrame(); m_videoPlayer.reset(); emit uvcOpenedChanged(); } void Application::verify(bool captureImage, uint8_t timeout) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } if (captureImage) { m_communication->verifyExtended(captureImage, timeout); } else { m_communication->verify(timeout); } m_verifyExtendedMode = captureImage; } void Application::enroll(const QString &username, bool persistence, uint8_t timeout) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } m_communication->enroll(username.toStdString(), persistence, timeout); m_palmUsername = username; } void Application::deleteUser(uint16_t userid) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } m_communication->deleteUser(userid); } void Application::deleteAll() { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } m_communication->deleteAll(); } void Application::enrollExtended(const QString &username, bool persistence, uint8_t timeout) { if (m_communication->currentMessageId() != ModuleCommunication::Idle) { m_communication->reset(); } m_communication->enrollExtended(username.toStdString(), persistence, timeout); m_palmUsername = username; } void Application::uploadImage(const QString &path, const QString &username, int operation) { m_uploadImageSendedSize = 0; ModuleCommunication::UploadImageInformation request; if (path.endsWith(".jpg")) { auto image = ImageDecoder::extractJpegYComponent(Amass::StringUtility::UTF8ToGBK(path.toStdString())); if (!image) { LOG(error) << "decode failed."; return; } m_uploadBuffer = image->data; LOG(info) << path.toStdString() << ", width: " << image->width << ", height: " << image->height; request.width = image->width; request.height = image->height; } else if (path.endsWith(".yuv")) { std::ifstream ifs(Amass::StringUtility::UTF8ToGBK(path.toStdString()), std::ofstream::binary); m_uploadBuffer = std::vector((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); if (m_uploadBuffer.empty()) { LOG(error) << "file is empty."; } request.width = 600; request.height = 800; } else { LOG(error) << "not supported format."; return; } mbedtls_md5_context context; mbedtls_md5_init(&context); mbedtls_md5_starts(&context); mbedtls_md5_update(&context, m_uploadBuffer.data(), m_uploadBuffer.size()); mbedtls_md5_finish(&context, request.md5); mbedtls_md5_free(&context); request.operation = operation; request.size = m_uploadBuffer.size(); strncpy(request.username, username.toStdString().c_str(), sizeof(request.username)); m_communication->uploadImageInfo(request); LOG(info) << "upload image, md5: " << ModuleCommunication::protocolDataFormatString(request.md5, sizeof(request.md5)); m_imageUploadling = true; m_uploadPath = path; m_uploadUsername = username; m_currentUploadOperation = operation; m_startUploadTime = std::chrono::system_clock::now(); } ModuleCommunication *Application::module() const { return m_communication.get(); } bool Application::connected() const { return static_cast(m_communication); } bool Application::uvcOpened() const { return static_cast(m_videoPlayer); } bool Application::persistenceMode() const { return m_persistenceMode; } void Application::setPersistenceMode(bool enabled) { if (m_persistenceMode != enabled) { m_persistenceMode = enabled; emit persistenceModeChanged(); } } bool Application::imageUploadPersistenceMode() const { return m_imageUploadPersistenceMode; } void Application::setImageUploadPersistenceMode(bool enabled) { if (m_imageUploadPersistenceMode != enabled) { m_imageUploadPersistenceMode = enabled; emit imageUploadPersistenceModeChanged(); } } int Application::persistenceVerifyInterval() const { return m_persistenceVerifyInterval; } void Application::setPersistenceVerifyInterval(int interval) { if (m_persistenceVerifyInterval != interval) { m_persistenceVerifyInterval = interval; emit persistenceVerifyIntervalChanged(); } } bool Application::isVerifying() const { if (!m_communication) { return false; } return (m_persistenceMode && m_persistenceModeStarted) || (m_communication->currentMessageId() == ModuleCommunication::Verify) || (m_communication->currentMessageId() == ModuleCommunication::VerifyExtended); } void Application::onNewPalmFeature(const PalmFeature &feature) { auto palms = m_database->palmFeatures(); if (std::find(palms.cbegin(), palms.cend(), feature) != palms.cend()) { LOG(warning) << "本地数据库已有相同特征数据。"; return; } if (!m_database->addPalmFeature(feature)) { LOG(error) << "add palm feature failed."; } } void Application::onErrorOccurred(const QString &error) { QTimer::singleShot(0, this, [this]() { close(); }); } void Application::onNewImageInfo(ModuleCommunication::MessageId messageId, uint32_t size, const uint8_t *md5) { using namespace std::chrono; m_palmImageId = messageId; m_palmImageSize = size; m_communication->requestEnrolledImage(0, ImageSliceSize); m_palmYImageBuffer.clear(); m_startUploadTime = system_clock::now(); if (messageId == ModuleCommunication::Note) { emit newStatusTip(Error, "活体未通过照片"); } } void Application::onNewImageSliceData(const std::vector &data) { using namespace std::chrono; // LOG(info) << "onNewImageSliceData:" << data.size() << ", already received: " << m_palmYImageBuffer.size() // << ", total size: " << m_palmImageSize; m_palmYImageBuffer.append(reinterpret_cast(data.data()), data.size()); if (m_palmYImageBuffer.size() < m_palmImageSize) { m_communication->requestEnrolledImage(m_palmYImageBuffer.size(), ImageSliceSize); } else { auto username = m_palmUsername.toStdString(); auto way = (m_palmImageId == ModuleCommunication::VerifyExtended) ? "verify" : "enroll"; if (m_palmImageId == ModuleCommunication::Note) { way = "no_alive"; } LOG(info) << "request image finished, username: " << username << ", userid: " << m_palmId << ", elapsed: " << duration_cast(system_clock::now() - m_startUploadTime); std::ostringstream oss; oss << YuvPath << "/" << username << "_" << m_palmId << "_" << way << "_" << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") << ".yuv"; std::ofstream ofs(Amass::StringUtility::UTF8ToGBK(oss.str()), std::ofstream::binary); ofs.write(m_palmYImageBuffer.data(), m_palmYImageBuffer.size()); QImage image(reinterpret_cast(m_palmYImageBuffer.data()), 600, 800, QImage::Format_Grayscale8); oss.str(""); oss << JpgPath << "/" << username << "_" << m_palmId << "_" << way << "_" << DateTime::toString(std::chrono::system_clock::now(), "%Y%m%d%H%M%S") << ".jpg"; image.save(QString::fromStdString(oss.str()), "jpg", 100); } } void Application::onCommandStarted(ModuleCommunication::MessageId messageId) { using namespace std::chrono; emit isVerifyingChanged(); } void Application::onCommandFinished(ModuleCommunication::MessageId messageId, ModuleCommunication::MessageStatus status) { // LOG(info) << m_persistenceMode << " " << m_persistenceModeStarted << " " << m_persistenceVerifyInterval; using namespace std::chrono; if (messageId == ModuleCommunication::UploadImageInfo) { m_communication->uploadImageData(m_uploadImageSendedSize, m_uploadBuffer.data() + m_uploadImageSendedSize, ImageSliceSize); } else if (messageId == ModuleCommunication::UploadImageData) { m_uploadImageSendedSize += ImageSliceSize; if (m_uploadImageSendedSize < m_uploadBuffer.size()) { auto remainSize = m_uploadBuffer.size() - m_uploadImageSendedSize; m_communication->uploadImageData(m_uploadImageSendedSize, (const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize, remainSize < ImageSliceSize ? remainSize : ImageSliceSize); m_imageUploadling = true; } if (status != ModuleCommunication::Needmore) { LOG(info) << "upload image finished, status: " << static_cast(status) << ", elapsed: " << duration_cast(system_clock::now() - m_startUploadTime); m_imageUploadling = false; if (m_imageUploadPersistenceMode) { QTimer::singleShot(1, this, [this]() { uploadImage(m_uploadPath, m_uploadUsername, m_currentUploadOperation); }); } } } if (((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) && m_persistenceMode) { // 持续识别逻辑 m_persistenceModeStarted = true; } else if (messageId == ModuleCommunication::Reset) { m_persistenceModeStarted = false; m_verifyTimer->stop(); } if (m_persistenceMode && m_persistenceModeStarted && ((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) && ((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) || (status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason) || (status == ModuleCommunication::Failed4LivenessCheck))) { m_verifyTimer->start(m_persistenceVerifyInterval * 1000); } if (((messageId == ModuleCommunication::EnrollSingle) || (messageId == ModuleCommunication::EnrollExtended)) && (status == ModuleCommunication::Success)) { emit newStatusTip(Info, "录入成功。"); } emit isVerifyingChanged(); } void Application::onVerifyTimeout() { if (m_verifyExtendedMode) { m_communication->verifyExtended(m_verifyExtendedMode, 120); } else { m_communication->verify(120); } } bool Application::startOta(const QString &path) { if (!QFile::exists(path)) { emit otaMessage("文件不存在"); return false; } auto device = CdcUpdater::searchDevice(); if (device) { LOG(info) << "device already in ota mode."; } else { if (m_communication) { m_communication->startOta(); } else { emit otaMessage("请先打开设备"); return false; } } LOG(info) << "start ota, ota path: " << path.toStdString(); m_updater = std::make_shared(); connect(m_updater.get(), &CdcUpdater::deviceDiscovered, this, &Application::onCdcDeviceDiscovered); connect(m_updater.get(), &CdcUpdater::updateFinished, this, &Application::updateFinished); connect(m_updater.get(), &CdcUpdater::progressChanged, this, &Application::otaProgressChanged); connect(m_updater.get(), &CdcUpdater::message, this, &Application::otaMessage); m_updater->start(path, device ? *device : QSerialPortInfo()); return true; } void Application::onCdcDeviceDiscovered(const QSerialPortInfo &info) { auto status = m_updater->open(info); LOG(info) << "open cdc port: " << info.portName().toStdString() << ", status: " << status; if (!status) { QTimer::singleShot(0, this, [this]() { m_updater->startSearchDevice(); }); } }