1.实现掌静脉特征获取。

This commit is contained in:
luocai 2024-05-22 18:03:11 +08:00
parent cb451b3070
commit c40e512df6
5 changed files with 512 additions and 57 deletions

View File

@ -66,13 +66,16 @@ void ModuleCommunication::deleteAll() {
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} }
void ModuleCommunication::onReadyRead() { void ModuleCommunication::requestPalmFeature(uint16_t userid) {
auto data = m_serialPort->readAll(); uint16_t n = htons(userid);
if (data.size() < 6) { auto [frameData, frameSize] = generateFrame(RequestPalmFeature, reinterpret_cast<const uint8_t *>(&n), sizeof(n));
LOG(warning) << "invalid data: " m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size()); LOG_CAT(info, GUI) << "发送获取掌静脉特征值指令: " << protocolDataFormatString(frameData, frameSize);
return; LOG_CAT(info, GUI) << "获取特征值用户ID: " << userid;
} LOG_CAT(info, GUI) << Separator;
}
void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) {
uint8_t messageId = data[2]; uint8_t messageId = data[2];
switch (messageId) { switch (messageId) {
case Reply: { case Reply: {
@ -81,23 +84,17 @@ void ModuleCommunication::onReadyRead() {
switch (replyId) { switch (replyId) {
case Verify: { case Verify: {
if (result == Success) { if (result == Success) {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), auto info = reinterpret_cast<const VerifyDataReply *>(data + 7);
data.size());
auto info = reinterpret_cast<VerifyDataReply *>(data.data() + 7);
LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid) LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid)
<< ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username)); << ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username));
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} else if (result == Failed4Timeout) { } else if (result == Failed4Timeout) {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "识别超时。"; LOG_CAT(info, GUI) << "识别超时。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} else if (result == Failed4UnknownReason) { } else if (result == Failed4UnknownReason) {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "未录入用户。"; LOG_CAT(info, GUI) << "未录入用户。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} else { } else {
@ -107,10 +104,8 @@ void ModuleCommunication::onReadyRead() {
} }
case EnrollSingle: { case EnrollSingle: {
if (result == Success) { if (result == Success) {
auto info = reinterpret_cast<EnrollDataReply *>(data.data() + 7); auto info = reinterpret_cast<const EnrollDataReply *>(data + 7);
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "注册成功,用户ID: " << ntohs(info->userid); LOG_CAT(info, GUI) << "注册成功,用户ID: " << ntohs(info->userid);
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} }
@ -118,15 +113,11 @@ void ModuleCommunication::onReadyRead() {
} }
case DeleteUser: { case DeleteUser: {
if (result == Success) { if (result == Success) {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除用户成功。"; LOG_CAT(info, GUI) << "删除用户成功。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} else { } else {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除用户失败。"; LOG_CAT(info, GUI) << "删除用户失败。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} }
@ -134,17 +125,26 @@ void ModuleCommunication::onReadyRead() {
} }
case DeleteAll: { case DeleteAll: {
if (result == Success) { if (result == Success) {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()),
data.size());
LOG_CAT(info, GUI) << "删除所有用户成功。"; LOG_CAT(info, GUI) << "删除所有用户成功。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
} }
break; break;
} }
case RequestPalmFeature: {
LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
if (result == Success) {
auto info = reinterpret_cast<const PalmFeatureHeader *>(data + 7);
LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid)
<< ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username))
<< ", 特征值长度: " << ntohs(info->featureTotalSize);
}
LOG_CAT(info, GUI) << Separator;
break;
}
default: default:
LOG(warning) << "unknown reply command: 0x" << (static_cast<int>(replyId) & 0xff) << ", data: " LOG(warning) << "unknown reply command: 0x" << (static_cast<int>(replyId) & 0xff)
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size()); << ", data: " << protocolDataFormatString(data, size);
break; break;
} }
break; break;
@ -153,32 +153,68 @@ void ModuleCommunication::onReadyRead() {
uint8_t noteId = data[5]; uint8_t noteId = data[5];
switch (noteId) { switch (noteId) {
case Ready: { case Ready: {
LOG_CAT(info, GUI) << "模组: " LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size());
LOG_CAT(info, GUI) << "模组上电初始化成功。"; LOG_CAT(info, GUI) << "模组上电初始化成功。";
LOG_CAT(info, GUI) << Separator; LOG_CAT(info, GUI) << Separator;
break; break;
} }
case 0x01: { // 模组返回的数据为当前帧的手掌状态 case 0x01: { // 模组返回的数据为当前帧的手掌状态
auto info = reinterpret_cast<VerifyNoteInfo *>(data.data() + 7); auto info = reinterpret_cast<const VerifyNoteInfo *>(data + 7);
LOG(info) << info->state; LOG(info) << info->state;
break; break;
} }
default: default:
LOG(warning) << "unknown note command: 0x" << (static_cast<int>(noteId) & 0xff) << ", data: " LOG(warning) << "unknown note command: 0x" << (static_cast<int>(noteId) & 0xff)
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size()); << ", data: " << protocolDataFormatString(data, size);
break; break;
} }
break; break;
} }
default: default:
LOG(warning) << "unknown command: 0x" << (static_cast<int>(data[2]) & 0xff) << ", data: " LOG(warning) << "unknown command: 0x" << (static_cast<int>(data[2]) & 0xff)
<< protocolDataFormatString(reinterpret_cast<const uint8_t *>(data.data()), data.size()); << ", data: " << protocolDataFormatString(data, size);
break; break;
} }
} }
void ModuleCommunication::onReadyRead() {
m_receivedBuffer.append(m_serialPort->readAll());
while (m_receivedBuffer.size() >= 2) {
int beginIndex = -1;
for (int i = 0; i < m_receivedBuffer.size() - 1; i++) {
if (static_cast<uint8_t>(m_receivedBuffer[i]) == 0xEF &&
static_cast<uint8_t>(m_receivedBuffer[i + 1]) == 0xAA) {
beginIndex = i;
break;
}
}
if (beginIndex < 0) {
m_receivedBuffer.clear();
break;
} else if (beginIndex != 0) {
m_receivedBuffer.remove(0, beginIndex);
beginIndex = 0;
}
if (m_receivedBuffer.size() < 5) break;
uint16_t packageSize = *reinterpret_cast<uint16_t *>(m_receivedBuffer.data() + 3);
packageSize = ntohs(packageSize);
uint16_t totalSize = 0;
if (packageSize == 0) {
totalSize = 6;
} else {
totalSize = packageSize + 6;
}
if (m_receivedBuffer.size() >= totalSize) {
processPackage(reinterpret_cast<const uint8_t *>(m_receivedBuffer.data()), totalSize);
m_receivedBuffer.remove(0, totalSize);
} else {
break;
}
}
}
std::pair<uint8_t *, uint32_t> ModuleCommunication::generateFrame(MessageId command, const uint8_t *data, std::pair<uint8_t *, uint32_t> ModuleCommunication::generateFrame(MessageId command, const uint8_t *data,
uint16_t size) { uint16_t size) {
static uint8_t sendBuffer[1024] = {0}; static uint8_t sendBuffer[1024] = {0};
@ -198,7 +234,7 @@ std::pair<uint8_t *, uint32_t> ModuleCommunication::generateFrame(MessageId comm
std::string ModuleCommunication::protocolDataFormatString(const uint8_t *data, int size) { std::string ModuleCommunication::protocolDataFormatString(const uint8_t *data, int size) {
std::ostringstream oss; std::ostringstream oss;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
oss << "0x" << std::hex << (static_cast<int>(data[i]) & 0xff) << " "; oss << "0x" << std::setfill('0') << std::setw(2) << std::hex << (static_cast<int>(data[i]) & 0xff) << " ";
} }
return oss.str(); return oss.str();
} }

View File

@ -19,6 +19,8 @@ public:
EnrollSingle = 0x1D, EnrollSingle = 0x1D,
DeleteUser = 0x20, DeleteUser = 0x20,
DeleteAll = 0x21, DeleteAll = 0x21,
RegisterPalmFeature = 0xF9,
RequestPalmFeature = 0xFA,
}; };
enum NoteId : uint8_t { enum NoteId : uint8_t {
@ -79,6 +81,14 @@ public:
uint8_t unlockStatus; uint8_t unlockStatus;
}; };
struct PalmFeatureHeader {
uint16_t userid; // 用户ID
uint8_t username[32]; // 用户姓名
uint8_t admin; // 是否管理员YES1 NO:0
uint8_t featureDataMd5[16]; // 整体特征数据的MD5值
uint16_t featureTotalSize; // 特征数据总长度
};
#pragma pack() #pragma pack()
explicit ModuleCommunication(QObject *parent = nullptr); explicit ModuleCommunication(QObject *parent = nullptr);
bool open(const QString &portName); bool open(const QString &portName);
@ -88,13 +98,17 @@ public:
void deleteUser(uint16_t userid); void deleteUser(uint16_t userid);
void deleteAll(); void deleteAll();
void requestPalmFeature(uint16_t userid);
protected: protected:
void processPackage(const uint8_t *data, uint16_t size);
void onReadyRead(); void onReadyRead();
std::pair<uint8_t *, uint32_t> generateFrame(MessageId command, const uint8_t *data = nullptr, uint16_t size = 0); 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); std::string protocolDataFormatString(const uint8_t *data, int size);
private: private:
std::shared_ptr<QSerialPort> m_serialPort; std::shared_ptr<QSerialPort> m_serialPort;
QByteArray m_receivedBuffer;
}; };
#endif // MODULECOMMUNICATION_H #endif // MODULECOMMUNICATION_H

