1.添加上位机工具。

This commit is contained in:
luocai 2024-05-21 21:09:55 +08:00
parent ce33c00574
commit cb451b3070
16 changed files with 1090 additions and 3 deletions

61
Analyser/Analyser.rc Normal file
View File

@ -0,0 +1,61 @@
// Microsoft Visual C++ generated resource script.
//
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
2 TEXTINCLUDE BEGIN "#include "
"winres.h"
"\r\n"
"\0" END
3 TEXTINCLUDE BEGIN "\r\n"
"\0" END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "..\\resources\\logo.ico"
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

20
Analyser/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
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)
add_executable(Analyser Analyser.rc
main.cpp
CategoryLogSinkBackend.h CategoryLogSinkBackend.cpp
Widget.h Widget.cpp
ModuleCommunication.h ModuleCommunication.cpp
)
target_link_libraries(Analyser
PRIVATE Universal
PRIVATE QtComponets
PRIVATE Ws2_32
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
)

View File

@ -0,0 +1,15 @@
#include "CategoryLogSinkBackend.h"
#include "AsyncEvent.h"
#include <QTextBrowser>
#include <iostream>
CategoryLogSinkBackend::CategoryLogSinkBackend(const std::string &category, QTextBrowser *target)
: m_category(category), m_target(target) {
}
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)); });
}
}

View File

@ -0,0 +1,20 @@
#ifndef __CATEGORYLOGSINKBACKEND_H__
#define __CATEGORYLOGSINKBACKEND_H__
#include <boost/log/sinks.hpp>
#include <string>
class QTextBrowser;
class CategoryLogSinkBackend
: public boost::log::sinks::basic_formatted_sink_backend<char, boost::log::sinks::synchronized_feeding> {
public:
CategoryLogSinkBackend(const std::string &category, QTextBrowser *target);
void consume(const boost::log::record_view &record, string_type const &output);
private:
std::string m_category;
QTextBrowser *m_target = nullptr;
};
#endif // __CATEGORYLOGSINKBACKEND_H__

View File

