From 4724af408653ac8882081bc86bc1585ad577b8a8 Mon Sep 17 00:00:00 2001 From: luocai Date: Thu, 13 Jun 2024 15:41:40 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0uvc=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=B5=81=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Analyser/Application.cpp | 296 ++++++++++++++++++++++++++++ Analyser/Application.h | 100 ++++++++++ Analyser/CMakeLists.txt | 46 ++++- Analyser/CategoryLogSinkBackend.cpp | 12 +- Analyser/CategoryLogSinkBackend.h | 8 +- Analyser/ModuleCommunication.cpp | 67 +++++-- Analyser/ModuleCommunication.h | 28 ++- Analyser/VideoFrameProvider.cpp | 26 +++ Analyser/VideoFrameProvider.h | 16 ++ Analyser/VideoPlayer.cpp | 91 +++++++++ Analyser/VideoPlayer.h | 34 ++++ Analyser/Widget.cpp | 105 ++++------ Analyser/Widget.h | 14 +- Analyser/main.cpp | 26 +-- Analyser/qml/ConnectionItem.qml | 80 ++++++++ Analyser/qml/OperationItem.qml | 110 +++++++++++ Analyser/qml/StatusTip.qml | 56 ++++++ Analyser/qml/main.qml | 132 +++++++++++++ Analyser/resources/successfull.svg | 9 + Analyser/resources/warning.svg | 17 ++ CMakeLists.txt | 4 + OtaUpdate/CMakeLists.txt | 4 - OtaUpdate/CdcUpdater.cpp | 3 +- OtaUpdate/Widget.cpp | 3 +- Peripheral/CMakeLists.txt | 4 + Peripheral/DeviceDiscovery.cpp | 32 +++ Peripheral/DeviceDiscovery.h | 1 + Readme.md | 4 + 28 files changed, 1192 insertions(+), 136 deletions(-) create mode 100644 Analyser/Application.cpp create mode 100644 Analyser/Application.h create mode 100644 Analyser/VideoFrameProvider.cpp create mode 100644 Analyser/VideoFrameProvider.h create mode 100644 Analyser/VideoPlayer.cpp create mode 100644 Analyser/VideoPlayer.h create mode 100644 Analyser/qml/ConnectionItem.qml create mode 100644 Analyser/qml/OperationItem.qml create mode 100644 Analyser/qml/StatusTip.qml create mode 100644 Analyser/qml/main.qml create mode 100644 Analyser/resources/successfull.svg create mode 100644 Analyser/resources/warning.svg diff --git a/Analyser/Application.cpp b/Analyser/Application.cpp new file mode 100644 index 0000000..5b9e1ce --- /dev/null +++ b/Analyser/Application.cpp @@ -0,0 +1,296 @@ +#include "Application.h" +#include "AsyncEvent.h" +#include "BoostLog.h" +#include "CategoryLogSinkBackend.h" +#include "Database.h" +#include "DeviceDiscovery.h" +#include "VideoFrameProvider.h" +#include "VideoPlayer.h" +#include +#include +#include +#include +#include +#include + +Application::Application(int &argc, char **argv) : m_app(std::make_shared(argc, argv)) { + 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); +} + +void Application::onNewVerifyResult(uint16_t userid, const QString &username) { + QTimer::singleShot(0, this, [this, userid, username]() { + emit newStatusTip(Info, QString("%1,识别耗时: %2ms") + .arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username) + .arg(m_verifyElapsed.count())); + }); +} + +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:/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::newVerifyResult, this, &Application::onNewVerifyResult); + connect(m_communication.get(), &ModuleCommunication::newPalmFeature, this, &Application::onNewPalmFeature); + connect(m_communication.get(), &ModuleCommunication::newEnrolledImageInfo, this, + &Application::onNewEnrolledImageInfo); + 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 ? "串口打开成功" : "串口打开失败"); + 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(uint8_t timeout) { + if (m_communication->currentMessageId() != ModuleCommunication::Idle) { + m_communication->reset(); + } + m_communication->verify(timeout); +} + +void Application::enroll(const QString &username, uint8_t timeout) { + if (m_communication->currentMessageId() != ModuleCommunication::Idle) { + m_communication->reset(); + } + m_communication->enroll(username, timeout); +} + +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(); +} + +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(); + } +} + +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); +} + +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::onNewEnrolledImageInfo(uint32_t size, const uint8_t *md5) { + using namespace std::chrono; + m_enrolledImageSize = size; + m_communication->requestEnrolledImage(0, 1024); + m_enrollYImageBuffer.clear(); + m_startUploadTime = system_clock::now(); +} + +void Application::onNewImageSliceData(const std::vector &data) { + using namespace std::chrono; + // LOG(info) << "onNewImageSliceData:" << data.size() << ", already received: " << m_enrollYImageBuffer.size() + // << ", total size: " << m_enrolledImageSize; + m_enrollYImageBuffer.append(reinterpret_cast(data.data()), data.size()); + if (m_enrollYImageBuffer.size() < m_enrolledImageSize) { + m_communication->requestEnrolledImage(m_enrollYImageBuffer.size(), 1024); + } else { + LOG(info) << "request finished, elapsed: " + << duration_cast(system_clock::now() - m_startUploadTime); + QImage image(reinterpret_cast(m_enrollYImageBuffer.data()), 600, 800, + QImage::Format_Grayscale8); + image.save("test.jpg"); + } +} + +void Application::onCommandStarted(ModuleCommunication::MessageId messageId) { + using namespace std::chrono; + if (messageId == ModuleCommunication::Verify) { + m_verifyStartTime = system_clock::now(); + } + 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::Verify) { + m_verifyElapsed = duration_cast(system_clock::now() - m_verifyStartTime); + LOG(info) << "verify elapsed " << m_verifyElapsed; + if (m_verifyElapsed > hours(10 * 1000)) { + m_verifyElapsed = milliseconds(100); + } + } + + if (messageId == ModuleCommunication::Verify && m_persistenceMode) { // 持续识别逻辑 + m_persistenceModeStarted = true; + } else if (messageId == ModuleCommunication::Reset) { + m_persistenceModeStarted = false; + m_verifyTimer->stop(); + } + + if (m_persistenceMode && m_persistenceModeStarted && messageId == ModuleCommunication::Verify && + ((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) || + (status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason))) { + m_verifyTimer->start(m_persistenceVerifyInterval * 1000); + } + + if ((messageId == ModuleCommunication::EnrollSingle) && (status == ModuleCommunication::Success)) { + emit newStatusTip(Info, "录入成功。"); + } + emit isVerifyingChanged(); +} + +void Application::onVerifyTimeout() { + m_communication->verify(120); +} diff --git a/Analyser/Application.h b/Analyser/Application.h new file mode 100644 index 0000000..6cf76c6 --- /dev/null +++ b/Analyser/Application.h @@ -0,0 +1,100 @@ +#ifndef __APPLICATION_H__ +#define __APPLICATION_H__ + +#include "DataStructure.h" +#include "ModuleCommunication.h" +#include "Singleton.h" +#include +#include +#include + +class QGuiApplication; +class Database; +class VideoPlayer; +class VideoFrameProvider; +class QTimer; + +class Application : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(ModuleCommunication *module READ module CONSTANT) + Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged) + Q_PROPERTY(bool uvcOpened READ uvcOpened NOTIFY uvcOpenedChanged) + Q_PROPERTY(bool persistenceMode READ persistenceMode WRITE setPersistenceMode NOTIFY persistenceModeChanged) + Q_PROPERTY(int persistenceVerifyInterval READ persistenceVerifyInterval WRITE setPersistenceVerifyInterval NOTIFY + persistenceVerifyIntervalChanged) + Q_PROPERTY(bool isVerifying READ isVerifying NOTIFY isVerifyingChanged) + friend class Amass::Singleton; + +public: + enum TipType { + Tip, + Error, + Info, + }; + Q_ENUM(TipType) + + void initializeLogger(); + int exec(); + Q_INVOKABLE QStringList availableSerialPorts() const; + Q_INVOKABLE QStringList availableUsbVideoCameras() const; + Q_INVOKABLE bool open(const QString &portName, int baudRate); + Q_INVOKABLE bool openUVC(const QString &deviceName); + Q_INVOKABLE void close(); + Q_INVOKABLE void closeUVC(); + Q_INVOKABLE void verify(uint8_t timeout); + Q_INVOKABLE void enroll(const QString &username, uint8_t timeout); + Q_INVOKABLE void deleteUser(uint16_t userid); + Q_INVOKABLE void deleteAll(); + ModuleCommunication *module() const; + bool connected() const; + bool uvcOpened() const; + bool persistenceMode() const; + void setPersistenceMode(bool enabled); + + int persistenceVerifyInterval() const; + void setPersistenceVerifyInterval(int interval); + bool isVerifying() const; + +signals: + void connectedChanged(); + void persistenceModeChanged(); + void persistenceVerifyIntervalChanged(); + void isVerifyingChanged(); + void uvcOpenedChanged(); + void newVideoFrame(); + void newLog(const QString &log); + void newStatusTip(TipType type, const QString &tip); + +protected: + Application(int &argc, char **argv); + void onNewVerifyResult(uint16_t userid, const QString &username); + void onNewPalmFeature(const PalmFeature &feature); + void onErrorOccurred(const QString &error); + void onNewEnrolledImageInfo(uint32_t size, const uint8_t *md5); + void onNewImageSliceData(const std::vector &data); + void onCommandStarted(ModuleCommunication::MessageId messageId); + void onCommandFinished(ModuleCommunication::MessageId messageId, ModuleCommunication::MessageStatus status); + void onVerifyTimeout(); + +private: + std::shared_ptr m_app; + std::shared_ptr m_communication; + std::shared_ptr m_database; + + bool m_persistenceMode = true; // 模组持续识别 + bool m_persistenceModeStarted = false; + int m_persistenceVerifyInterval = 1; + QTimer *m_verifyTimer = nullptr; + std::chrono::system_clock::time_point m_verifyStartTime; + std::chrono::milliseconds m_verifyElapsed; + + uint32_t m_enrolledImageSize = 0; + QByteArray m_enrollYImageBuffer; + std::chrono::system_clock::time_point m_startUploadTime; + + std::shared_ptr m_videoPlayer; + VideoFrameProvider *m_videoFrameProvider; +}; + +#endif // __APPLICATION_H__ diff --git a/Analyser/CMakeLists.txt b/Analyser/CMakeLists.txt index c555c56..198bf38 100644 --- a/Analyser/CMakeLists.txt +++ b/Analyser/CMakeLists.txt @@ -1,15 +1,49 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets SerialPort) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets SerialPort) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick QuickTemplates2 Widgets SerialPort) add_executable(Analyser Analyser.rc main.cpp + Application.h Application.cpp CategoryLogSinkBackend.h CategoryLogSinkBackend.cpp Widget.h Widget.cpp ModuleCommunication.h ModuleCommunication.cpp PalmFeatureTableModel.h PalmFeatureTableModel.cpp + VideoFrameProvider.h VideoFrameProvider.cpp + VideoPlayer.h VideoPlayer.cpp +) + +qt_add_qml_module(Analyser + URI Analyser + VERSION 1.0 + QML_FILES + qml/main.qml + qml/ConnectionItem.qml + qml/OperationItem.qml + qml/StatusTip.qml + RESOURCES + resources/successfull.svg + resources/warning.svg +) + +target_compile_definitions(Analyser + PRIVATE _CRT_SECURE_NO_WARNINGS +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_property(TARGET Analyser PROPERTY + WIN32_EXECUTABLE true + ) +endif() + +target_include_directories(Analyser + PRIVATE ${FFmpeg_INCLUDE_DIR} +) + +target_link_directories(Analyser + PRIVATE ${FFmpeg_LIB_DIR} ) target_link_libraries(Analyser @@ -17,6 +51,14 @@ target_link_libraries(Analyser PRIVATE Encrypt PRIVATE Database PRIVATE Ws2_32 + PRIVATE Peripheral + PRIVATE avcodec + PRIVATE swscale + PRIVATE avutil + PRIVATE avdevice + PRIVATE avformat + PRIVATE Qt${QT_VERSION_MAJOR}::Quick + PRIVATE Qt${QT_VERSION_MAJOR}::QuickTemplates2 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort ) diff --git a/Analyser/CategoryLogSinkBackend.cpp b/Analyser/CategoryLogSinkBackend.cpp index 33a208d..72fabd6 100644 --- a/Analyser/CategoryLogSinkBackend.cpp +++ b/Analyser/CategoryLogSinkBackend.cpp @@ -1,15 +1,13 @@ #include "CategoryLogSinkBackend.h" -#include "AsyncEvent.h" -#include -#include +#include "BoostLog.h" -CategoryLogSinkBackend::CategoryLogSinkBackend(const std::string &category, QTextBrowser *target) - : m_category(category), m_target(target) { +CategoryLogSinkBackend::CategoryLogSinkBackend(const std::string &category, Append &&append) + : m_category(category), m_append(std::move(append)) { } void CategoryLogSinkBackend::consume(const boost::log::record_view &record, string_type const &output) { auto &&category = record[AmassKeywords::category]; - if (category == m_category) { - Amass::executeAtObjectThread(m_target, [this, output]() { m_target->append(QString::fromStdString(output)); }); + if ((category == m_category) && m_append) { + m_append(output); } } diff --git a/Analyser/CategoryLogSinkBackend.h b/Analyser/CategoryLogSinkBackend.h index a11f4d1..5e175de 100644 --- a/Analyser/CategoryLogSinkBackend.h +++ b/Analyser/CategoryLogSinkBackend.h @@ -2,19 +2,19 @@ #define __CATEGORYLOGSINKBACKEND_H__ #include +#include #include -class QTextBrowser; - class CategoryLogSinkBackend : public boost::log::sinks::basic_formatted_sink_backend { public: - CategoryLogSinkBackend(const std::string &category, QTextBrowser *target); + using Append = std::function; + CategoryLogSinkBackend(const std::string &category, Append &&append); void consume(const boost::log::record_view &record, string_type const &output); private: std::string m_category; - QTextBrowser *m_target = nullptr; + Append m_append; }; #endif // __CATEGORYLOGSINKBACKEND_H__ diff --git a/Analyser/ModuleCommunication.cpp b/Analyser/ModuleCommunication.cpp index 9197e43..f66d047 100644 --- a/Analyser/ModuleCommunication.cpp +++ b/Analyser/ModuleCommunication.cpp @@ -1,6 +1,6 @@ #include "ModuleCommunication.h" #include "BoostLog.h" -#include +#include "StringUtility.h" #include #include #include @@ -16,14 +16,19 @@ static inline uint8_t xor_checksum_byte(const uint8_t *data, uint32_t len) { ModuleCommunication::ModuleCommunication(QObject *parent) : QObject{parent} { } -bool ModuleCommunication::open(const QString &portName) { +bool ModuleCommunication::open(const QString &portName, int baudRate) { bool ret = true; m_serialPort = std::make_shared(portName); - m_serialPort->setBaudRate(QSerialPort::Baud115200); + m_serialPort->setBaudRate(baudRate); connect(m_serialPort.get(), &QSerialPort::readyRead, this, &ModuleCommunication::onReadyRead); + connect(m_serialPort.get(), &QSerialPort::errorOccurred, this, &ModuleCommunication::onErrorOccurred); ret = m_serialPort->open(QSerialPort::ReadWrite); - LOG_CAT(info, GUI) << "打开串口(" << portName.toStdString() << ")" << (ret ? "成功" : "失败") << "。"; + LOG_CAT(info, GUI) << "打开串口(" << portName.toStdString() << ")" << (ret ? "成功" : "失败") + << ", 波特率: " << baudRate; + if (!ret) { + LOG(error) << m_serialPort->errorString().toStdString(); + } LOG_CAT(info, GUI) << Separator; return ret; } @@ -34,6 +39,8 @@ void ModuleCommunication::verify(uint8_t timeout) { auto [frameData, frameSize] = generateFrame(Verify, reinterpret_cast(&data), sizeof(data)); m_serialPort->write(reinterpret_cast(frameData), frameSize); + m_currentMessageId = Verify; + emit commandStarted(Verify); LOG_CAT(info, GUI) << "发送识别指令: " << protocolDataFormatString(frameData, frameSize); LOG_CAT(info, GUI) << Separator; @@ -52,12 +59,18 @@ void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) { strncpy(reinterpret_cast(data.username), username.c_str(), sizeof(data.username)); auto [frameData, frameSize] = generateFrame(EnrollSingle, reinterpret_cast(&data), sizeof(data)); m_serialPort->write(reinterpret_cast(frameData), frameSize); + m_currentMessageId = EnrollSingle; + emit commandStarted(EnrollSingle); LOG_CAT(info, GUI) << "发送注册指令: " << protocolDataFormatString(frameData, frameSize); LOG_CAT(info, GUI) << "用户名: " << username << ", 超时时间: " << static_cast(timeout) << "s"; LOG_CAT(info, GUI) << Separator; } +void ModuleCommunication::enroll(const QString &username, uint8_t timeout) { + return enroll(username.toStdString(), timeout); +} + void ModuleCommunication::enrollEx(const std::string &username, uint8_t timeout) { EnrollData data = {0}; data.timeout = timeout; @@ -74,6 +87,8 @@ void ModuleCommunication::deleteUser(uint16_t userid) { uint16_t n = htons(userid); auto [frameData, frameSize] = generateFrame(DeleteUser, reinterpret_cast(&n), sizeof(n)); m_serialPort->write(reinterpret_cast(frameData), frameSize); + m_currentMessageId = DeleteUser; + emit commandStarted(DeleteUser); LOG_CAT(info, GUI) << "发送删除用户指令: " << protocolDataFormatString(frameData, frameSize); LOG_CAT(info, GUI) << "删除用户ID: " << userid; LOG_CAT(info, GUI) << Separator; @@ -82,6 +97,8 @@ void ModuleCommunication::deleteUser(uint16_t userid) { void ModuleCommunication::deleteAll() { auto [frameData, frameSize] = generateFrame(DeleteAll); m_serialPort->write(reinterpret_cast(frameData), frameSize); + m_currentMessageId = DeleteAll; + emit commandStarted(DeleteAll); LOG_CAT(info, GUI) << "发送删除所有指令: " << protocolDataFormatString(frameData, frameSize); LOG_CAT(info, GUI) << Separator; } @@ -110,7 +127,7 @@ void ModuleCommunication::enrollPalmFeature(uint16_t userid, const PalmFeature & auto buffer = new uint8_t[sizeof(PalmFeatureHeader) + feature.feature.size()]; auto header = reinterpret_cast(buffer); header->userid = htons(userid); - header->featureTotalSize = htons(feature.feature.size()); + header->featureTotalSize = htons(static_cast(feature.feature.size())); strncpy(reinterpret_cast(header->username), feature.username.c_str(), sizeof(header->username)); mbedtls_md5_context context; @@ -124,8 +141,8 @@ void ModuleCommunication::enrollPalmFeature(uint16_t userid, const PalmFeature & memcpy(buffer + sizeof(PalmFeatureHeader), feature.feature.data(), feature.feature.size()); - auto [frameData, frameSize] = - generateFrame(RegisterPalmFeature, buffer, sizeof(PalmFeatureHeader) + feature.feature.size()); + auto [frameData, frameSize] = generateFrame( + RegisterPalmFeature, buffer, static_cast(sizeof(PalmFeatureHeader) + feature.feature.size())); m_serialPort->write(reinterpret_cast(frameData), frameSize); LOG_CAT(info, GUI) << "发送注册掌静脉特征指令: " << protocolDataFormatString(frameData, frameSize); LOG_CAT(info, GUI) << Separator; @@ -133,6 +150,10 @@ void ModuleCommunication::enrollPalmFeature(uint16_t userid, const PalmFeature & if (buffer != nullptr) delete[] buffer; } +ModuleCommunication::MessageId ModuleCommunication::currentMessageId() const { + return m_currentMessageId; +} + void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { uint8_t messageId = data[2]; switch (messageId) { @@ -152,13 +173,22 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); if (result == Success) { auto info = reinterpret_cast(data + 7); - LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid) + uint16_t userid = ntohs(info->userid); + LOG_CAT(info, GUI) << "用户ID: " << userid << ", 用户名: " << std::string_view(reinterpret_cast(info->username)); + emit newVerifyResult(userid, reinterpret_cast(info->username)); } else if (result == Failed4Timeout) { LOG_CAT(info, GUI) << "识别超时。"; - } else if (result == Failed4UnknownReason) { + } else if (result == Rejected) { + LOG_CAT(info, GUI) << "模组拒绝该命令。"; + } else if (result == Failed4UnknownUser) { LOG_CAT(info, GUI) << "未录入用户。"; + emit newVerifyResult(InvalidUserId, ""); + } else if (result == Failed4UnknownReason) { + LOG_CAT(info, GUI) << "未知错误。"; + emit newVerifyResult(InvalidUserId, ""); } else { + LOG_CAT(info, GUI) << "未知错误(" << static_cast(result) << ")。"; } LOG_CAT(info, GUI) << Separator; break; @@ -178,11 +208,10 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); if (result == Success) { auto info = reinterpret_cast(data + 7); - uint16_t lineSize = ntohs(info->lineSize); + uint16_t width = ntohs(info->width); uint16_t height = ntohs(info->height); - LOG_CAT(info, GUI) << "图片大小: " << ntohs(info->width) << "x" << height - << ", line size: " << lineSize; - emit newEnrolledImageInfo(lineSize * height, info->md5); + LOG_CAT(info, GUI) << "图片大小: " << width << "x" << height; + emit newEnrolledImageInfo(width * height, info->md5); } LOG_CAT(info, GUI) << Separator; break; @@ -244,6 +273,8 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { << ", data: " << protocolDataFormatString(data, size); break; } + m_currentMessageId = Idle; + emit commandFinished(static_cast(replyId), static_cast(result)); break; } case Note: { @@ -256,8 +287,6 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { break; } case PalmState: { // 模组返回的数据为当前帧的手掌状态 - auto info = reinterpret_cast(data + 7); - LOG(info) << info->state; break; } case UnknownError: { @@ -282,6 +311,7 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) { } void ModuleCommunication::onReadyRead() { + // LOG(info) << "ModuleCommunication::onReadyRead"; m_receivedBuffer.append(m_serialPort->readAll()); while (m_receivedBuffer.size() >= 2) { int beginIndex = -1; @@ -299,6 +329,7 @@ void ModuleCommunication::onReadyRead() { m_receivedBuffer.remove(0, beginIndex); beginIndex = 0; } + if (m_receivedBuffer.size() < 5) break; uint16_t packageSize = *reinterpret_cast(m_receivedBuffer.data() + 3); packageSize = ntohs(packageSize); @@ -318,6 +349,12 @@ void ModuleCommunication::onReadyRead() { } } +void ModuleCommunication::onErrorOccurred(QSerialPort::SerialPortError error) { + if (error == QSerialPort::NoError) return; + LOG_CAT(info, GUI) << m_serialPort->portName().toStdString() << ": " << m_serialPort->errorString().toStdString(); + emit errorOccurred(m_serialPort->errorString()); +} + std::pair ModuleCommunication::generateFrame(MessageId command, const uint8_t *data, uint16_t size) { static uint8_t sendBuffer[1024] = {0}; diff --git a/Analyser/ModuleCommunication.h b/Analyser/ModuleCommunication.h index 1c033d3..6a268bf 100644 --- a/Analyser/ModuleCommunication.h +++ b/Analyser/ModuleCommunication.h @@ -3,16 +3,16 @@ #include "DataStructure.h" #include +#include #include -class QSerialPort; - class ModuleCommunication : public QObject { Q_OBJECT static constexpr uint32_t UsernameSize = 32; static constexpr const char *Separator = "----------"; public: + constexpr static uint16_t InvalidUserId = std::numeric_limits::max(); enum MessageId : uint8_t { Reply = 0, Note = 0x01, @@ -25,6 +25,7 @@ public: DeleteAll = 0x21, RegisterPalmFeature = 0xF9, RequestPalmFeature = 0xFA, + Idle = 0xFF, }; enum NoteId : uint8_t { @@ -83,7 +84,6 @@ public: struct EnrolledImageReply { uint16_t width; - uint16_t lineSize; uint16_t height; uint8_t md5[16]; }; @@ -115,32 +115,44 @@ public: #pragma pack() explicit ModuleCommunication(QObject *parent = nullptr); - bool open(const QString &portName); - void verify(uint8_t timeout); - void reset(); + bool open(const QString &portName, int baudRate); + Q_INVOKABLE void verify(uint8_t timeout); + Q_INVOKABLE void reset(); void enroll(const std::string &username, uint8_t timeout); + Q_INVOKABLE void enroll(const QString &username, uint8_t timeout); + void enrollEx(const std::string &username, uint8_t timeout); - void deleteUser(uint16_t userid); - void deleteAll(); + Q_INVOKABLE void deleteUser(uint16_t userid); + Q_INVOKABLE void deleteAll(); void requestEnrolledImage(uint32_t offset, uint32_t size); void requestPalmFeature(uint16_t userid); void enrollPalmFeature(uint16_t userid, const PalmFeature &feature); + + MessageId currentMessageId() const; + signals: + void newVerifyResult(uint16_t userid, const QString &username); void newPalmFeature(const PalmFeature &feature); void newEnrolledImageInfo(uint32_t size, const uint8_t *md5); void newImageSliceData(const std::vector &data); + void errorOccurred(const QString &error); + void commandStarted(ModuleCommunication::MessageId messageId); + void commandFinished(MessageId messageId, MessageStatus status); protected: void processPackage(const uint8_t *data, uint16_t size); void onReadyRead(); + void onErrorOccurred(QSerialPort::SerialPortError error); std::pair generateFrame(MessageId command, const uint8_t *data = nullptr, uint16_t size = 0); std::string protocolDataFormatString(const uint8_t *data, int size); private: std::shared_ptr m_serialPort; QByteArray m_receivedBuffer; + + MessageId m_currentMessageId = ModuleCommunication::Idle; }; #endif // MODULECOMMUNICATION_H diff --git a/Analyser/VideoFrameProvider.cpp b/Analyser/VideoFrameProvider.cpp new file mode 100644 index 0000000..32ae5ed --- /dev/null +++ b/Analyser/VideoFrameProvider.cpp @@ -0,0 +1,26 @@ +#include "VideoFrameProvider.h" + +VideoFrameProvider::VideoFrameProvider() + : QQuickImageProvider(QQuickImageProvider::Image), m_image(800, 600, QImage::Format_RGB32) { + m_image.fill(Qt::black); +} + +QImage VideoFrameProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + Q_UNUSED(id); + + if (size) *size = m_image.size(); + + if (requestedSize.width() > 0 && requestedSize.height() > 0) + return m_image.scaled(requestedSize.width(), requestedSize.height(), Qt::KeepAspectRatio); + + return m_image; +} + +void VideoFrameProvider::setImage(const QImage &image) { + m_image = image; +} + +void VideoFrameProvider::reset() { + m_image = QImage(800, 600, QImage::Format_RGB32); + m_image.fill(Qt::black); +} diff --git a/Analyser/VideoFrameProvider.h b/Analyser/VideoFrameProvider.h new file mode 100644 index 0000000..b919968 --- /dev/null +++ b/Analyser/VideoFrameProvider.h @@ -0,0 +1,16 @@ +#ifndef __VIDEOFRAMEPROVIDER_H__ +#define __VIDEOFRAMEPROVIDER_H__ + +#include + +class VideoFrameProvider : public QQuickImageProvider { +public: + VideoFrameProvider(); + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) final; + void setImage(const QImage &image); + void reset(); + +private: + QImage m_image; +}; +#endif // __VIDEOFRAMEPROVIDER_H__ diff --git a/Analyser/VideoPlayer.cpp b/Analyser/VideoPlayer.cpp new file mode 100644 index 0000000..196a4b8 --- /dev/null +++ b/Analyser/VideoPlayer.cpp @@ -0,0 +1,91 @@ +#include "VideoPlayer.h" +#include "BoostLog.h" +#include +extern "C" { +#include "libavdevice/avdevice.h" +#include "libavformat/avformat.h" +} + +VideoPlayer::VideoPlayer() { + static bool init = false; + if (!init) { + avdevice_register_all(); + init = true; + } +} + +void VideoPlayer::setFrameCallback(FrameCallback &&callback) { + m_callback = std::move(callback); +} + +void VideoPlayer::setErrorCallback(ErrorCallback &&callback) { + m_errorCallback = std::move(callback); +} + +VideoPlayer::~VideoPlayer() { + close(); +} + +bool VideoPlayer::open(const std::string &deviceName) { + bool ret = true; + m_formatContext = avformat_alloc_context(); + auto format = av_find_input_format("dshow"); + + AVDictionary *dictionary = nullptr; + // ffmpeg -f dshow -list_options true -i video="UVC Camera" + av_dict_set(&dictionary, "video_size", "800*600", 0); + + std::ostringstream oss; + oss << "video=" << deviceName; + auto device = oss.str(); + int status = avformat_open_input(&m_formatContext, "video=UVC Camera", format, &dictionary); + if (status != 0) { + char message[256] = {0}; + av_make_error_string(message, sizeof(message), status); + LOG(error) << "open device[" << device << "] failed: " << status << "," << message; + ret = false; + } else { + m_exit = false; + m_thread = std::thread(&VideoPlayer::run, this); + } + av_dict_free(&dictionary); + + return ret; +} + +void VideoPlayer::close() { + m_exit = true; + if (m_thread.joinable()) { + m_thread.join(); + } + avformat_close_input(&m_formatContext); + avformat_free_context(m_formatContext); + m_formatContext = nullptr; +} + +void VideoPlayer::run() { + auto packet = av_packet_alloc(); + while (!m_exit) { + auto status = av_read_frame(m_formatContext, packet); + if (status == 0) { + QImage image; + image.loadFromData(packet->data, packet->size); + image.mirror(false, true); + if (!image.isNull() && m_callback) m_callback(image); + } else { + char message[256] = {0}; + av_make_error_string(message, sizeof(message), status); + if (m_errorCallback) m_errorCallback(status, message); + LOG(error) << "av_read_frame() failed: " << status << "," << message; + if (status == -EIO) { + break; + } + } + av_packet_unref(packet); + } + + av_packet_free(&packet); + QImage image(800, 600, QImage::Format_RGB888); + image.fill(Qt::black); + if (m_callback) m_callback(image); +} diff --git a/Analyser/VideoPlayer.h b/Analyser/VideoPlayer.h new file mode 100644 index 0000000..a1164ca --- /dev/null +++ b/Analyser/VideoPlayer.h @@ -0,0 +1,34 @@ +#ifndef __VIDEOPLAYER_H__ +#define __VIDEOPLAYER_H__ + +#include +#include +#include + +class QImage; +struct AVFormatContext; + +class VideoPlayer { +public: + using FrameCallback = std::function; + using ErrorCallback = std::function; + VideoPlayer(); + void setFrameCallback(FrameCallback &&callback); + void setErrorCallback(ErrorCallback &&callback); + ~VideoPlayer(); + bool open(const std::string &deviceName); + void close(); + +protected: + void run(); + +private: + AVFormatContext *m_formatContext = nullptr; + bool m_exit = true; + std::thread m_thread; + + FrameCallback m_callback; + ErrorCallback m_errorCallback; +}; + +#endif // __VIDEOPLAYER_H__ diff --git a/Analyser/Widget.cpp b/Analyser/Widget.cpp index 0723d84..04fa671 100644 --- a/Analyser/Widget.cpp +++ b/Analyser/Widget.cpp @@ -1,4 +1,5 @@ #include "Widget.h" +#include "Application.h" #include "BoostLog.h" #include "CategoryLogSinkBackend.h" #include "Database.h" @@ -72,26 +73,15 @@ Widget::Widget(QWidget *parent) : QWidget{parent} { layout->addLayout(operatorLayout, 1); layout->addWidget(tabWidget, 3); - m_database = std::make_shared(); m_featureModel = new PalmFeatureTableModel(this); m_featureTableView->setModel(m_featureModel); QTimer::singleShot(0, this, [this]() { onSerialRefreshButtonClicked(); m_commandGroupBox->setEnabled(false); - if (!m_database->open("database.db")) { - LOG(error) << "open database failed."; - } }); } -void Widget::initializeLogger() { - auto backend = boost::make_shared("GUI", m_logBrowser); - using SynchronousCategorySink = boost::log::sinks::synchronous_sink; - auto sink = boost::make_shared(backend); - // sink->set_formatter(&zlogFormatter); - boost::log::core::get()->add_sink(sink); -} QGroupBox *Widget::initializeCommandGroupBox() { auto ret = new QGroupBox("命令"); @@ -204,55 +194,19 @@ QGroupBox *Widget::initializeUvcGroupBox() { return ret; } -void Widget::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 Widget::onNewEnrolledImageInfo(uint32_t size, const uint8_t *md5) { - m_enrolledImageSize = size; - m_receivedImageSize = 0; - m_communication->requestEnrolledImage(0, 1024); - m_ofs = std::make_shared("palm.yuv", std::ofstream::binary); -} - -void Widget::onNewImageSliceData(const std::vector &data) { - m_receivedImageSize += data.size(); - LOG(info) << "onNewImageSliceData:" << data.size() << ", already received: " << m_receivedImageSize - << ", total size: " << m_enrolledImageSize; - m_ofs->write(reinterpret_cast(data.data()), data.size()); - if (m_receivedImageSize < m_enrolledImageSize) { - m_communication->requestEnrolledImage(m_receivedImageSize, 1024); - } else { - LOG(info) << "request finished."; - m_ofs.reset(); - } -} - void Widget::onSerialConnectButtonClicked() { auto button = dynamic_cast(sender()); if (button == nullptr) return; auto text = button->text(); if (text == "连接") { auto portName = m_serialComboBox->currentText(); - m_communication = std::make_shared(); - connect(m_communication.get(), &ModuleCommunication::newPalmFeature, this, &Widget::onNewPalmFeature); - connect(m_communication.get(), &ModuleCommunication::newEnrolledImageInfo, this, - &Widget::onNewEnrolledImageInfo); - connect(m_communication.get(), &ModuleCommunication::newImageSliceData, this, &Widget::onNewImageSliceData); - bool status = m_communication->open(portName); + auto status = Amass::Singleton::instance()->open(portName, 2000000); if (status) { m_commandGroupBox->setEnabled(true); button->setText("关闭"); } } else if (text == "关闭") { - m_communication.reset(); + Amass::Singleton::instance()->close(); m_commandGroupBox->setEnabled(false); button->setText("连接"); } @@ -262,6 +216,7 @@ void Widget::onSerialRefreshButtonClicked() { m_serialComboBox->clear(); auto ports = QSerialPortInfo::availablePorts(); for (auto &port : ports) { + if (port.description() == "蓝牙链接上的标准串行") continue; m_serialComboBox->addItem(port.portName()); } } @@ -270,51 +225,65 @@ void Widget::onUvcRefreshButtonClicked() { } void Widget::onEnrollButtonClicked() { + auto module = Amass::Singleton::instance()->module(); + if (!module) return; auto name = m_enrollNameEdit->text(); auto timeout = m_enrollTimeoutEdit->text().toInt(); - m_communication->enroll(name.toStdString(), timeout); + module->enroll(name.toStdString(), timeout); } void Widget::onEnrollExButtonClicked() { + auto module = Amass::Singleton::instance()->module(); + if (!module) return; auto name = m_enrollNameEdit->text(); auto timeout = m_enrollTimeoutEdit->text().toInt(); - m_communication->enrollEx(name.toStdString(), timeout); + module->enrollEx(name.toStdString(), timeout); } void Widget::onVerifyButtonClicked() { - if (!m_communication) return; + auto module = Amass::Singleton::instance()->module(); + if (!module) return; auto timeout = m_verifyTimeoutEdit->text().toInt(); - m_communication->verify(timeout); + module->verify(timeout); } void Widget::onDeleteAllButtonClicked() { - if (!m_communication) return; - m_communication->deleteAll(); + auto module = Amass::Singleton::instance()->module(); + if (!module) return; + module->deleteAll(); } void Widget::onDeleteButtonClicked() { - if (!m_communication) return; + auto module = Amass::Singleton::instance()->module(); + if (!module) return; auto id = m_deleteIdEdit->text().toInt(); - m_communication->deleteUser(id); + module->deleteUser(id); } void Widget::onRequestPalmFeatureButtonClicked() { - if (!m_communication) return; + auto module = Amass::Singleton::instance()->module(); + if (!module) return; auto id = m_palmFeatureEdit->text().toInt(); - m_communication->requestPalmFeature(id); + module->requestPalmFeature(id); } void Widget::onRegisterPalmFeatureButtonClicked() { - if (!m_communication) return; - auto features = m_database->palmFeatures(); - if (features.empty()) { - LOG(error) << "feature is empty."; - return; - } - m_communication->enrollPalmFeature(264, features.at(0)); + auto module = Amass::Singleton::instance()->module(); + if (!module) return; + // auto features = m_database->palmFeatures(); + // if (features.empty()) { + // LOG(error) << "feature is empty."; + // return; + // } + // module->enrollPalmFeature(264, features.at(0)); } void Widget::onResetButtonClicked() { - if (!m_communication) return; - m_communication->reset(); + auto module = Amass::Singleton::instance()->module(); + if (!module) return; + module->reset(); +} + +void Widget::onNewLog(const QString &log) { + m_logBrowser->append(log); } diff --git a/Analyser/Widget.h b/Analyser/Widget.h index 02a454b..9f7e5e0 100644 --- a/Analyser/Widget.h +++ b/Analyser/Widget.h @@ -1,7 +1,6 @@ #ifndef WIDGET_H #define WIDGET_H -#include "DataStructure.h" #include #include @@ -19,7 +18,7 @@ class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); - void initializeLogger(); + void onNewLog(const QString &log); protected: QGroupBox *initializeCommandGroupBox(); @@ -45,10 +44,6 @@ protected: QGroupBox *initializeUvcGroupBox(); - void onNewPalmFeature(const PalmFeature &feature); - void onNewEnrolledImageInfo(uint32_t size, const uint8_t *md5); - void onNewImageSliceData(const std::vector &data); - private: QComboBox *m_serialComboBox = nullptr; QPushButton *m_serialConnectButton = nullptr; @@ -68,15 +63,8 @@ private: QLineEdit *m_palmFeatureEdit = nullptr; - std::shared_ptr m_communication; - std::shared_ptr m_database; - PalmFeatureTableModel *m_featureModel = nullptr; QTableView *m_featureTableView = nullptr; - - uint32_t m_enrolledImageSize = 0; - uint32_t m_receivedImageSize = 0; - std::shared_ptr m_ofs; }; #endif // WIDGET_H diff --git a/Analyser/main.cpp b/Analyser/main.cpp index 4fac0ff..ae5b6ee 100644 --- a/Analyser/main.cpp +++ b/Analyser/main.cpp @@ -1,21 +1,21 @@ +#include "Application.h" #include "BoostLog.h" +#include "DeviceDiscovery.h" #include "Widget.h" -#include -#include int main(int argc, char *argv[]) { + using namespace Amass; boost::log::initialize("logs/app"); - QApplication a(argc, argv); - QFont font; - font.setPointSize(16); - a.setFont(font); - Widget w; - w.initializeLogger(); - w.setWindowTitle("L015上位机工具"); - w.setMinimumWidth(1120); - w.setMinimumHeight(640); - w.show(); + auto app = Singleton::instance(argc, argv); + app->initializeLogger(); - return a.exec(); + // Widget w; + // w.setWindowTitle("L015上位机工具"); + // w.setMinimumWidth(1120); + // w.setMinimumHeight(640); + // w.show(); + // QObject::connect(app.get(), &Application::newLog, &w, &Widget::onNewLog); + + return app->exec(); } diff --git a/Analyser/qml/ConnectionItem.qml b/Analyser/qml/ConnectionItem.qml new file mode 100644 index 0000000..7013cd4 --- /dev/null +++ b/Analyser/qml/ConnectionItem.qml @@ -0,0 +1,80 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Analyser + +RowLayout { + GroupBox { + title: "串口设置" + Layout.fillWidth: true + GridLayout { + columns: 2 + Text { + text: qsTr("端口") + } + ComboBox { + id: serialPort + enabled: !App.connected + implicitWidth: 100 + } + + Text { + text: qsTr("波特率") + } + + ComboBox { + id: baudrate + enabled: !App.connected + implicitWidth: 110 + model: ["2000000", "115200"] + } + + Button { + text: "刷新" + enabled: !App.connected + onClicked: { + serialPort.model = App.availableSerialPorts() + } + } + + Button { + text: App.connected ? "断开" : "连接" + onClicked: App.connected ? App.close() : App.open( + serialPort.currentText, + parseInt(baudrate.currentText)) + } + } + } + + GroupBox { + Layout.fillWidth: true + Layout.fillHeight: true + title: "UVC设置" + GridLayout { + columns: 2 + Text { + text: qsTr("设备名") + } + ComboBox { + id: uvcs + enabled: !App.uvcOpened + implicitWidth: 150 + } + Button { + enabled: !App.uvcOpened + text: "刷新" + onClicked: uvcs.model = App.availableUsbVideoCameras() + } + Button { + text: App.uvcOpened ? "关闭" : "连接" + onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC( + uvcs.currentText) + } + } + } + + Component.onCompleted: { + serialPort.model = App.availableSerialPorts() + uvcs.model = App.availableUsbVideoCameras() + } +} diff --git a/Analyser/qml/OperationItem.qml b/Analyser/qml/OperationItem.qml new file mode 100644 index 0000000..9fe731a --- /dev/null +++ b/Analyser/qml/OperationItem.qml @@ -0,0 +1,110 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Analyser + +ColumnLayout { + ConnectionItem {} + + GroupBox { + title: "命令" + Layout.columnSpan: 2 + enabled: App.connected + GridLayout { + columns: 2 + GroupBox { + title: "注册用户" + GridLayout { + columns: 2 + Text { + text: qsTr("用户姓名") + } + TextField { + id: enrollName + implicitWidth: 100 + } + Text { + text: qsTr("超时时间") + } + TextField { + id: enrollTimeout + implicitWidth: 100 + text: "10" + } + Button { + text: "注册" + onClicked: App.enroll(enrollName.text, + parseInt(enrollTimeout.text)) + } + } + } + GroupBox { + title: "识别用户" + GridLayout { + columns: 2 + Text { + text: qsTr("超时时间(s)") + } + TextField { + id: verifyTimeout + implicitWidth: 80 + text: "10" + } + Text { + text: qsTr("持续识别") + } + Switch { + checked: App.persistenceMode + onToggled: App.persistenceMode = !App.persistenceMode + } + Text { + text: qsTr("识别间隔(s)") + } + TextField { + id: verifyIntetval + implicitWidth: 80 + text: App.persistenceVerifyInterval + } + Item {} + Button { + text: App.isVerifying?"停止": "识别" + onClicked: { + if(App.isVerifying){ + App.module.reset() + }else { + App.persistenceVerifyInterval = parseInt( + verifyIntetval.text) + App.verify(parseInt(verifyTimeout.text)) + } + } + } + } + } + GroupBox { + title: "删除用户" + GridLayout { + columns: 2 + Text { + text: qsTr("用户ID") + } + TextField { + id: deleteUserId + implicitWidth: 100 + } + Button { + text: "删除" + onClicked: App.deleteUser(parseInt(deleteUserId.text)) + } + Button { + text: "删除所有" + onClicked: App.deleteAll() + } + } + } + Button { + text: "复位" + onClicked: App.module.reset() + } + } + } +} diff --git a/Analyser/qml/StatusTip.qml b/Analyser/qml/StatusTip.qml new file mode 100644 index 0000000..1164ef6 --- /dev/null +++ b/Analyser/qml/StatusTip.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls + + +Popup{ + id: control + property alias text: textItem.text + property alias icon: image.source + property alias color: back.color + property string borderColor + x: (parent.width-200)/2 + y: 40 + width: 200 + height: 32 + font.pixelSize: 16 + contentItem: Row{ + leftPadding: 4 + spacing: 9.6 + Image { + id: image + anchors.verticalCenter: parent.verticalCenter + } + Text { + id: textItem + anchors.verticalCenter: parent.verticalCenter + text: control.text + font: control.font + color: "#666666" + } + } + + background: Rectangle { + id:back + anchors.fill: parent + color: "#EBF8ED" + radius: 3.2 + border.width: 1 + border.color: control.borderColor + layer.enabled: true + } + + Timer { + id: timer + repeat: false + onTriggered: control.visible=false + } + + + function show(text,timeout){ + control.text = text + timer.interval = timeout + timer.restart(); + control.visible=true + } + +} diff --git a/Analyser/qml/main.qml b/Analyser/qml/main.qml new file mode 100644 index 0000000..443cbc1 --- /dev/null +++ b/Analyser/qml/main.qml @@ -0,0 +1,132 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Analyser + +Window { + width: 1120 + height: 640 + visible: true + title: qsTr("L015上位机工具") + + OperationItem { + id: operationItem + width: 450 + anchors.top: parent.top + } + + Item { + id: resultGroupBox + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom + width: 220 + Text { + id: resultGroupBoxTitle + text: "识别结果" + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: 30 + } + ScrollView { + id: resultView + anchors.left: parent.left + anchors.right: parent.right + anchors.top: resultGroupBoxTitle.bottom + anchors.bottom: parent.bottom + TextArea { + id: resultBrowser + font.pixelSize: 14 + readOnly: true + wrapMode: TextArea.WordWrap + } + } + Button { + text: "清空" + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: resultBrowser.clear() + } + } + + ColumnLayout { + anchors.left: operationItem.right + anchors.right: resultGroupBox.left + anchors.top: parent.top + anchors.bottom: parent.bottom + TabBar { + id: bar + width: parent.width + TabButton { + implicitWidth: 100 + text: qsTr("视频流") + } + TabButton { + text: qsTr("日志") + } + } + + StackLayout { + width: parent.width + currentIndex: bar.currentIndex + clip: true + Image { + id: image + cache: false + fillMode: Image.PreserveAspectFit + rotation: 90 + source: "image://videoframe/" + } + Item { + ScrollView { + id: view + anchors.fill: parent + TextArea { + id: logBrowser + readOnly: true + wrapMode: TextArea.WordWrap + } + } + Button { + text: "清空" + anchors.right: parent.right + anchors.bottom: parent.bottom + onClicked: logBrowser.clear() + } + } + } + } + + Connections { + target: App + function onNewLog(text) { + logBrowser.append(text) + } + function onNewStatusTip(level, tip) { + if (level === 0) { + statusTip.icon = "../resources/successfull.svg" + statusTip.color="#EBF8ED" + statusTip.show(tip, 2000) + } else if (level === 1) { + statusTip.icon = "../resources/warning.svg" + statusTip.color="#FAFAD2" + statusTip.show(tip, 2000) + } else if (level === 2) { + resultBrowser.append(tip) + } + console.log(level, Application.Info) + } + function onNewVideoFrame() { + image.source = "" + image.source = "image://videoframe/" + } + } + + StatusTip { + id: statusTip + width: 200 + height: 50 + icon: "../resources/successfull.svg" + } +} diff --git a/Analyser/resources/successfull.svg b/Analyser/resources/successfull.svg new file mode 100644 index 0000000..ce8a61b --- /dev/null +++ b/Analyser/resources/successfull.svg @@ -0,0 +1,9 @@ + + + + Combined Shape + Created with Sketch. + + + + \ No newline at end of file diff --git a/Analyser/resources/warning.svg b/Analyser/resources/warning.svg new file mode 100644 index 0000000..135c164 --- /dev/null +++ b/Analyser/resources/warning.svg @@ -0,0 +1,17 @@ + + + + 💚 Icon + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a35a374..2222d4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,10 @@ add_compile_definitions( BOOST_USE_WINAPI_VERSION=BOOST_WINAPI_VERSION_WIN10 ) +set(FFmpeg_ROOT ${Libraries_ROOT}/ffmpeg-7.0.1-full_build-shared) +set(FFmpeg_INCLUDE_DIR ${FFmpeg_ROOT}/include) +set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib) + add_subdirectory(${Projects_ROOT}/Kylin/Universal Universal) add_subdirectory(${Projects_ROOT}/Kylin/Encrypt Encrypt) add_subdirectory(${Projects_ROOT}/Kylin/QtComponets QtComponets) diff --git a/OtaUpdate/CMakeLists.txt b/OtaUpdate/CMakeLists.txt index 02c2ca2..46732e7 100644 --- a/OtaUpdate/CMakeLists.txt +++ b/OtaUpdate/CMakeLists.txt @@ -19,12 +19,8 @@ qt_add_executable(SmartLockerUpdater target_link_libraries(SmartLockerUpdater PRIVATE Peripheral PRIVATE Encrypt - PRIVATE mfplat - PRIVATE mfuuid - PRIVATE Mfreadwrite PRIVATE Qt${QT_VERSION_MAJOR}::Widgets PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort - PRIVATE Mf ) set_target_properties(SmartLockerUpdater PROPERTIES diff --git a/OtaUpdate/CdcUpdater.cpp b/OtaUpdate/CdcUpdater.cpp index 54df82d..b875329 100644 --- a/OtaUpdate/CdcUpdater.cpp +++ b/OtaUpdate/CdcUpdater.cpp @@ -1,5 +1,6 @@ #include "CdcUpdater.h" #include "BoostLog.h" +#include "StringUtility.h" #include #include #include @@ -44,7 +45,7 @@ void CdcUpdater::start(const QString &path) { if (m_timerId < 0) { m_timerId = startTimer(1000); } - m_path = path.toStdString(); + m_path = Amass::StringUtility::UTF8ToGBK(path.toStdString()); LOG(info) << "ota file: " << m_path; emit progressChanged(++m_progress); } diff --git a/OtaUpdate/Widget.cpp b/OtaUpdate/Widget.cpp index 0ddc92a..8574968 100644 --- a/OtaUpdate/Widget.cpp +++ b/OtaUpdate/Widget.cpp @@ -2,6 +2,7 @@ #include "BoostLog.h" #include "CdcUpdater.h" #include "DeviceDiscovery.h" +#include "StringUtility.h" #include #include #include @@ -57,7 +58,7 @@ Widget::~Widget() {} void Widget::start() { m_progressBar->setValue(0); auto filePath = m_fileEditor->text(); - if (!std::filesystem::exists(filePath.toStdString())) { + if (!std::filesystem::exists(Amass::StringUtility::UTF8ToGBK(filePath.toStdString()))) { QMessageBox::warning(this, "升级", "升级文件不存在!"); return; } diff --git a/Peripheral/CMakeLists.txt b/Peripheral/CMakeLists.txt index fb61448..ef6122f 100644 --- a/Peripheral/CMakeLists.txt +++ b/Peripheral/CMakeLists.txt @@ -8,4 +8,8 @@ target_include_directories(Peripheral target_link_libraries(Peripheral PUBLIC Universal + PRIVATE Mfreadwrite + PRIVATE Mf + PRIVATE mfplat + PRIVATE mfuuid ) diff --git a/Peripheral/DeviceDiscovery.cpp b/Peripheral/DeviceDiscovery.cpp index 8e774da..28f1185 100644 --- a/Peripheral/DeviceDiscovery.cpp +++ b/Peripheral/DeviceDiscovery.cpp @@ -116,6 +116,38 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr &device, std::e &llTimeStamp, &pSample); } +std::vector DeviceDiscovery::devices() { + std::vector ret; + IMFAttributes *attributes = nullptr; + boost::scope::scope_exit guard([&attributes] { SafeRelease(&attributes); }); + auto result = MFCreateAttributes(&attributes, 1); + if (FAILED(result)) { + return ret; + } + + result = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (FAILED(result)) { + return ret; + } + + UINT32 count; + IMFActivate **devices = nullptr; + result = MFEnumDeviceSources(attributes, &devices, &count); + if (FAILED(result)) { + return ret; + } + + if (count == 0) { + return ret; + } + + for (int i = 0; i < count; i++) { + auto name = this->deviceName(devices[i]); + ret.push_back(name); + } + return ret; +} + DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr &source) { DeviceDiscovery::Resolutions ret; HRESULT result = S_OK; diff --git a/Peripheral/DeviceDiscovery.h b/Peripheral/DeviceDiscovery.h index 276a98e..ecdb01f 100644 --- a/Peripheral/DeviceDiscovery.h +++ b/Peripheral/DeviceDiscovery.h @@ -24,6 +24,7 @@ public: DeviceDiscovery(); std::shared_ptr find(const std::string &deviceName, std::error_code &error); void enterOtaMode(const std::shared_ptr &device, std::error_code &error); + std::vector devices(); protected: std::string deviceName(IMFActivate *device); diff --git a/Readme.md b/Readme.md index 3206df0..08e1d4d 100644 --- a/Readme.md +++ b/Readme.md @@ -17,6 +17,9 @@ sock_cmd_exec() 内部线程逻辑之间通信 algo_cb()->onFacePalmDetectionPassed()->app_server_alive()->sock_cmd_exec:SOCK_CMD__ALIVE_PALM ST__PlamRegister()->__PalmRegister() -> PalmFeatureExtract() -> spv2_get_feature() + +frame_proc_task() +frame_sync_task() // L015 V200 __DUAL_SNS_FAKE__ 双sensor活体 ``` @@ -55,5 +58,6 @@ HOST_TOOLS := /opt/Xuantie-900-gcc-elf-newlib-x86_64-V2.6.1/bin ```shell ./rebuild-app.sh y L015 V200 R002 # 编译烧录固件 ./rebuild-app-ota.sh y L015 V200 R002 11 # 编译OTA固件,11为OTA版本号 +600X800 ```