From c40e512df6ec16db159f8933dcc6f63f8d92f618 Mon Sep 17 00:00:00 2001 From: luocai <168062547@qq.com> Date: Wed, 22 May 2024 18:03:11 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=AE=9E=E7=8E=B0=E6=8E=8C=E9=9D=99=E8=84=89?= =?UTF-8?q?=E7=89=B9=E5=BE=81=E8=8E=B7=E5=8F=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Analyser/ModuleCommunication.cpp | 116 +++--- Analyser/ModuleCommunication.h | 14 + Analyser/Widget.cpp | 56 ++- Analyser/Widget.h | 8 + resources/L015掌静脉识别模组串口通信协议.md | 375 ++++++++++++++++++++ 5 files changed, 512 insertions(+), 57 deletions(-) create mode 100644 resources/L015掌静脉识别模组串口通信协议.md diff --git a/Analyser/ModuleCommunication.cpp b/Analyser/ModuleCommunication.cpp index 1f67b84..d50f24d 100644 --- a/Analyser/ModuleCommunication.cpp +++ b/Analyser/ModuleCommunication.cpp @@ -66,13 +66,16 @@ void ModuleCommunication::deleteAll() { LOG_CAT(info, GUI) << Separator; } -void ModuleCommunication::onReadyRead() { - auto data = m_serialPort->readAll(); - if (data.size() < 6) { - LOG(warning) << "invalid data: " - << protocolDataFormatString(reinterpret_cast(data.data()), data.size()); - return; - } +void ModuleCommunication::requestPalmFeature(uint16_t userid) { + uint16_t n = htons(userid); + auto [frameData, frameSize] = generateFrame(RequestPalmFeature, reinterpret_cast(&n), sizeof(n)); + m_serialPort->write(reinterpret_cast(frameData), frameSize); + LOG_CAT(info, GUI) << "发送获取掌静脉特征值指令: " << protocolDataFormatString(frameData, frameSize); + 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]; switch (messageId) { case Reply: { @@ -81,23 +84,17 @@ void ModuleCommunication::onReadyRead() { switch (replyId) { case Verify: { if (result == Success) { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); - auto info = reinterpret_cast(data.data() + 7); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); + auto info = reinterpret_cast(data + 7); LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid) << ", 用户名: " << std::string_view(reinterpret_cast(info->username)); LOG_CAT(info, GUI) << Separator; } else if (result == Failed4Timeout) { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "识别超时。"; LOG_CAT(info, GUI) << Separator; } else if (result == Failed4UnknownReason) { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "未录入用户。"; LOG_CAT(info, GUI) << Separator; } else { @@ -107,10 +104,8 @@ void ModuleCommunication::onReadyRead() { } case EnrollSingle: { if (result == Success) { - auto info = reinterpret_cast(data.data() + 7); - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + auto info = reinterpret_cast(data + 7); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "注册成功,用户ID: " << ntohs(info->userid); LOG_CAT(info, GUI) << Separator; } @@ -118,15 +113,11 @@ void ModuleCommunication::onReadyRead() { } case DeleteUser: { if (result == Success) { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "删除用户成功。"; LOG_CAT(info, GUI) << Separator; } else { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "删除用户失败。"; LOG_CAT(info, GUI) << Separator; } @@ -134,17 +125,26 @@ void ModuleCommunication::onReadyRead() { } case DeleteAll: { if (result == Success) { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), - data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "删除所有用户成功。"; LOG_CAT(info, GUI) << Separator; } break; } + case RequestPalmFeature: { + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); + if (result == Success) { + auto info = reinterpret_cast(data + 7); + LOG_CAT(info, GUI) << "用户ID: " << ntohs(info->userid) + << ", 用户名: " << std::string_view(reinterpret_cast(info->username)) + << ", 特征值长度: " << ntohs(info->featureTotalSize); + } + LOG_CAT(info, GUI) << Separator; + break; + } default: - LOG(warning) << "unknown reply command: 0x" << (static_cast(replyId) & 0xff) << ", data: " - << protocolDataFormatString(reinterpret_cast(data.data()), data.size()); + LOG(warning) << "unknown reply command: 0x" << (static_cast(replyId) & 0xff) + << ", data: " << protocolDataFormatString(data, size); break; } break; @@ -153,32 +153,68 @@ void ModuleCommunication::onReadyRead() { uint8_t noteId = data[5]; switch (noteId) { case Ready: { - LOG_CAT(info, GUI) << "模组: " - << protocolDataFormatString(reinterpret_cast(data.data()), data.size()); + LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size); LOG_CAT(info, GUI) << "模组上电初始化成功。"; LOG_CAT(info, GUI) << Separator; break; } case 0x01: { // 模组返回的数据为当前帧的手掌状态 - auto info = reinterpret_cast(data.data() + 7); + auto info = reinterpret_cast(data + 7); LOG(info) << info->state; break; } default: - LOG(warning) << "unknown note command: 0x" << (static_cast(noteId) & 0xff) << ", data: " - << protocolDataFormatString(reinterpret_cast(data.data()), data.size()); + LOG(warning) << "unknown note command: 0x" << (static_cast(noteId) & 0xff) + << ", data: " << protocolDataFormatString(data, size); break; } break; } default: - LOG(warning) << "unknown command: 0x" << (static_cast(data[2]) & 0xff) << ", data: " - << protocolDataFormatString(reinterpret_cast(data.data()), data.size()); + LOG(warning) << "unknown command: 0x" << (static_cast(data[2]) & 0xff) + << ", data: " << protocolDataFormatString(data, size); 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(m_receivedBuffer[i]) == 0xEF && + static_cast(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(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(m_receivedBuffer.data()), totalSize); + m_receivedBuffer.remove(0, totalSize); + } else { + break; + } + } +} + std::pair ModuleCommunication::generateFrame(MessageId command, const uint8_t *data, uint16_t size) { static uint8_t sendBuffer[1024] = {0}; @@ -198,7 +234,7 @@ std::pair ModuleCommunication::generateFrame(MessageId comm 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(data[i]) & 0xff) << " "; + oss << "0x" << std::setfill('0') << std::setw(2) << std::hex << (static_cast(data[i]) & 0xff) << " "; } return oss.str(); } diff --git a/Analyser/ModuleCommunication.h b/Analyser/ModuleCommunication.h index d7a433a..5f1dad2 100644 --- a/Analyser/ModuleCommunication.h +++ b/Analyser/ModuleCommunication.h @@ -19,6 +19,8 @@ public: EnrollSingle = 0x1D, DeleteUser = 0x20, DeleteAll = 0x21, + RegisterPalmFeature = 0xF9, + RequestPalmFeature = 0xFA, }; enum NoteId : uint8_t { @@ -79,6 +81,14 @@ public: uint8_t unlockStatus; }; + struct PalmFeatureHeader { + uint16_t userid; // 用户ID + uint8_t username[32]; // 用户姓名 + uint8_t admin; // 是否管理员,YES:1 NO:0 + uint8_t featureDataMd5[16]; // 整体特征数据的MD5值 + uint16_t featureTotalSize; // 特征数据总长度 + }; + #pragma pack() explicit ModuleCommunication(QObject *parent = nullptr); bool open(const QString &portName); @@ -88,13 +98,17 @@ public: void deleteUser(uint16_t userid); void deleteAll(); + void requestPalmFeature(uint16_t userid); + protected: + void processPackage(const uint8_t *data, uint16_t size); void onReadyRead(); std::pair generateFrame(MessageId command, const uint8_t *data = nullptr, uint16_t size = 0); std::string protocolDataFormatString(const uint8_t *data, int size); private: std::shared_ptr m_serialPort; + QByteArray m_receivedBuffer; }; #endif // MODULECOMMUNICATION_H diff --git a/Analyser/Widget.cpp b/Analyser/Widget.cpp index 1b77c4e..603b30e 100644 --- a/Analyser/Widget.cpp +++ b/Analyser/Widget.cpp @@ -39,26 +39,17 @@ Widget::Widget(QWidget *parent) : QWidget{parent} { 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); + connectLayout->addWidget(initializeUvcGroupBox()); m_logBrowser = new QTextBrowser(); 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(); tabWidget->addTab(m_logBrowser, "日志"); @@ -103,6 +94,10 @@ QGroupBox *Widget::initializeCommandGroupBox() { return ret; } +void Widget::onClearLogButtonClicked() { + m_logBrowser->clear(); +} + QGroupBox *Widget::initializeEnrollGroupBox() { auto ret = new QGroupBox("注册用户"); auto layout = new QFormLayout(); @@ -156,20 +151,38 @@ QGroupBox *Widget::initializePalmFeatureGroupBox() { auto layout = new QFormLayout(); - auto edit = new QLineEdit(""); - layout->addRow("用户ID:", edit); + m_palmFeatureEdit = new QLineEdit(""); + layout->addRow("用户ID:", m_palmFeatureEdit); auto button = new QPushButton("特征值下发"); layout->addRow("", button); auto button1 = new QPushButton("特征值上报"); layout->addRow("", button1); + connect(button1, &QPushButton::clicked, this, &Widget::onRequestPalmFeatureButtonClicked); ret->setLayout(layout); 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() { auto button = dynamic_cast(sender()); if (button == nullptr) return; @@ -197,6 +210,9 @@ void Widget::onSerialRefreshButtonClicked() { } } +void Widget::onUvcRefreshButtonClicked() { +} + void Widget::onEnrollButtonClicked() { auto name = m_enrollNameEdit->text(); auto timeout = m_enrollTimeoutEdit->text().toInt(); @@ -219,3 +235,9 @@ void Widget::onDeleteButtonClicked() { auto id = m_deleteIdEdit->text().toInt(); m_communication->deleteUser(id); } + +void Widget::onRequestPalmFeatureButtonClicked() { + if (!m_communication) return; + auto id = m_palmFeatureEdit->text().toInt(); + m_communication->requestPalmFeature(id); +} diff --git a/Analyser/Widget.h b/Analyser/Widget.h index a4de317..4f04e3e 100644 --- a/Analyser/Widget.h +++ b/Analyser/Widget.h @@ -18,19 +18,25 @@ public: protected: QGroupBox *initializeCommandGroupBox(); + void onClearLogButtonClicked(); void onSerialConnectButtonClicked(); void onSerialRefreshButtonClicked(); + void onUvcRefreshButtonClicked(); + void onEnrollButtonClicked(); void onVerifyButtonClicked(); void onDeleteAllButtonClicked(); void onDeleteButtonClicked(); + void onRequestPalmFeatureButtonClicked(); QGroupBox *initializeEnrollGroupBox(); QGroupBox *initializeVerifyGroupBox(); QGroupBox *initializeDeleteGroupBox(); QGroupBox *initializePalmFeatureGroupBox(); + QGroupBox *initializeUvcGroupBox(); + private: QComboBox *m_serialComboBox = nullptr; QPushButton *m_serialConnectButton = nullptr; @@ -48,6 +54,8 @@ private: QPushButton *m_deleteButton = nullptr; QPushButton *m_deleteAllButton = nullptr; + QLineEdit *m_palmFeatureEdit = nullptr; + std::shared_ptr m_communication; }; diff --git a/resources/L015掌静脉识别模组串口通信协议.md b/resources/L015掌静脉识别模组串口通信协议.md new file mode 100644 index 0000000..4077dbc --- /dev/null +++ b/resources/L015掌静脉识别模组串口通信协议.md @@ -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 。
0表示此消息无参数 | +| ParityCheck | 1 byte | 协议的奇偶检验码。
去除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)。**主控等待模组录入应答的超时时间应大于此参数设置值。** + +主控下发消息格式如下: + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
SyncWordMsgIDDataSizeDataParityCheck
2 bytes1 byte2 bytes2 bytes1 byte
0xEF 0xAAMID_VERIFY
(0x12)
0x02pd_rightaway
(1 byte)
timeout
(1 byte)
+ + +识别结果 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:保留参数,未使用 + +模组识别成功上报消息格式如下: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SyncWordMsgIDDataSizeDataParityCheck
RIDResultResultData
2 bytes1 byte2 bytesN bytes1 byte
0xEF 0xAAMID_REPLY
(0x00)
0x26MID_VERIFY
(0x12)
Result
(1 byte)
user_id
(2 byte)
username
(32 byte)
admin
(1 byte)
unlockStatus
(1 byte)
+ + +### 掌静脉注册(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 会继续增加,直至 65530,ID 变为 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): + +- 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 消息的完整协议如下所示: + + + + + + + + + + + + + + + + + + + + + + + + + +
SyncWordMsgIDDataSizeDataParityCheck
2 bytes1 byte2 bytesN bytes1 byte
0xEF 0xAAMID_REPLY(0x00) RID(1 byte)Result(1 byte)ResultData( N-2 bytes)
+ +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 消息主要作用是主动向主控上报一些信息,报文格式如下: + + + + + + + + + + + + + + + + + + + + + + + + +
SyncWordMsgIDDataSizeDataParityCheck
2 bytes1 byte2 bytesN bytes1 byte
0xEF 0xAAMID_NOTE(0x01) NID(1 byte)NoteData( N-1 bytes)
+ +目前NID主要内容如下: + +| NID(*表示该消息携带 NoteData 数据) | Code | 说明 | +| ------------------------------------ | ---- | -------------------- | +| NID_READY | 0x00 | 模组已上电初始化成功 | + +## 示例报文 + + + + +