@ -0,0 +1,204 @@
#include "ModuleCommunication.h"
#include "BoostLog.h"
#include <QSerialPort>
#include <WinSock2.h>
#include <sstream>
static inline uint8_t xor_checksum_byte(const uint8_t *data, uint32_t len) {
uint8_t sum = 0;
for (uint32_t i = 0; i < len; ++i) {
sum ^= data[i];
}
return sum;
}
ModuleCommunication::ModuleCommunication(QObject *parent) : QObject{parent} {
}
bool ModuleCommunication::open(const QString &portName) {
bool ret = true;
m_serialPort = std::make_shared<QSerialPort>(portName);
m_serialPort->setBaudRate(QSerialPort::Baud115200);
connect(m_serialPort.get(), &QSerialPort::readyRead, this, &ModuleCommunication::onReadyRead);
ret = m_serialPort->open(QSerialPort::ReadWrite);
LOG_CAT(info, GUI) << "打开串口(" << portName.toStdString() << ")" << (ret ? "成功" : "失败") << "";
LOG_CAT(info, GUI) << Separator;
return ret;
}
void ModuleCommunication::verify(uint8_t timeout) {
VerifyInfo data = {0};
data.timeout = timeout;
auto [frameData, frameSize] = generateFrame(Verify, reinterpret_cast<const uint8_t *>(&data), sizeof(data));
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
LOG_CAT(info, GUI) << "发送识别指令: " << protocolDataFormatString(frameData, frameSize);
LOG_CAT(info, GUI) << Separator;
}
void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) {
EnrollData data = {0};
data.timeout = timeout;
strncpy(reinterpret_cast<char *>(data.username), username.c_str(), sizeof(data.username));
auto [frameData, frameSize] = generateFrame(EnrollSingle, reinterpret_cast<const uint8_t *>(&data), sizeof(data));
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
LOG_CAT(info, GUI) << "发送注册指令: " << protocolDataFormatString(frameData, frameSize);
LOG_CAT(info, GUI) << "用户名: " << username << ", 超时时间: " << static_cast<int>(timeout) << "s";
LOG_CAT(info, GUI) << Separator;
}
void ModuleCommunication::deleteUser(uint16_t userid) {
uint16_t n = htons(userid);
auto [frameData, frameSize] = generateFrame(DeleteUser, reinterpret_cast<const uint8_t *>(&n), sizeof(n));
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
LOG_CAT(info, GUI) << "发送删除用户指令: " << protocolDataFormatString(frameData, frameSize);
LOG_CAT(info, GUI) << "删除用户ID: " << userid;
LOG_CAT(info, GUI) << Separator;
}
void ModuleCommunication::deleteAll() {
auto [frameData, frameSize] = generateFrame(DeleteAll);
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
LOG_CAT(info, GUI) << "发送删除所有指令: " << protocolDataFormatString(frameData, frameSize);
LOG_CAT(info, GUI) << Separator;
}
void ModuleCommunication::onReadyRead() {
auto data = m_serialPort->readAll();
if (data.size() < 6) {
LOG(warning) << "invalid data: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
return;
}
uint8_t messageId = data[2];
switch (messageId) {
case Reply: {
uint8_t replyId = data[5];
auto result = data[6];
switch (replyId) {
case Verify: {
if (result == Success) {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
auto info = reinterpret_cast<VerifyDataReply *>(data.data() + 7);
LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid)
<< ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username));
LOG_CAT(info, GUI) << Separator;
} else if (result == Failed4Timeout) {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "识别超时。";
LOG_CAT(info, GUI) << Separator;
} else if (result == Failed4UnknownReason) {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "未录入用户。";
LOG_CAT(info, GUI) << Separator;
} else {
}
break;
}
case EnrollSingle: {
if (result == Success) {
auto info = reinterpret_cast<EnrollDataReply *>(data.data() + 7);
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "注册成功,用户ID: " << ntohs(info->userid);
LOG_CAT(info, GUI) << Separator;
}
break;
}
case DeleteUser: {
if (result == Success) {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除用户成功。";
LOG_CAT(info, GUI) << Separator;
} else {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除用户失败。";
LOG_CAT(info, GUI) << Separator;
}
break;
}
case DeleteAll: {
if (result == Success) {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除所有用户成功。";
LOG_CAT(info, GUI) << Separator;
}
break;
}
default:
LOG(warning) << "unknown reply command: 0x" << (static_cast<int>(replyId) & 0xff) << ", data: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
break;
}
break;
}
case Note: {
uint8_t noteId = data[5];
switch (noteId) {
case Ready: {
LOG_CAT(info, GUI) << "模组: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
LOG_CAT(info, GUI) << "模组上电初始化成功。";
LOG_CAT(info, GUI) << Separator;
break;
}
case 0x01: { // 模组返回的数据为当前帧的手掌状态
auto info = reinterpret_cast<VerifyNoteInfo *>(data.data() + 7);
LOG(info) << info->state;
break;
}
default:
LOG(warning) << "unknown note command: 0x" << (static_cast<int>(noteId) & 0xff) << ", data: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
break;
}
break;
}
default:
LOG(warning) << "unknown command: 0x" << (static_cast<int>(data[2]) & 0xff) << ", data: "
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
break;
}
}
std::pair<uint8_t *, uint32_t> ModuleCommunication::generateFrame(MessageId command, const uint8_t *data,
uint16_t size) {
static uint8_t sendBuffer[1024] = {0};
memset(sendBuffer, 0, sizeof(sendBuffer));
sendBuffer[0] = 0xef;
sendBuffer[1] = 0xaa;
sendBuffer[2] = command;
sendBuffer[3] = size >> 8;
sendBuffer[4] = size & 0xff;
if (size > 0) {
memcpy(sendBuffer + 5, data, size);
}
sendBuffer[5 + size] = xor_checksum_byte(sendBuffer + 2, 3 + size);
return std::make_pair(sendBuffer, size + 6);
}
std::string ModuleCommunication::protocolDataFormatString(const uint8_t *data, int size) {
std::ostringstream oss;
for (int i = 0; i < size; i++) {
oss << "0x" << std::hex << (static_cast<int>(data[i]) & 0xff) << " ";
}
return oss.str();
}

View File