View File

@ -39,26 +39,17 @@ Widget::Widget(QWidget *parent) : QWidget{parent} {
serialLayout->addWidget(m_serialConnectButton, 2, 1); serialLayout->addWidget(m_serialConnectButton, 2, 1);
serialGroupBox->setLayout(serialLayout); 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(); auto connectLayout = new QHBoxLayout();
connectLayout->addWidget(serialGroupBox); connectLayout->addWidget(serialGroupBox);
connectLayout->addWidget(uvcGroupBox); connectLayout->addWidget(initializeUvcGroupBox());
m_logBrowser = new QTextBrowser(); m_logBrowser = new QTextBrowser();
m_logBrowser->setReadOnly(true); m_logBrowser->setReadOnly(true);
auto logLayout = new QVBoxLayout();
m_logBrowser->setLayout(logLayout);
auto btn = new QPushButton("清空");
connect(btn, &QPushButton::clicked, this, &Widget::onClearLogButtonClicked);
logLayout->addWidget(btn, 0, Qt::AlignBottom | Qt::AlignRight);
auto tabWidget = new QTabWidget(); auto tabWidget = new QTabWidget();
tabWidget->addTab(m_logBrowser, "日志"); tabWidget->addTab(m_logBrowser, "日志");
@ -103,6 +94,10 @@ QGroupBox *Widget::initializeCommandGroupBox() {
return ret; return ret;
} }
void Widget::onClearLogButtonClicked() {
m_logBrowser->clear();
}
QGroupBox *Widget::initializeEnrollGroupBox() { QGroupBox *Widget::initializeEnrollGroupBox() {
auto ret = new QGroupBox("注册用户"); auto ret = new QGroupBox("注册用户");
auto layout = new QFormLayout(); auto layout = new QFormLayout();
@ -156,20 +151,38 @@ QGroupBox *Widget::initializePalmFeatureGroupBox() {
auto layout = new QFormLayout(); auto layout = new QFormLayout();
auto edit = new QLineEdit(""); m_palmFeatureEdit = new QLineEdit("");
layout->addRow("用户ID:", edit); layout->addRow("用户ID:", m_palmFeatureEdit);
auto button = new QPushButton("特征值下发"); auto button = new QPushButton("特征值下发");
layout->addRow("", button); layout->addRow("", button);
auto button1 = new QPushButton("特征值上报"); auto button1 = new QPushButton("特征值上报");
layout->addRow("", button1); layout->addRow("", button1);
connect(button1, &QPushButton::clicked, this, &Widget::onRequestPalmFeatureButtonClicked);
ret->setLayout(layout); ret->setLayout(layout);
return ret; return ret;
} }
QGroupBox *Widget::initializeUvcGroupBox() {
auto ret = new QGroupBox("UVC设置");
auto uvcLayout = new QGridLayout();
auto label = new QLabel("设备名");
uvcLayout->addWidget(label, 0, 0);
auto comboBox = new QComboBox();
uvcLayout->addWidget(comboBox, 0, 1);
auto uvcRefreshButton = new QPushButton("刷新");
auto uvcConnectButton = new QPushButton("连接");
uvcLayout->addWidget(uvcRefreshButton, 1, 0);
uvcLayout->addWidget(uvcConnectButton, 1, 1);
ret->setLayout(uvcLayout);
return ret;
}
void Widget::onSerialConnectButtonClicked() { void Widget::onSerialConnectButtonClicked() {
auto button = dynamic_cast<QPushButton *>(sender()); auto button = dynamic_cast<QPushButton *>(sender());
if (button == nullptr) return; if (button == nullptr) return;
@ -197,6 +210,9 @@ void Widget::onSerialRefreshButtonClicked() {
} }
} }
void Widget::onUvcRefreshButtonClicked() {
}
void Widget::onEnrollButtonClicked() { void Widget::onEnrollButtonClicked() {
auto name = m_enrollNameEdit->text(); auto name = m_enrollNameEdit->text();
auto timeout = m_enrollTimeoutEdit->text().toInt(); auto timeout = m_enrollTimeoutEdit->text().toInt();
@ -219,3 +235,9 @@ void Widget::onDeleteButtonClicked() {
auto id = m_deleteIdEdit->text().toInt(); auto id = m_deleteIdEdit->text().toInt();
m_communication->deleteUser(id); m_communication->deleteUser(id);
} }
void Widget::onRequestPalmFeatureButtonClicked() {
if (!m_communication) return;
auto id = m_palmFeatureEdit->text().toInt();
m_communication->requestPalmFeature(id);
}

View File

@ -18,19 +18,25 @@ public:
protected: protected:
QGroupBox *initializeCommandGroupBox(); QGroupBox *initializeCommandGroupBox();
void onClearLogButtonClicked();
void onSerialConnectButtonClicked(); void onSerialConnectButtonClicked();
void onSerialRefreshButtonClicked(); void onSerialRefreshButtonClicked();
void onUvcRefreshButtonClicked();
void onEnrollButtonClicked(); void onEnrollButtonClicked();
void onVerifyButtonClicked(); void onVerifyButtonClicked();
void onDeleteAllButtonClicked(); void onDeleteAllButtonClicked();
void onDeleteButtonClicked(); void onDeleteButtonClicked();
void onRequestPalmFeatureButtonClicked();
QGroupBox *initializeEnrollGroupBox(); QGroupBox *initializeEnrollGroupBox();
QGroupBox *initializeVerifyGroupBox(); QGroupBox *initializeVerifyGroupBox();
QGroupBox *initializeDeleteGroupBox(); QGroupBox *initializeDeleteGroupBox();
QGroupBox *initializePalmFeatureGroupBox(); QGroupBox *initializePalmFeatureGroupBox();
QGroupBox *initializeUvcGroupBox();
private: private:
QComboBox *m_serialComboBox = nullptr; QComboBox *m_serialComboBox = nullptr;
QPushButton *m_serialConnectButton = nullptr; QPushButton *m_serialConnectButton = nullptr;
@ -48,6 +54,8 @@ private:
QPushButton *m_deleteButton = nullptr; QPushButton *m_deleteButton = nullptr;
QPushButton *m_deleteAllButton = nullptr; QPushButton *m_deleteAllButton = nullptr;
QLineEdit *m_palmFeatureEdit = nullptr;
std::shared_ptr<ModuleCommunication> m_communication; std::shared_ptr<ModuleCommunication> m_communication;
}; };

