1.添加上位机工具。
This commit is contained in:
parent
ce33c00574
commit
cb451b3070
61
Analyser/Analyser.rc
Normal file
61
Analyser/Analyser.rc
Normal 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
20
Analyser/CMakeLists.txt
Normal 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
|
||||||
|
)
|
15
Analyser/CategoryLogSinkBackend.cpp
Normal file
15
Analyser/CategoryLogSinkBackend.cpp
Normal 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)); });
|
||||||
|
}
|
||||||
|
}
|
20
Analyser/CategoryLogSinkBackend.h
Normal file
20
Analyser/CategoryLogSinkBackend.h
Normal 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__
|
204
Analyser/ModuleCommunication.cpp
Normal file
204
Analyser/ModuleCommunication.cpp
Normal 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();
|
||||||
|
}
|
100
Analyser/ModuleCommunication.h
Normal file
100
Analyser/ModuleCommunication.h
Normal 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
221
Analyser/Widget.cpp
Normal 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
54
Analyser/Widget.h
Normal 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
21
Analyser/main.cpp
Normal 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();
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
61
OtaUpdate/OtaUpdate.rc
Normal 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
310
Readme.md
@ -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 会继续增加,直至 65530,ID 变为 0。
|
||||||
|
- face_direction:保留,暂未使用。
|
||||||
|
|
||||||
|
#### 删除单个掌静脉(MID_DELUSER)
|
||||||
|
|
||||||
|
通过传入用户 ID, 删除指定 ID 的单个用户。
|
||||||
|
|
||||||
|
删除单个用户指令携带参数 msg_deluser_data 定义如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
struct msg_deluser_data {
|
||||||
|
uint16_t user_id;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- user_id:需要删除的用户 ID。
|
||||||
|
|
||||||
|
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s):
|
||||||
|
|
||||||
|
- 0x00(MR_SUCCESS):删除成功
|
||||||
|
- 0x05(MR_FAILED4_UNKNOWNREASON):未知错误
|
||||||
|
- 0x08(MR_FAILED4_UNKNOWNUSER):删除的用户ID不存在
|
||||||
|
|
||||||
|
#### 删除所有掌静脉(MID_DELALL)
|
||||||
|
|
||||||
|
删除所有已注册的用户。
|
||||||
|
|
||||||
|
该指令无需携带参数。
|
||||||
|
|
||||||
|
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s):
|
||||||
|
|
||||||
|
- 0x00(MR_SUCCESS):删除成功
|
||||||
|
- 0x05(MR_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):
|
||||||
|
|
||||||
|
- 0x00(MR_SUCCESS):注册成功
|
||||||
|
- 0x05(MR_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
BIN
resources/logo.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/logo.png
Normal file
BIN
resources/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Loading…
Reference in New Issue
Block a user