@ -0,0 +1,100 @@
#ifndef MODULECOMMUNICATION_H
#define MODULECOMMUNICATION_H
#include <QObject>
#include <memory>
class QSerialPort;
class ModuleCommunication : public QObject {
Q_OBJECT
static constexpr uint32_t UsernameSize = 32;
static constexpr const char *Separator = "----------";
public:
enum MessageId : uint8_t {
Reply = 0,
Note = 0x01,
Verify = 0x12,
EnrollSingle = 0x1D,
DeleteUser = 0x20,
DeleteAll = 0x21,
};
enum NoteId : uint8_t {
Ready = 0x00,
};
enum MessageStatus : uint8_t {
Success = 0,
Rejected = 1,
Aborted = 2,
Failed4Camera = 4,
Failed4UnknownReason = 5,
Failed4InvalidParam = 6,
Failed4NoMemory = 7,
Failed4UnknownUser = 8,
Failed4MaxUser = 9,
Failed4FaceEnrolled = 10,
Failed4LivenessCheck = 12,
Failed4Timeout = 13,
Failed4Authorization = 14,
Failed4ReadFile = 19,
Failed4WriteFile = 20,
Failed4NoEncrypt = 21,
};
#pragma pack(1)
struct VerifyInfo {
uint8_t powerDownRightAway; // power down right away after verifying
uint8_t timeout; // timeout, unit second, default 10s
};
struct VerifyNoteInfo {
int16_t state; // corresponding to PALM_STATE_*
// position
int16_t left; // in pixel
int16_t top;
int16_t right;
int16_t bottom;
// pose
int16_t yaw; // up and down in vertical orientation
int16_t pitch; // right or left turned in horizontal orientation
int16_t roll; // slope
};
struct EnrollData {
uint8_t admin = 0;
uint8_t username[32];
uint8_t palmDirection;
uint8_t timeout;
};
struct EnrollDataReply {
uint16_t userid;
uint8_t face_direction; // depleted, user ignore this field
};
struct VerifyDataReply {
uint16_t userid;
uint8_t username[UsernameSize]; // 32Bytes uint8_t admin;
uint8_t unlockStatus;
};
#pragma pack()
explicit ModuleCommunication(QObject *parent = nullptr);
bool open(const QString &portName);
void verify(uint8_t timeout);
void enroll(const std::string &username, uint8_t timeout);
void deleteUser(uint16_t userid);
void deleteAll();
protected:
void onReadyRead();
std::pair<uint8_t *, uint32_t> 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<QSerialPort> m_serialPort;
};
#endif // MODULECOMMUNICATION_H

221
Analyser/Widget.cpp Normal file
View File