View File

@ -0,0 +1,375 @@
## 串口配置
- 波特率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 为 NID_READY 的 MID_NOTE 消息: 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。**主控等待模组录入应答的超时时间应大于此参数设置值。**
主控下发消息格式如下:
<br/>
<table align="center">
<tr align="center">
<th>SyncWord</th>
<th>MsgID</th>
<th>DataSize</th>
<th colspan="2">Data</th>
<th>ParityCheck</th>
</tr>
<tr align="center">
<td>2 bytes</td>
<td>1 byte</td>
<td>2 bytes</td>
<td colspan="2">2 bytes</td>
<td>1 byte</td>
</tr>
<tr align="center">
<td>0xEF 0xAA</td>
<td>MID_VERIFY<br/>(0x12)</td>
<td>0x02</td>
<td>pd_rightaway<br/>(1 byte)</td>
<td>timeout<br/>(1 byte)</td>
<td> </td>
</tr>
</table>
识别结果 Result 确认码的返回值见 `命令结果上报MID_REPLY`
模组掌静脉识别成功后,通过消息 MID_REPLY 返回的数据 ResultData 定义如下:
```c++
struct msg_reply_verify_data {
uint16_t user_id;
uint8_t username[32];
uint8_t admin;
uint8_t unlockStatus;
};
```
参数说明:
- user_id识别成功的用户 ID
- username识别成功的用户名字
- admin识别成功的用户是否为管理员用户
- unlockStatus保留参数未使用
模组识别成功上报消息格式如下:
<table align="center">
<tr align="center">
<th rowspan="3">SyncWord</th>
<th rowspan="3">MsgID</th>
<th rowspan="3">DataSize</th>
<th colspan="6">Data</th>
<th>ParityCheck</th>
</tr>
<td>RID</td>
<td>Result</td>
<td colspan="4">ResultData</td>
<tr>
</tr>
<tr align="center">
<td>2 bytes</td>
<td>1 byte</td>
<td>2 bytes</td>
<td colspan="6">N bytes</td>
<td>1 byte</td>
</tr>
<tr>
</tr>
<tr align="center">
<td>0xEF 0xAA</td>
<td>MID_REPLY<br/>(0x00)</td>
<td>0x26</td>
<td>MID_VERIFY<br/>(0x12)</td>
<td>Result<br/>(1 byte)</td>
<td>user_id<br/>(2 byte)</td>
<td>username<br/>(32 byte)</td>
<td>admin<br/>(1 byte)</td>
<td>unlockStatus<br/>(1 byte)</td>
<td> </td>
</tr>
</table>
### 掌静脉注册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;
};
```
参数说明:
- user_id用户 ID 每次录入都会增加 1即使删除某个 ID 后,再次录入 ID 会继续增加,直至 65530ID 变为 0。
- face_direction保留暂未使用。
### 删除单个掌静脉MID_DELUSER
通过传入用户 ID, 删除指定 ID 的单个用户。
删除单个用户指令携带参数 msg_deluser_data 定义如下:
```c++
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 | 模组已上电初始化成功 |
## 示例报文