@ -0,0 +1,221 @@
#include "Widget.h"
#include "BoostLog.h"
#include "CategoryLogSinkBackend.h"
#include "ModuleCommunication.h"
#include <QComboBox>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSerialPortInfo>
#include <QTabWidget>
#include <QTextBrowser>
#include <QTimer>
#include <QVBoxLayout>
Widget::Widget(QWidget *parent) : QWidget{parent} {
auto serialGroupBox = new QGroupBox("串口设置");
auto serialLayout = new QGridLayout();
auto label = new QLabel("端口");
serialLayout->addWidget(label, 0, 0);
m_serialComboBox = new QComboBox();
serialLayout->addWidget(m_serialComboBox, 0, 1);
label = new QLabel("波特率");
serialLayout->addWidget(label, 1, 0);
label = new QLabel("115200");
serialLayout->addWidget(label, 1, 1);
auto refreshButton = new QPushButton("刷新");
connect(refreshButton, &QPushButton::clicked, this, &Widget::onSerialRefreshButtonClicked);
m_serialConnectButton = new QPushButton("连接");
connect(m_serialConnectButton, &QPushButton::clicked, this, &Widget::onSerialConnectButtonClicked);
serialLayout->addWidget(refreshButton, 2, 0);
serialLayout->addWidget(m_serialConnectButton, 2, 1);
serialGroupBox->setLayout(serialLayout);
auto uvcGroupBox = new QGroupBox("UVC设置");
auto uvcLayout = new QGridLayout();
label = new QLabel("设备名");
uvcLayout->addWidget(label, 0, 0);
label = new QLabel("未发现设备");
uvcLayout->addWidget(label, 0, 1);
auto uvcRefreshButton = new QPushButton("刷新");
auto uvcConnectButton = new QPushButton("连接");
uvcLayout->addWidget(uvcRefreshButton, 1, 0);
uvcLayout->addWidget(uvcConnectButton, 1, 1);
uvcGroupBox->setLayout(uvcLayout);
auto connectLayout = new QHBoxLayout();
connectLayout->addWidget(serialGroupBox);
connectLayout->addWidget(uvcGroupBox);
m_logBrowser = new QTextBrowser();
m_logBrowser->setReadOnly(true);
auto tabWidget = new QTabWidget();
tabWidget->addTab(m_logBrowser, "日志");
tabWidget->addTab(new QWidget(), "视频流");
tabWidget->addTab(new QWidget(), "已注册列表");
m_commandGroupBox = initializeCommandGroupBox();
auto operatorLayout = new QVBoxLayout();
operatorLayout->addLayout(connectLayout);
operatorLayout->addWidget(m_commandGroupBox);
operatorLayout->addStretch(2);
auto layout = new QHBoxLayout(this);
layout->addLayout(operatorLayout, 1);
layout->addWidget(tabWidget, 3);
QTimer::singleShot(0, this, [this]() {
onSerialRefreshButtonClicked();
m_commandGroupBox->setEnabled(false);
});
}
void Widget::initializeLogger() {
auto backend = boost::make_shared<CategoryLogSinkBackend>("GUI", m_logBrowser);
using SynchronousCategorySink = boost::log::sinks::synchronous_sink<CategoryLogSinkBackend>;
auto sink = boost::make_shared<SynchronousCategorySink>(backend);
// sink->set_formatter(&zlogFormatter);
boost::log::core::get()->add_sink(sink);
}
QGroupBox *Widget::initializeCommandGroupBox() {
auto ret = new QGroupBox("命令");
auto layout = new QGridLayout();
layout->addWidget(initializeEnrollGroupBox(), 0, 0);
layout->addWidget(initializeVerifyGroupBox(), 0, 1);
layout->addWidget(initializeDeleteGroupBox(), 1, 0);
layout->addWidget(initializePalmFeatureGroupBox(), 1, 1);
ret->setLayout(layout);
return ret;
}
QGroupBox *Widget::initializeEnrollGroupBox() {
auto ret = new QGroupBox("注册用户");
auto layout = new QFormLayout();
m_enrollNameEdit = new QLineEdit();
layout->addRow("用户姓名:", m_enrollNameEdit);
m_enrollTimeoutEdit = new QLineEdit("10");
layout->addRow("超时时间:", m_enrollTimeoutEdit);
m_enrollButton = new QPushButton("注册");
connect(m_enrollButton, &QPushButton::clicked, this, &Widget::onEnrollButtonClicked);
layout->addRow("", m_enrollButton);
ret->setLayout(layout);
return ret;
}
QGroupBox *Widget::initializeVerifyGroupBox() {
auto ret = new QGroupBox("识别用户");
auto layout = new QFormLayout();
m_verifyTimeoutEdit = new QLineEdit("10");
layout->addRow("超时时间:", m_verifyTimeoutEdit);
m_verifyButton = new QPushButton("识别");
connect(m_verifyButton, &QPushButton::clicked, this, &Widget::onVerifyButtonClicked);
layout->addRow("", m_verifyButton);
ret->setLayout(layout);
return ret;
}
QGroupBox *Widget::initializeDeleteGroupBox() {
auto ret = new QGroupBox("删除用户");
auto layout = new QFormLayout();
m_deleteIdEdit = new QLineEdit("");
layout->addRow("用户ID:", m_deleteIdEdit);
m_deleteButton = new QPushButton("删除");
connect(m_deleteButton, &QPushButton::clicked, this, &Widget::onDeleteButtonClicked);
layout->addRow("", m_deleteButton);
m_deleteAllButton = new QPushButton("删除所有");
connect(m_deleteAllButton, &QPushButton::clicked, this, &Widget::onDeleteAllButtonClicked);
layout->addRow("", m_deleteAllButton);
ret->setLayout(layout);
return ret;
}
QGroupBox *Widget::initializePalmFeatureGroupBox() {
auto ret = new QGroupBox("特征值下发/上报");
auto layout = new QFormLayout();
auto edit = new QLineEdit("");
layout->addRow("用户ID:", edit);
auto button = new QPushButton("特征值下发");
layout->addRow("", button);
auto button1 = new QPushButton("特征值上报");
layout->addRow("", button1);
ret->setLayout(layout);
return ret;
}
void Widget::onSerialConnectButtonClicked() {
auto button = dynamic_cast<QPushButton *>(sender());
if (button == nullptr) return;
auto text = button->text();
if (text == "连接") {
auto portName = m_serialComboBox->currentText();
m_communication = std::make_shared<ModuleCommunication>();
bool status = m_communication->open(portName);
if (status) {
m_commandGroupBox->setEnabled(true);
button->setText("关闭");
}
} else if (text == "关闭") {
m_communication.reset();
m_commandGroupBox->setEnabled(false);
button->setText("连接");
}
}
void Widget::onSerialRefreshButtonClicked() {
m_serialComboBox->clear();
auto ports = QSerialPortInfo::availablePorts();
for (auto &port : ports) {
m_serialComboBox->addItem(port.portName());
}
}
void Widget::onEnrollButtonClicked() {
auto name = m_enrollNameEdit->text();
auto timeout = m_enrollTimeoutEdit->text().toInt();
m_communication->enroll(name.toStdString(), timeout);
}
void Widget::onVerifyButtonClicked() {
if (!m_communication) return;
auto timeout = m_verifyTimeoutEdit->text().toInt();
m_communication->verify(timeout);
}
void Widget::onDeleteAllButtonClicked() {
if (!m_communication) return;
m_communication->deleteAll();
}
void Widget::onDeleteButtonClicked() {
if (!m_communication) return;
auto id = m_deleteIdEdit->text().toInt();
m_communication->deleteUser(id);
}

54
Analyser/Widget.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef WIDGET_H
#define WIDGET_H
#include "qgroupbox.h"
#include <QWidget>
class QPushButton;
class QTextBrowser;
class QComboBox;
class QLineEdit;
class ModuleCommunication;
class Widget : public QWidget {
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
void initializeLogger();
protected:
QGroupBox *initializeCommandGroupBox();
void onSerialConnectButtonClicked();
void onSerialRefreshButtonClicked();
void onEnrollButtonClicked();
void onVerifyButtonClicked();
void onDeleteAllButtonClicked();
void onDeleteButtonClicked();
QGroupBox *initializeEnrollGroupBox();
QGroupBox *initializeVerifyGroupBox();
QGroupBox *initializeDeleteGroupBox();
QGroupBox *initializePalmFeatureGroupBox();
private:
QComboBox *m_serialComboBox = nullptr;
QPushButton *m_serialConnectButton = nullptr;
QTextBrowser *m_logBrowser = nullptr;
QGroupBox *m_commandGroupBox = nullptr;
QLineEdit *m_enrollNameEdit = nullptr;
QLineEdit *m_enrollTimeoutEdit = nullptr;
QPushButton *m_enrollButton = nullptr;
QLineEdit *m_verifyTimeoutEdit = nullptr;
QPushButton *m_verifyButton = nullptr;
QLineEdit *m_deleteIdEdit = nullptr;
QPushButton *m_deleteButton = nullptr;
QPushButton *m_deleteAllButton = nullptr;
std::shared_ptr<ModuleCommunication> m_communication;
};
#endif // WIDGET_H

21
Analyser/main.cpp Normal file
View File

@ -0,0 +1,21 @@
#include "BoostLog.h"
#include "Widget.h"
#include <QApplication>
#include <QFont>
int main(int argc, char *argv[]) {
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();
return a.exec();
}

View File

@ -17,5 +17,7 @@ add_compile_definitions(
add_subdirectory(${Projects_ROOT}/Kylin/Universal Universal) add_subdirectory(${Projects_ROOT}/Kylin/Universal Universal)
add_subdirectory(${Projects_ROOT}/Kylin/Encrypt Encrypt) add_subdirectory(${Projects_ROOT}/Kylin/Encrypt Encrypt)
add_subdirectory(${Projects_ROOT}/Kylin/QtComponets QtComponets)
add_subdirectory(Analyser)
add_subdirectory(OtaUpdate) add_subdirectory(OtaUpdate)

View File

@ -4,7 +4,7 @@ set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets SerialPort) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets SerialPort)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets SerialPort) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets SerialPort)
set(PROJECT_SOURCES set(PROJECT_SOURCES OtaUpdate.rc
main.cpp main.cpp
CdcUpdater.h CdcUpdater.cpp CdcUpdater.h CdcUpdater.cpp
DeviceDiscovery.h DeviceDiscovery.cpp DeviceDiscovery.h DeviceDiscovery.cpp

View File

@ -47,8 +47,6 @@ protected:
bool write(Command command, const uint8_t *data = nullptr, uint32_t size = 0); bool write(Command command, const uint8_t *data = nullptr, uint32_t size = 0);
void timerEvent(QTimerEvent *event) final; void timerEvent(QTimerEvent *event) final;
void transferBin(); void transferBin();
protected slots:
void onReadyRead(); void onReadyRead();
private: private:

61
OtaUpdate/OtaUpdate.rc Normal file
View File

@ -0,0 +1,61 @@
// Microsoft Visual C++ generated resource script.
//
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// 中文(简体,中国) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
2 TEXTINCLUDE BEGIN "#include "
"winres.h"
"\r\n"
"\0" END
3 TEXTINCLUDE BEGIN "\r\n"
"\0" END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "..\\resources\\logo.ico"
#endif // 中文(简体,中国) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

310
Readme.md
View File

@ -14,3 +14,313 @@
## 模组串口通讯协议
### 串口配置
- 波特率115200
- 数据位8
- 停止位1
- 奇偶检验:无
- 流控制:无
### 消息格式
主控和模块通讯的基本格式如下表所示,字节序为 **大端字节序Big Endian**
| SyncWord | MsgID | DataSize | Data | ParityCheck |
| -------- | ------ | -------- | ------- | ----------- |
| 2 bytes | 1 byte | 2 bytes | N bytes | 1 byte |
下表是对上述各个字段的详细说明:
| 字段 | 长度 | 说明 |
| ----------- | ------- | ------------------------------------------------------------ |
| SyncWord | 2 bytes | 固定的消息开头同步字0xEF 0xAA |
| MsgID | 1 byte | 消息ID例如 MID_VERIFY |
| DataSize | 2 bytes | Data数据的长度0 ≤ size ≤ 65535 |
| Data | N bytes | 消息MsgID对应的数据内容长度 N 为 DataSize 。<br/>0表示此消息无参数 |
| ParityCheck | 1 byte | 协议的奇偶检验码。<br/>去除SyncWord对 MsgID、DataSize、Data 的内容字节做XOR运算 |
### 消息列表
| MsgID | Code | 说明 |
| ----------------------- | ---- | ------------------------------------------------------------ |
| MID_REPLY | 0x00 | 模组对主控发送出的命令的应答,对于主控下发的每条命令,模组最终都会使用 MID_REPLY 进行结果应答上报 |
| MID_NOTE | 0x01 | 摸组主动上报给主控的信息,根据 NID 判断消息类型和对应的 Data 结构(详细内容见下文) |
| MID_VERIFY | 0x12 | 掌静脉识别比对 |
| MID_ENROLL_SINGLE | 0x1D | 掌静脉录入(单帧) |
| MID_DELUSER | 0x20 | 删除一个已注册的掌静脉 |
| MID_DELALL | 0x21 | 删除所有已注册的掌静脉 |
| MID_ENROLL_PALM_FEATUTE | 0xF9 | 主控下发掌静脉特征值给模组进行注册 |
| MID_GET_PALM_FEATUTE | 0xFA | 主控请求获取指定用户掌静脉特征值 |
#### 设备初始化完成
模组上电初始化完成后,会通过串口向主控发送 NID_READY 报文: 0xEF 0xAA 0x01 0x00 0x01 0x00 0x00。详细解释见 `模组状态上报MID_NOTE`)。
主机在接收到握手信号后,可以和模组进行指令交互。
#### 掌静脉识别MID_VERIFY
主控下发该指令给模组,模组开始识别掌静脉进行比对,指令下发携带的参数 msg_verify_data 定义如下:
```c++
struct msg_verify_data {
uint8_t pd_rightaway;
uint8_t timeout;
};
```
参数说明:
- pd_rightaway表示是否在识别完成后立刻关机(预留参数),默认设 00。
- timeout识别超时时间默认为10s用户可以自行设置最大不超过255s。**主控等待模组录入应答的超时时间应大于此参数设置值。**
识别结果 Result 确认码的返回值见 `命令结果上报MID_REPLY`
模组掌静脉识别成功后,通过消息 MID_REPLY 返回的数据 ResultData 定义如下:
```c++
struct VerifyDataReply {
uint16_t user_id;
uint8_t username[32];
uint8_t admin;
uint8_t unlockStatus;
};
```
参数说明:
- user_id识别成功的用户 ID
- username识别成功的用户名字
- admin识别成功的用户是否为管理员用户
- unlockStatus保留参数未使用
#### 掌静脉注册MID_ENROLL_SINGLE
掌静脉注册指令下发携带的参数 `msg_enroll_data` 定义如下:
```c++
struct msg_enroll_data {
uint8_t admin;
uint8_t username[32];
uint8_t palm_direction;
uint8_t timeout;
};
```
参数说明:
- admin设置该录入的掌静脉人员为管理员。
- username录入用户的用户名。
- palm_direction保留暂未使用。可设置为 0x00。
- timeout录入过程的超时时间s默认为10s用户可以自行设置最大不超过255s。**主控等待模组录入应答的超时时间应大于此参数设置值。**
录入结果 Result 确认码的返回值见 `命令结果上报MID_REPLY`
模组掌静脉录入成功后,通过消息 MID_REPLY 返回的数据 ResultData 定义如下:
```c++
struct msg_reply_enroll_data {
uint16_t user_id;
uint8_t face_direction; // depleted, user ignore this field
};
```
参数说明:
- user_id用户 ID 每次录入都会增加 1即使删除某个 ID 后,再次录入 ID 会继续增加,直至 65530ID 变为 0。
- face_direction保留暂未使用。
#### 删除单个掌静脉MID_DELUSER
通过传入用户 ID, 删除指定 ID 的单个用户。
删除单个用户指令携带参数 msg_deluser_data 定义如下:
```
struct msg_deluser_data {
uint16_t user_id;
};
```
参数说明:
- user_id需要删除的用户 ID。
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s
- 0x00MR_SUCCESS删除成功
- 0x05MR_FAILED4_UNKNOWNREASON未知错误
- 0x08MR_FAILED4_UNKNOWNUSER删除的用户ID不存在
#### 删除所有掌静脉MID_DELALL
删除所有已注册的用户。
该指令无需携带参数。
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s
- 0x00MR_SUCCESS删除成功
- 0x05MR_FAILED4_UNKNOWNREASON未知错误
#### 掌静脉特征值注册MID_ENROLL_PALM_FEATUTE
该命令用于直接将掌静脉特征值下发给模组进行注册。
下发掌静脉特征值注册携带的参数 msg_palm_feature_enroll_data 如下:
```c++
struct msg_palm_feature_enroll_data {
uint16_t user_id;
uint8_t username[32];
uint8_t admin;
uint8_t feature_data_md5[16];
uint16_t feature_total_size;
uint8_t feature_data[feature_total_size];
};
```
参数说明:
- user_id用户ID下发掌静脉特征值时由用户设置。
- username录入用户的用户名。
- admin设置该录入的掌静脉人员为管理员。
- feature_data_md5特征值 feature_data 的 md5 校验值
- feature_total_size特征值 feature_data 的长度
- feature_data掌静脉特征值数据
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s
- 0x00MR_SUCCESS注册成功
- 0x05MR_FAILED4_UNKNOWNREASON未知错误
#### 获取掌静脉特征值MID_GET_PALM_FEATUTE
该命令用于获取对应已注册用户的掌静脉特征值,携带参数 msg_get_palm_feature_data 如下:
```c++
struct msg_get_palm_feature_data {
uint16_t user_id;
}
```
录入结果 Result 确认码的返回值见 `命令结果上报MID_REPLY`
获取掌静脉特征值任务成功后,通过消息 MID_REPLY 返回的数据 ResultData 定义如下:
```c++
struct msg_palm_feature_enroll_data {
uint16_t user_id;
uint8_t username[32];
uint8_t admin;
uint8_t feature_data_md5[16];
uint16_t feature_total_size;
uint8_t feature_data[feature_total_size];
};
```
参数定义见 `掌静脉特征值注册MID_ENROLL_PALM_FEATUTE` 携带参数的参数定义说明。
#### 命令结果上报MID_REPLY
模组向主控发送的 MID_NOTE 消息的完整协议如下所示:
<table align="center">
<tr align="center">
<th>SyncWord</th>
<th>MsgID</th>
<th>DataSize</th>
<th colspan="3">Data</th>
<th>ParityCheck</th>
</tr>
<tr align="center">
<td>2 bytes</td>
<td>1 byte</td>
<td>2 bytes</td>
<td colspan="3">N bytes</td>
<td>1 byte</td>
</tr>
<tr align="center">
<td>0xEF 0xAA</td>
<td>MID_REPLY(0x00)</td>
<td> </td>
<td>RID(1 byte)</td>
<td>Result(1 byte)</td>
<td>ResultData( N-2 bytes)</td>
<td> </td>
</tr>
</table>
RID表示模组当前正在处理的任务例如当 RID 为 MID_ENROLL_SINGLE 是,表示该消息是模组处理完单帧录入任务后回复的消息。
消息对应的 ResultData 会在主控下发命令(例如 MID_ENROLL_SINGLE )中进行介绍。
Result 表示该命令的最终执行结果,详细如下表所示。
| Result | Code | 说明 |
| ------------------------ | ---- | ------------------- |
| MR_SUCCESS | 0 | 指令执行成功 |
| MR_REJECTED | 1 | 模组拒绝该命令 |
| MR_ABORTED | 2 | 录入/解锁算法已终止 |
| MR_FAILED4_CAMERA | 4 | 相机打开失败 |
| MR_FAILED4_UNKNOWNREASON | 5 | 未知错误 |
| MR_FAILED4_INVALIDPARAM | 6 | 无效的参数 |
| MR_FAILED4_NOMEMORY | 7 | 内存不足 |
| MR_FAILED4_UNKNOWNUSER | 8 | 未录入的用户 |
| MR_FAILED4_MAXUSER | 9 | 录入超过最大数量 |
| MR_FAILED4_PALMENROLLED | 10 | 掌静脉已录入 |
| MR_FAILED4_LIVENESSCHECK | 12 | 活体检测失败 |
| MR_FAILED4_TIMEOUT | 13 | 录入或解锁超时 |
| MR_FAILED4_AUTHORIZATION | 14 | 加密芯片授权失败 |
| MR_FAILED4_READ_FILE | 19 | 读文件失败 |
| MR_FAILED4_WRITE_FILE | 20 | 写文件失败 |
| MR_FAILED4_NO_ENCRYPT | 21 | 未采用加密通讯 |
#### 模组状态上报MID_NOTE
MID_NOTE 消息主要作用是主动向主控上报一些信息,报文格式如下:
<table align="center">
<tr align="center">
<th>SyncWord</th>
<th>MsgID</th>
<th>DataSize</th>
<th colspan="3">Data</th>
<th>ParityCheck</th>
</tr>
<tr align="center">
<td>2 bytes</td>
<td>1 byte</td>
<td>2 bytes</td>
<td colspan="3">N bytes</td>
<td>1 byte</td>
</tr>
<tr align="center">
<td>0xEF 0xAA</td>
<td>MID_NOTE(0x01)</td>
<td> </td>
<td>NID(1 byte)</td>
<td>NoteData( N-1 bytes)</td>
<td> </td>
</tr>
</table>
目前NID主要内容如下
| NID*表示该消息携带 NoteData 数据) | Code | 说明 |
| ------------------------------------ | ---- | -------------------- |
| NID_READY | 0x00 | 模组已上电初始化成功 |
### 示例报文

BIN
resources/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
resources/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB