Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a2764a9ade | ||
943fb6fb4b | |||
|
ce673bf330 | ||
|
28f2f60532 | ||
|
c846ae81a0 | ||
|
fd72d00925 | ||
|
8055f4e70f | ||
5ae5c5adc6 | |||
5b602e607c | |||
c3430caf2f | |||
0aa2721f67 | |||
|
6ee75bfae1 | ||
|
404889a19c | ||
|
881048a286 | ||
|
a6541c6a52 | ||
|
59d0a475f4 | ||
|
6f1aad0bac | ||
1c92079320 | |||
2a69cdc84b | |||
14dd4c8ee6 | |||
e9ec8ece00 | |||
4f9919a931 | |||
0e1e76a3a5 | |||
8fa26363b7 | |||
36c0a0226a | |||
|
e518f0841e | ||
|
2661f0729a | ||
9c8194dec2 | |||
f5f6569051 | |||
f968a0a242 | |||
e55983b41f | |||
ba63b6b491 | |||
4788f1c04d | |||
e3d537951c | |||
f0cbe30768 | |||
800369d7a3 | |||
10aea27972 | |||
a57dc9a11b | |||
fd97a1f727 | |||
9fb565d310 | |||
dd25ec9aa4 |
@ -1,25 +1,35 @@
|
|||||||
name: Build Applications
|
name: Build Applications
|
||||||
run-name: ${{ github.actor }} is building SmartLockerTools...
|
run-name: ${{ github.actor }} is building SmartLockerTools...
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
tags-ignore:
|
||||||
|
- 'v*'
|
||||||
|
paths:
|
||||||
|
- '**.cpp'
|
||||||
|
- '**.h'
|
||||||
|
- '**.conf'
|
||||||
jobs:
|
jobs:
|
||||||
PullDocker:
|
|
||||||
runs-on: [ubuntu-latest, ubuntu-24.04]
|
|
||||||
steps:
|
|
||||||
- name: Login to Docker Registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: frp-by1.wwvvww.cn:45288
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Pull Docker image
|
|
||||||
run: docker pull frp-by1.wwvvww.cn:45288/ubuntu_dev:24.04
|
|
||||||
Build:
|
Build:
|
||||||
runs-on: [ubuntu-latest, ubuntu-24.04]
|
runs-on: [ubuntu-latest, ubuntu-24.04]
|
||||||
container:
|
container:
|
||||||
image: frp-by1.wwvvww.cn:45288/ubuntu_dev:24.04
|
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/ubuntu_dev:24.04
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Set up SSH
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -t ed25519 -p 22022 frp-by1.wwvvww.cn >> ~/.ssh/known_hosts
|
||||||
|
- name: Clone repository
|
||||||
|
run: |
|
||||||
|
echo "git clone --depth 1 --branch=${GITHUB_REF##*/} ssh://git@frp-by1.wwvvww.cn:22022/${{ gitea.repository }}.git"
|
||||||
|
git clone --depth 1 --branch=${GITHUB_REF##*/} ssh://git@frp-by1.wwvvww.cn:22022/${{ gitea.repository }}.git .
|
||||||
|
git checkout ${GITHUB_SHA}
|
||||||
- run: resources/build.sh build
|
- run: resources/build.sh build
|
||||||
- run: resources/build.sh deploy
|
- run: resources/build.sh deploy
|
||||||
- name: Notify
|
- name: Notify
|
||||||
@ -27,8 +37,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "仓库名: ${{ github.repository }}" >> notify.tpl
|
echo "仓库名: ${{ github.repository }}" >> notify.tpl
|
||||||
echo "构建状态: ${{ job.status }}">> notify.tpl
|
echo "构建状态: ${{ job.status }}">> notify.tpl
|
||||||
echo "构建地址: https://gitea.amass.fun/${{ github.repository }}/actions/runs/${{ github.run_number }}">> notify.tpl
|
echo "构建地址: https://amass.fun/gitea/${{ github.repository }}/actions/runs/${{ github.run_number }}">> notify.tpl
|
||||||
echo "仓库地址: https://gitea.amass.fun/${{ github.repository }}">> notify.tpl
|
echo "仓库地址: https://amass.fun/gitea/${{ github.repository }}">> notify.tpl
|
||||||
echo "提交ID: $(git rev-parse --short HEAD)">> notify.tpl
|
echo "提交ID: $(git rev-parse --short HEAD)">> notify.tpl
|
||||||
echo -n "提交消息: ${{ github.event.head_commit.message }}">> notify.tpl
|
echo -n "提交消息: ${{ github.event.head_commit.message }}">> notify.tpl
|
||||||
cat notify.tpl | envsubst | jq -sR . | xargs -0 -I {} curl -H "Content-Type: application/json" -X POST -d '{"type":"text","msg":{} }' https://amass.fun/notify
|
cat notify.tpl | envsubst | jq -sR . | xargs -0 -I {} curl -H "Content-Type: application/json" -X POST -d '{"type":"text","msg":{} }' https://amass.fun/notify
|
@ -1,14 +1,39 @@
|
|||||||
name: Windows CI
|
name: Windows CI
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
tags-ignore:
|
||||||
|
- 'v*'
|
||||||
|
paths:
|
||||||
|
- '**.cpp'
|
||||||
|
- '**.h'
|
||||||
|
- '**.conf'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: [windows11]
|
runs-on: [windows11]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Clone repository
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
|
$ref = "${{ github.ref }}"
|
||||||
|
if ($ref -like 'refs/heads/*') {
|
||||||
|
$branch = $ref -replace '^refs/heads/', ''
|
||||||
|
} elseif ($ref -like 'refs/tags/*') {
|
||||||
|
$branch = $ref -replace '^refs/tags/', ''
|
||||||
|
} else {
|
||||||
|
$branch = $ref
|
||||||
|
}
|
||||||
|
git clone --depth 1 --branch=$branch https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@amass.fun/gitea/${{ gitea.repository }}.git .
|
||||||
|
git checkout ${{ github.sha }}
|
||||||
- name: Build and deploy
|
- name: Build and deploy
|
||||||
run: |
|
run: |
|
||||||
resources/build.ps1 build
|
resources/build.ps1 build
|
||||||
resources/build.ps1 deploy
|
resources/build.ps1 deploy
|
||||||
resources/build.ps1 changelog
|
resources/build.ps1 changelog
|
||||||
|
- name: Upload Gitea Release
|
||||||
|
uses: akkuman/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
body_path: build/CHANGELOG.txt
|
||||||
|
files: |-
|
||||||
|
build/掌静脉工具v*.zip
|
@ -1,5 +1,5 @@
|
|||||||
name: Deploy Release
|
name: Deploy Release
|
||||||
run-name: ${{ github.actor }} is building Bilby...
|
run-name: ${{ github.actor }} is building SmartLockerTools...
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@ -8,12 +8,22 @@ jobs:
|
|||||||
Build:
|
Build:
|
||||||
runs-on: [ubuntu-latest, ubuntu-24.04]
|
runs-on: [ubuntu-latest, ubuntu-24.04]
|
||||||
container:
|
container:
|
||||||
image: frp-by1.wwvvww.cn:45288/ubuntu_dev:24.04
|
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/ubuntu_dev:24.04
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Set up SSH
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
with:
|
mkdir -p ~/.ssh/
|
||||||
fetch-depth: 0
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -t ed25519 -p 22022 frp-by1.wwvvww.cn >> ~/.ssh/known_hosts
|
||||||
|
- name: Clone repository
|
||||||
|
run: |
|
||||||
|
echo "git clone --depth 1 --branch=${GITHUB_REF##*/} ssh://git@frp-by1.wwvvww.cn:22022/${{ gitea.repository }}.git"
|
||||||
|
git clone --depth 1 --branch=${GITHUB_REF##*/} ssh://git@frp-by1.wwvvww.cn:22022/${{ gitea.repository }}.git .
|
||||||
|
git checkout ${GITHUB_SHA}
|
||||||
- run: resources/build.sh build
|
- run: resources/build.sh build
|
||||||
- run: resources/build.sh deploy
|
- run: resources/build.sh deploy
|
||||||
- name: Generate Changelog
|
- name: Generate Changelog
|
||||||
|
@ -8,8 +8,18 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: [windows11]
|
runs-on: [windows11]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Clone repository
|
||||||
uses: actions/checkout@v4
|
run: |
|
||||||
|
$ref = "${{ github.ref }}"
|
||||||
|
if ($ref -like 'refs/heads/*') {
|
||||||
|
$branch = $ref -replace '^refs/heads/', ''
|
||||||
|
} elseif ($ref -like 'refs/tags/*') {
|
||||||
|
$branch = $ref -replace '^refs/tags/', ''
|
||||||
|
} else {
|
||||||
|
$branch = $ref
|
||||||
|
}
|
||||||
|
git clone --depth 1 --branch=$branch https://${{ secrets.GIT_USERNAME }}:${{ secrets.GIT_PASSWORD }}@amass.fun/gitea/${{ gitea.repository }}.git .
|
||||||
|
git checkout ${{ github.sha }}
|
||||||
- name: Build and deploy
|
- name: Build and deploy
|
||||||
run: |
|
run: |
|
||||||
resources/build.ps1 build
|
resources/build.ps1 build
|
||||||
@ -20,4 +30,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
body_path: build/CHANGELOG.txt
|
body_path: build/CHANGELOG.txt
|
||||||
files: |-
|
files: |-
|
||||||
build/SmartLockerTools.zip
|
build/掌静脉工具v*.zip
|
@ -6,6 +6,7 @@
|
|||||||
#include "Configuration.h"
|
#include "Configuration.h"
|
||||||
#include "Database.h"
|
#include "Database.h"
|
||||||
#include "DateTime.h"
|
#include "DateTime.h"
|
||||||
|
#include "DeviceDiscovery.h"
|
||||||
#include "ImageDecoder.h"
|
#include "ImageDecoder.h"
|
||||||
#include "StringUtility.h"
|
#include "StringUtility.h"
|
||||||
#include "VideoFrameProvider.h"
|
#include "VideoFrameProvider.h"
|
||||||
@ -20,7 +21,6 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <mbedtls/md5.h>
|
#include <mbedtls/md5.h>
|
||||||
#include "DeviceDiscovery.h"
|
|
||||||
|
|
||||||
constexpr uint32_t ImageSliceSize = (4000 - 32);
|
constexpr uint32_t ImageSliceSize = (4000 - 32);
|
||||||
|
|
||||||
@ -58,12 +58,13 @@ void Application::onNewEnrollResult(uint16_t userid) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::onNewVerifyResult(uint16_t userid, const QString &username) {
|
void Application::onNewVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed) {
|
||||||
m_palmUsername = username;
|
m_palmUsername = username;
|
||||||
m_palmId = userid;
|
m_palmId = userid;
|
||||||
QTimer::singleShot(0, this, [this, userid, username]() {
|
QTimer::singleShot(0, this, [this, userid, username, elapsed]() {
|
||||||
emit newStatusTip(Info,
|
emit newStatusTip(Info, QString("%1,识别耗时: %2ms")
|
||||||
QString("%1").arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username));
|
.arg(userid == ModuleCommunication::InvalidUserId ? "未录入用户" : username)
|
||||||
|
.arg(elapsed));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,12 +119,15 @@ QStringList Application::availableSerialPorts() const {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Application::availableUsbVideoCameras() const {
|
QVariantList Application::availableUsbVideoCameras() const {
|
||||||
QStringList ret;
|
QVariantList ret;
|
||||||
DeviceDiscovery d;
|
DeviceDiscovery d;
|
||||||
auto devices = d.devices();
|
auto devices = d.devices();
|
||||||
for (auto &device : devices) {
|
for (auto &device : devices) {
|
||||||
ret << QString::fromStdString(device);
|
QVariantMap item;
|
||||||
|
item.insert("name", QString::fromStdString(device.friendlyName));
|
||||||
|
item.insert("path", QString::fromStdString(device.alternativeName));
|
||||||
|
ret << item;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -144,6 +148,7 @@ bool Application::open(const QString &portName, int baudRate) {
|
|||||||
emit newStatusTip(status ? Tip : Error, status ? "串口打开成功" : "串口打开失败");
|
emit newStatusTip(status ? Tip : Error, status ? "串口打开成功" : "串口打开失败");
|
||||||
if (status) {
|
if (status) {
|
||||||
QTimer::singleShot(0, this, [this]() { m_communication->requestVersion(); });
|
QTimer::singleShot(0, this, [this]() { m_communication->requestVersion(); });
|
||||||
|
QTimer::singleShot(0, this, [this]() { m_communication->requestDebugSettings(); });
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -174,13 +179,22 @@ bool Application::openUVC(const QString &deviceName) {
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::resetModule() {
|
||||||
|
if (connected()) {
|
||||||
|
m_communication->reset();
|
||||||
|
}
|
||||||
|
m_imageUploadling = false;
|
||||||
|
m_persistenceModeStarted = false;
|
||||||
|
if (m_verifyTimer->isActive()) {
|
||||||
|
m_verifyTimer->stop();
|
||||||
|
}
|
||||||
|
emit currentMessageIdChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::close() {
|
void Application::close() {
|
||||||
|
resetModule();
|
||||||
m_communication.reset();
|
m_communication.reset();
|
||||||
emit connectedChanged();
|
emit connectedChanged();
|
||||||
|
|
||||||
m_persistenceModeStarted = false;
|
|
||||||
m_verifyTimer->stop();
|
|
||||||
emit isVerifyingChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::closeUVC() {
|
void Application::closeUVC() {
|
||||||
@ -191,19 +205,24 @@ void Application::closeUVC() {
|
|||||||
emit uvcOpenedChanged();
|
emit uvcOpenedChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::verify(uint8_t timeout) {
|
void Application::verify(bool captureImage, uint8_t timeout) {
|
||||||
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
||||||
m_communication->reset();
|
m_communication->reset();
|
||||||
}
|
}
|
||||||
|
if (captureImage) {
|
||||||
|
m_communication->verifyExtended(captureImage, timeout);
|
||||||
|
} else {
|
||||||
m_communication->verify(timeout);
|
m_communication->verify(timeout);
|
||||||
}
|
}
|
||||||
|
m_verifyExtendedMode = captureImage;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::enroll(const QString &username, uint8_t timeout) {
|
void Application::enroll(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout) {
|
||||||
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
||||||
m_communication->reset();
|
m_communication->reset();
|
||||||
}
|
}
|
||||||
m_communication->enroll(username.toStdString(), timeout);
|
m_communication->enroll(username.toStdString(), strictMode, excludeMode, persistence, timeout);
|
||||||
m_palmUsername = username;
|
m_palmUsername = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,6 +240,15 @@ void Application::deleteAll() {
|
|||||||
m_communication->deleteAll();
|
m_communication->deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::enrollExtended(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout) {
|
||||||
|
if (m_communication->currentMessageId() != ModuleCommunication::Idle) {
|
||||||
|
m_communication->reset();
|
||||||
|
}
|
||||||
|
m_communication->enrollExtended(username.toStdString(), strictMode, excludeMode, persistence, timeout);
|
||||||
|
m_palmUsername = username;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::uploadImage(const QString &path, const QString &username, int operation) {
|
void Application::uploadImage(const QString &path, const QString &username, int operation) {
|
||||||
m_uploadImageSendedSize = 0;
|
m_uploadImageSendedSize = 0;
|
||||||
ModuleCommunication::UploadImageInformation request;
|
ModuleCommunication::UploadImageInformation request;
|
||||||
@ -279,26 +307,29 @@ bool Application::uvcOpened() const {
|
|||||||
return static_cast<bool>(m_videoPlayer);
|
return static_cast<bool>(m_videoPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::persistenceMode() const {
|
ModuleCommunication::MessageId Application::persistenceCommand() const {
|
||||||
return m_persistenceMode;
|
return m_persistenceCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setPersistenceMode(bool enabled) {
|
void Application::setPersistenceCommand(ModuleCommunication::MessageId command) {
|
||||||
if (m_persistenceMode != enabled) {
|
if (m_persistenceCommand != command) {
|
||||||
m_persistenceMode = enabled;
|
m_persistenceCommand = command;
|
||||||
emit persistenceModeChanged();
|
emit persistenceCommandChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::imageUploadPersistenceMode() const {
|
ModuleCommunication::MessageId Application::currentMessageId() const {
|
||||||
return m_imageUploadPersistenceMode;
|
ModuleCommunication::MessageId ret = ModuleCommunication::Idle;
|
||||||
|
if (connected()) {
|
||||||
|
if (m_persistenceModeStarted) {
|
||||||
|
ret = m_persistenceCommand;
|
||||||
|
} else {
|
||||||
|
ret = m_communication->currentMessageId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::setImageUploadPersistenceMode(bool enabled) {
|
|
||||||
if (m_imageUploadPersistenceMode != enabled) {
|
|
||||||
m_imageUploadPersistenceMode = enabled;
|
|
||||||
emit imageUploadPersistenceModeChanged();
|
|
||||||
}
|
}
|
||||||
|
// LOG(info) << "current message id: " << static_cast<int>(ret)
|
||||||
|
// << ", persistence mode started: " << m_persistenceModeStarted;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Application::persistenceVerifyInterval() const {
|
int Application::persistenceVerifyInterval() const {
|
||||||
@ -312,15 +343,6 @@ void Application::setPersistenceVerifyInterval(int interval) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::isVerifying() const {
|
|
||||||
if (!m_communication) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (m_persistenceMode && m_persistenceModeStarted) ||
|
|
||||||
(m_communication->currentMessageId() == ModuleCommunication::Verify) ||
|
|
||||||
(m_communication->currentMessageId() == ModuleCommunication::VerifyExtended);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::onNewPalmFeature(const PalmFeature &feature) {
|
void Application::onNewPalmFeature(const PalmFeature &feature) {
|
||||||
auto palms = m_database->palmFeatures();
|
auto palms = m_database->palmFeatures();
|
||||||
if (std::find(palms.cbegin(), palms.cend(), feature) != palms.cend()) {
|
if (std::find(palms.cbegin(), palms.cend(), feature) != palms.cend()) {
|
||||||
@ -389,7 +411,7 @@ void Application::onNewImageSliceData(const std::vector<uint8_t> &data) {
|
|||||||
|
|
||||||
void Application::onCommandStarted(ModuleCommunication::MessageId messageId) {
|
void Application::onCommandStarted(ModuleCommunication::MessageId messageId) {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
emit isVerifyingChanged();
|
emit currentMessageIdChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::onCommandFinished(ModuleCommunication::MessageId messageId,
|
void Application::onCommandFinished(ModuleCommunication::MessageId messageId,
|
||||||
@ -401,45 +423,53 @@ void Application::onCommandFinished(ModuleCommunication::MessageId messageId,
|
|||||||
ImageSliceSize);
|
ImageSliceSize);
|
||||||
} else if (messageId == ModuleCommunication::UploadImageData) {
|
} else if (messageId == ModuleCommunication::UploadImageData) {
|
||||||
m_uploadImageSendedSize += ImageSliceSize;
|
m_uploadImageSendedSize += ImageSliceSize;
|
||||||
if (m_uploadImageSendedSize < m_uploadBuffer.size()) {
|
if (m_imageUploadling && (m_uploadImageSendedSize < m_uploadBuffer.size())) {
|
||||||
auto remainSize = m_uploadBuffer.size() - m_uploadImageSendedSize;
|
auto remainSize = m_uploadBuffer.size() - m_uploadImageSendedSize;
|
||||||
m_communication->uploadImageData(m_uploadImageSendedSize,
|
m_communication->uploadImageData(m_uploadImageSendedSize,
|
||||||
(const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize,
|
(const uint8_t *)m_uploadBuffer.data() + m_uploadImageSendedSize,
|
||||||
remainSize < ImageSliceSize ? remainSize : ImageSliceSize);
|
remainSize < ImageSliceSize ? remainSize : ImageSliceSize);
|
||||||
m_imageUploadling = true;
|
|
||||||
}
|
}
|
||||||
if (status != ModuleCommunication::Needmore) {
|
if (status != ModuleCommunication::Needmore) {
|
||||||
LOG(info) << "upload image finished, status: " << static_cast<int>(status)
|
LOG(info) << "upload image finished, status: " << static_cast<int>(status)
|
||||||
<< ", elapsed: " << duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
|
<< ", elapsed: " << duration_cast<milliseconds>(system_clock::now() - m_startUploadTime);
|
||||||
m_imageUploadling = false;
|
if (m_imageUploadling && (m_persistenceCommand == ModuleCommunication::UploadImageInfo)) {
|
||||||
if (m_imageUploadPersistenceMode) {
|
QTimer::singleShot(0, this,
|
||||||
QTimer::singleShot(1, this,
|
|
||||||
[this]() { uploadImage(m_uploadPath, m_uploadUsername, m_currentUploadOperation); });
|
[this]() { uploadImage(m_uploadPath, m_uploadUsername, m_currentUploadOperation); });
|
||||||
}
|
}
|
||||||
|
m_imageUploadling = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) &&
|
// LOG(info) << "messageId: " << (int)messageId << ", m_persistenceCommand: " << (int)m_persistenceCommand;
|
||||||
m_persistenceMode) { // 持续识别逻辑
|
if (messageId == m_persistenceCommand) {
|
||||||
m_persistenceModeStarted = true;
|
m_persistenceModeStarted = true;
|
||||||
} else if (messageId == ModuleCommunication::Reset) {
|
}
|
||||||
|
if (messageId == ModuleCommunication::Reset) {
|
||||||
m_persistenceModeStarted = false;
|
m_persistenceModeStarted = false;
|
||||||
|
if (m_verifyTimer->isActive()) {
|
||||||
m_verifyTimer->stop();
|
m_verifyTimer->stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_persistenceMode && m_persistenceModeStarted &&
|
if (((m_persistenceCommand == ModuleCommunication::Verify) ||
|
||||||
|
(m_persistenceCommand == ModuleCommunication::VerifyExtended)) &&
|
||||||
|
m_persistenceModeStarted &&
|
||||||
((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) &&
|
((messageId == ModuleCommunication::Verify) || (messageId == ModuleCommunication::VerifyExtended)) &&
|
||||||
((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) ||
|
((status == ModuleCommunication::Success) || (status == ModuleCommunication::Failed4UnknownUser) ||
|
||||||
(status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason) ||
|
(status == ModuleCommunication::Failed4Timeout) || (status == ModuleCommunication::Failed4UnknownReason) ||
|
||||||
(status == ModuleCommunication::Failed4LivenessCheck))) {
|
(status == ModuleCommunication::Failed4LivenessCheck))) {
|
||||||
m_verifyTimer->start(m_persistenceVerifyInterval * 1000);
|
m_verifyTimer->start(m_persistenceVerifyInterval * 1000);
|
||||||
}
|
}
|
||||||
emit isVerifyingChanged();
|
emit currentMessageIdChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::onVerifyTimeout() {
|
void Application::onVerifyTimeout() {
|
||||||
|
if (m_verifyExtendedMode) {
|
||||||
|
m_communication->verifyExtended(m_verifyExtendedMode, 120);
|
||||||
|
} else {
|
||||||
m_communication->verify(120);
|
m_communication->verify(120);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Application::startOta(const QString &path) {
|
bool Application::startOta(const QString &path) {
|
||||||
if (!QFile::exists(path)) {
|
if (!QFile::exists(path)) {
|
||||||
@ -451,6 +481,7 @@ bool Application::startOta(const QString &path) {
|
|||||||
LOG(info) << "device already in ota mode.";
|
LOG(info) << "device already in ota mode.";
|
||||||
} else {
|
} else {
|
||||||
if (m_communication) {
|
if (m_communication) {
|
||||||
|
resetModule();
|
||||||
m_communication->startOta();
|
m_communication->startOta();
|
||||||
} else {
|
} else {
|
||||||
emit otaMessage("请先打开设备");
|
emit otaMessage("请先打开设备");
|
||||||
|
@ -22,12 +22,10 @@ class Application : public QObject {
|
|||||||
Q_PROPERTY(ModuleCommunication *module READ module NOTIFY connectedChanged)
|
Q_PROPERTY(ModuleCommunication *module READ module NOTIFY connectedChanged)
|
||||||
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
Q_PROPERTY(bool connected READ connected NOTIFY connectedChanged)
|
||||||
Q_PROPERTY(bool uvcOpened READ uvcOpened NOTIFY uvcOpenedChanged)
|
Q_PROPERTY(bool uvcOpened READ uvcOpened NOTIFY uvcOpenedChanged)
|
||||||
Q_PROPERTY(bool persistenceMode READ persistenceMode WRITE setPersistenceMode NOTIFY persistenceModeChanged)
|
Q_PROPERTY(ModuleCommunication::MessageId persistenceCommand READ persistenceCommand WRITE setPersistenceCommand NOTIFY persistenceCommandChanged)
|
||||||
Q_PROPERTY(bool imageUploadPersistenceMode READ imageUploadPersistenceMode WRITE setImageUploadPersistenceMode
|
|
||||||
NOTIFY imageUploadPersistenceModeChanged)
|
|
||||||
Q_PROPERTY(int persistenceVerifyInterval READ persistenceVerifyInterval WRITE setPersistenceVerifyInterval NOTIFY
|
Q_PROPERTY(int persistenceVerifyInterval READ persistenceVerifyInterval WRITE setPersistenceVerifyInterval NOTIFY
|
||||||
persistenceVerifyIntervalChanged)
|
persistenceVerifyIntervalChanged)
|
||||||
Q_PROPERTY(bool isVerifying READ isVerifying NOTIFY isVerifyingChanged)
|
Q_PROPERTY(ModuleCommunication::MessageId currentMessageId READ currentMessageId NOTIFY currentMessageIdChanged)
|
||||||
friend class Amass::Singleton<Application>;
|
friend class Amass::Singleton<Application>;
|
||||||
static constexpr auto JpgPath = "jpg";
|
static constexpr auto JpgPath = "jpg";
|
||||||
static constexpr auto YuvPath = "yuv";
|
static constexpr auto YuvPath = "yuv";
|
||||||
@ -45,35 +43,36 @@ public:
|
|||||||
int exec();
|
int exec();
|
||||||
static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
|
||||||
Q_INVOKABLE QStringList availableSerialPorts() const;
|
Q_INVOKABLE QStringList availableSerialPorts() const;
|
||||||
Q_INVOKABLE QStringList availableUsbVideoCameras() const;
|
Q_INVOKABLE QVariantList availableUsbVideoCameras() const;
|
||||||
Q_INVOKABLE bool open(const QString &portName, int baudRate);
|
Q_INVOKABLE bool open(const QString &portName, int baudRate);
|
||||||
Q_INVOKABLE bool openUVC(const QString &deviceName);
|
Q_INVOKABLE bool openUVC(const QString &deviceName);
|
||||||
Q_INVOKABLE void close();
|
Q_INVOKABLE void close();
|
||||||
Q_INVOKABLE void closeUVC();
|
Q_INVOKABLE void closeUVC();
|
||||||
Q_INVOKABLE bool startOta(const QString &path);
|
Q_INVOKABLE bool startOta(const QString &path);
|
||||||
Q_INVOKABLE void verify(uint8_t timeout);
|
Q_INVOKABLE void verify(bool captureImage, uint8_t timeout);
|
||||||
Q_INVOKABLE void enroll(const QString &username, uint8_t timeout);
|
Q_INVOKABLE void enroll(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout);
|
||||||
|
Q_INVOKABLE void enrollExtended(const QString &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout);
|
||||||
Q_INVOKABLE void deleteUser(uint16_t userid);
|
Q_INVOKABLE void deleteUser(uint16_t userid);
|
||||||
Q_INVOKABLE void deleteAll();
|
Q_INVOKABLE void deleteAll();
|
||||||
Q_INVOKABLE void uploadImage(const QString &path, const QString &username, int operation);
|
Q_INVOKABLE void uploadImage(const QString &path, const QString &username, int operation);
|
||||||
ModuleCommunication *module() const;
|
ModuleCommunication *module() const;
|
||||||
|
Q_INVOKABLE void resetModule();
|
||||||
bool connected() const;
|
bool connected() const;
|
||||||
bool uvcOpened() const;
|
bool uvcOpened() const;
|
||||||
bool persistenceMode() const;
|
ModuleCommunication::MessageId persistenceCommand() const;
|
||||||
void setPersistenceMode(bool enabled);
|
void setPersistenceCommand(ModuleCommunication::MessageId command);
|
||||||
bool imageUploadPersistenceMode() const;
|
ModuleCommunication::MessageId currentMessageId() const;
|
||||||
void setImageUploadPersistenceMode(bool enabled);
|
|
||||||
|
|
||||||
int persistenceVerifyInterval() const;
|
int persistenceVerifyInterval() const;
|
||||||
void setPersistenceVerifyInterval(int interval);
|
void setPersistenceVerifyInterval(int interval);
|
||||||
bool isVerifying() const;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
void persistenceModeChanged();
|
void persistenceCommandChanged();
|
||||||
void persistenceVerifyIntervalChanged();
|
void persistenceVerifyIntervalChanged();
|
||||||
void imageUploadPersistenceModeChanged();
|
void currentMessageIdChanged();
|
||||||
void isVerifyingChanged();
|
|
||||||
void uvcOpenedChanged();
|
void uvcOpenedChanged();
|
||||||
void newVideoFrame();
|
void newVideoFrame();
|
||||||
void newLog(const QString &log);
|
void newLog(const QString &log);
|
||||||
@ -85,7 +84,7 @@ signals:
|
|||||||
protected:
|
protected:
|
||||||
Application(int &argc, char **argv);
|
Application(int &argc, char **argv);
|
||||||
void onNewEnrollResult(uint16_t userid);
|
void onNewEnrollResult(uint16_t userid);
|
||||||
void onNewVerifyResult(uint16_t userid, const QString &username);
|
void onNewVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed);
|
||||||
void onNewPalmFeature(const PalmFeature &feature);
|
void onNewPalmFeature(const PalmFeature &feature);
|
||||||
void onErrorOccurred(ModuleCommunication::NoteId note, const QString &error, const QString &detailMessage);
|
void onErrorOccurred(ModuleCommunication::NoteId note, const QString &error, const QString &detailMessage);
|
||||||
void onNewImageInfo(ModuleCommunication::MessageId messageId, uint32_t size, const uint8_t *md5);
|
void onNewImageInfo(ModuleCommunication::MessageId messageId, uint32_t size, const uint8_t *md5);
|
||||||
@ -102,7 +101,8 @@ private:
|
|||||||
std::shared_ptr<CdcUpdater> m_updater;
|
std::shared_ptr<CdcUpdater> m_updater;
|
||||||
std::shared_ptr<Database> m_database;
|
std::shared_ptr<Database> m_database;
|
||||||
|
|
||||||
bool m_persistenceMode = true; // 模组持续识别
|
ModuleCommunication::MessageId m_persistenceCommand = ModuleCommunication::Idle; // 模组持续识别
|
||||||
|
bool m_verifyExtendedMode = false;
|
||||||
bool m_persistenceModeStarted = false;
|
bool m_persistenceModeStarted = false;
|
||||||
int m_persistenceVerifyInterval = 1;
|
int m_persistenceVerifyInterval = 1;
|
||||||
QTimer *m_verifyTimer = nullptr;
|
QTimer *m_verifyTimer = nullptr;
|
||||||
@ -120,7 +120,6 @@ private:
|
|||||||
QString m_uploadPath;
|
QString m_uploadPath;
|
||||||
QString m_uploadUsername;
|
QString m_uploadUsername;
|
||||||
bool m_imageUploadling = false;
|
bool m_imageUploadling = false;
|
||||||
bool m_imageUploadPersistenceMode = false;
|
|
||||||
|
|
||||||
std::shared_ptr<VideoPlayer> m_videoPlayer;
|
std::shared_ptr<VideoPlayer> m_videoPlayer;
|
||||||
VideoFrameProvider *m_videoFrameProvider;
|
VideoFrameProvider *m_videoFrameProvider;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
project(Analyser VERSION 0.3 LANGUAGES C CXX)
|
project(Analyser VERSION 0.4 LANGUAGES C CXX)
|
||||||
set(APPLICATION_NAME "掌静脉测试工具")
|
set(APPLICATION_NAME "掌静脉测试工具")
|
||||||
|
|
||||||
|
find_package(Boost REQUIRED COMPONENTS json)
|
||||||
|
|
||||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick QuickTemplates2 SerialPort JpegPrivate BundledLibjpeg)
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Quick QuickTemplates2 SerialPort JpegPrivate BundledLibjpeg)
|
||||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick QuickTemplates2 SerialPort JpegPrivate BundledLibjpeg)
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Quick QuickTemplates2 SerialPort JpegPrivate BundledLibjpeg)
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ qt_add_qml_module(Analyser
|
|||||||
resources/successfull.svg
|
resources/successfull.svg
|
||||||
resources/warning.svg
|
resources/warning.svg
|
||||||
resources/palm-middle.png
|
resources/palm-middle.png
|
||||||
|
QML_FILES qml/LogView.qml
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_definitions(Analyser
|
target_compile_definitions(Analyser
|
||||||
@ -74,6 +77,7 @@ target_link_libraries(Analyser
|
|||||||
PRIVATE avdevice
|
PRIVATE avdevice
|
||||||
PRIVATE avformat
|
PRIVATE avformat
|
||||||
$<$<PLATFORM_ID:Windows>:Ws2_32>
|
$<$<PLATFORM_ID:Windows>:Ws2_32>
|
||||||
|
PRIVATE Boost::json
|
||||||
PRIVATE Qt${QT_VERSION_MAJOR}::Quick
|
PRIVATE Qt${QT_VERSION_MAJOR}::Quick
|
||||||
PRIVATE Qt${QT_VERSION_MAJOR}::QuickTemplates2
|
PRIVATE Qt${QT_VERSION_MAJOR}::QuickTemplates2
|
||||||
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
|
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
#include "BoostLog.h"
|
#include "BoostLog.h"
|
||||||
#include "StringUtility.h"
|
#include "StringUtility.h"
|
||||||
#include <boost/describe.hpp>
|
#include <boost/describe.hpp>
|
||||||
|
#include <boost/json/object.hpp>
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <boost/json/serialize.hpp>
|
||||||
#include <boost/mp11.hpp>
|
#include <boost/mp11.hpp>
|
||||||
#include <mbedtls/md5.h>
|
#include <mbedtls/md5.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -52,6 +55,18 @@ void ModuleCommunication::verify(uint8_t timeout) {
|
|||||||
LOG_CAT(info, GUI) << Separator;
|
LOG_CAT(info, GUI) << Separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModuleCommunication::verifyExtended(bool captureImage, uint8_t timeout) {
|
||||||
|
VerifyRequest data = {0};
|
||||||
|
data.timeout = timeout;
|
||||||
|
data.save_image = captureImage ? 0x01 : 0x00;
|
||||||
|
auto [frameData, frameSize] = generateFrame(VerifyExtended, reinterpret_cast<const uint8_t *>(&data), sizeof(data));
|
||||||
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
|
setCurrentMessageIdStatus(VerifyExtended);
|
||||||
|
|
||||||
|
LOG_CAT(info, GUI) << "发送扩展识别指令: " << protocolDataFormatString(frameData, frameSize);
|
||||||
|
LOG_CAT(info, GUI) << Separator;
|
||||||
|
}
|
||||||
|
|
||||||
void ModuleCommunication::reset() {
|
void ModuleCommunication::reset() {
|
||||||
auto [frameData, frameSize] = generateFrame(Reset);
|
auto [frameData, frameSize] = generateFrame(Reset);
|
||||||
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
@ -60,9 +75,13 @@ void ModuleCommunication::reset() {
|
|||||||
LOG_CAT(info, GUI) << Separator;
|
LOG_CAT(info, GUI) << Separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) {
|
void ModuleCommunication::enroll(const std::string &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout) {
|
||||||
EnrollRequest data = {0};
|
EnrollRequest data = {0};
|
||||||
|
data.strictMode = strictMode ? 1 : 0;
|
||||||
|
data.excludeMode = excludeMode;
|
||||||
data.timeout = timeout;
|
data.timeout = timeout;
|
||||||
|
data.skipSave = persistence ? 0 : 1;
|
||||||
strncpy(reinterpret_cast<char *>(data.username), username.c_str(), sizeof(data.username));
|
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));
|
auto [frameData, frameSize] = generateFrame(EnrollSingle, reinterpret_cast<const uint8_t *>(&data), sizeof(data));
|
||||||
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
@ -73,6 +92,22 @@ void ModuleCommunication::enroll(const std::string &username, uint8_t timeout) {
|
|||||||
LOG_CAT(info, GUI) << Separator;
|
LOG_CAT(info, GUI) << Separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModuleCommunication::enrollExtended(const std::string &username, bool strictMode, uint8_t excludeMode,
|
||||||
|
bool persistence, uint8_t timeout) {
|
||||||
|
EnrollRequest data = {};
|
||||||
|
data.strictMode = strictMode ? 1 : 0;
|
||||||
|
data.excludeMode = excludeMode;
|
||||||
|
data.timeout = timeout;
|
||||||
|
data.skipSave = persistence ? 0 : 1;
|
||||||
|
strncpy(reinterpret_cast<char *>(data.username), username.c_str(), sizeof(data.username));
|
||||||
|
auto [frameData, frameSize] = generateFrame(EnrollExtended, reinterpret_cast<const uint8_t *>(&data), sizeof(data));
|
||||||
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
|
setCurrentMessageIdStatus(EnrollExtended);
|
||||||
|
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) {
|
void ModuleCommunication::deleteUser(uint16_t userid) {
|
||||||
uint16_t n = htons(userid);
|
uint16_t n = htons(userid);
|
||||||
auto [frameData, frameSize] = generateFrame(DeleteUser, reinterpret_cast<const uint8_t *>(&n), sizeof(n));
|
auto [frameData, frameSize] = generateFrame(DeleteUser, reinterpret_cast<const uint8_t *>(&n), sizeof(n));
|
||||||
@ -135,8 +170,29 @@ void ModuleCommunication::requestCurrentStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ModuleCommunication::setDebugEnabled(bool enabled) {
|
void ModuleCommunication::setDebugEnabled(bool enabled) {
|
||||||
uint8_t data = enabled ? 0x01 : 0x00;
|
DebugRequest request;
|
||||||
auto [frameData, frameSize] = generateFrame(EnableDebug, &data, sizeof(data));
|
boost::json::object object;
|
||||||
|
object["command"] = "set_configurations";
|
||||||
|
object["cdc_log_enabled"] = enabled;
|
||||||
|
auto text = boost::json::serialize(object);
|
||||||
|
std::strncpy(request.message, text.c_str(), sizeof(request.message));
|
||||||
|
request.length = htons(text.size());
|
||||||
|
|
||||||
|
auto [frameData, frameSize] =
|
||||||
|
generateFrame(EnableDebug, reinterpret_cast<const uint8_t *>(&request), sizeof(request));
|
||||||
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleCommunication::requestDebugSettings() {
|
||||||
|
DebugRequest request;
|
||||||
|
boost::json::object object;
|
||||||
|
object["command"] = "get_configurations";
|
||||||
|
auto text = boost::json::serialize(object);
|
||||||
|
std::strncpy(request.message, text.c_str(), sizeof(request.message));
|
||||||
|
request.length = htons(text.size());
|
||||||
|
|
||||||
|
auto [frameData, frameSize] =
|
||||||
|
generateFrame(EnableDebug, reinterpret_cast<const uint8_t *>(&request), sizeof(request));
|
||||||
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
m_serialPort->write(reinterpret_cast<const char *>(frameData), frameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,21 +240,25 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) {
|
|||||||
if (result == Success) {
|
if (result == Success) {
|
||||||
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
||||||
uint16_t userid = ntohs(info->userid);
|
uint16_t userid = ntohs(info->userid);
|
||||||
|
uint16_t elapsed = ntohs(info->elapsed);
|
||||||
LOG_CAT(info, GUI) << "用户ID: " << userid
|
LOG_CAT(info, GUI) << "用户ID: " << userid
|
||||||
<< ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username));
|
<< ", 用户名: " << std::string_view(reinterpret_cast<const char *>(info->username))
|
||||||
emit newVerifyResult(userid, reinterpret_cast<const char *>(info->username));
|
<< ", 耗时: " << elapsed << "ms";
|
||||||
|
emit newVerifyResult(userid, reinterpret_cast<const char *>(info->username), elapsed);
|
||||||
} else if (result == Failed4Timeout) {
|
} else if (result == Failed4Timeout) {
|
||||||
LOG_CAT(info, GUI) << "识别超时。";
|
LOG_CAT(info, GUI) << "识别超时。";
|
||||||
} else if (result == Rejected) {
|
} else if (result == Rejected) {
|
||||||
LOG_CAT(info, GUI) << "模组拒绝该命令。";
|
LOG_CAT(info, GUI) << "模组拒绝该命令。";
|
||||||
} else if (result == Failed4UnknownUser) {
|
} else if (result == Failed4UnknownUser) {
|
||||||
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
||||||
emit newVerifyResult(InvalidUserId, "");
|
uint16_t elapsed = ntohs(info->elapsed);
|
||||||
LOG_CAT(info, GUI) << "未录入用户";
|
emit newVerifyResult(InvalidUserId, "", elapsed);
|
||||||
|
LOG_CAT(info, GUI) << "未录入用户, 耗时: " << elapsed << "ms";
|
||||||
} else if (result == Failed4UnknownReason) {
|
} else if (result == Failed4UnknownReason) {
|
||||||
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
auto info = reinterpret_cast<const VerifyReply *>(data + 7);
|
||||||
emit newVerifyResult(InvalidUserId, "");
|
uint16_t elapsed = ntohs(info->elapsed);
|
||||||
LOG_CAT(info, GUI) << "未知错误";
|
emit newVerifyResult(InvalidUserId, "", elapsed);
|
||||||
|
LOG_CAT(info, GUI) << "未知错误, 耗时: " << elapsed << "ms";
|
||||||
} else {
|
} else {
|
||||||
LOG_CAT(info, GUI) << "未知错误(" << static_cast<int>(result) << ")。";
|
LOG_CAT(info, GUI) << "未知错误(" << static_cast<int>(result) << ")。";
|
||||||
}
|
}
|
||||||
@ -288,6 +348,29 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) {
|
|||||||
case UploadImageInfo: {
|
case UploadImageInfo: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case UploadImageData: {
|
||||||
|
auto info = reinterpret_cast<const UploadImageReply *>(data + 7);
|
||||||
|
if (result == Success) {
|
||||||
|
if (info->operation == 0) {
|
||||||
|
uint16_t userid = ntohs(info->userid);
|
||||||
|
LOG_CAT(info, GUI) << "图片下发注册成功,用户ID: " << userid;
|
||||||
|
emit newEnrollResult(userid);
|
||||||
|
} else if (info->operation == 1) {
|
||||||
|
auto result = reinterpret_cast<const UploadImageVerifyReply *>(info);
|
||||||
|
LOG_CAT(info, GUI) << "图片下发识别成功,用户ID: " << ntohs(result->userid) << ", 用户名: "
|
||||||
|
<< std::string_view(reinterpret_cast<const char *>(result->result.username))
|
||||||
|
<< ", 耗时: " << ntohs(result->result.elapsed) << "ms";
|
||||||
|
} else {
|
||||||
|
LOG(warning) << "unknown upload image operation: " << info->operation;
|
||||||
|
}
|
||||||
|
LOG_CAT(info, GUI) << Separator;
|
||||||
|
} else if (result == Needmore) {
|
||||||
|
} else {
|
||||||
|
LOG(info) << "upload image failed, operation: " << static_cast<uint16_t>(info->operation)
|
||||||
|
<< ", status: " << static_cast<uint16_t>(result);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case GetCurrentStatus: {
|
case GetCurrentStatus: {
|
||||||
LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
|
LOG_CAT(info, GUI) << "模组: " << protocolDataFormatString(data, size);
|
||||||
LOG_CAT(info, GUI) << "模组当前状态: " << static_cast<int>(data[7]);
|
LOG_CAT(info, GUI) << "模组当前状态: " << static_cast<int>(data[7]);
|
||||||
@ -333,7 +416,27 @@ void ModuleCommunication::processPackage(const uint8_t *data, uint16_t size) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EnableDebug: {
|
case EnableDebug: {
|
||||||
LOG(info) << "set moudle debug mode: " << (result == Success);
|
auto replyData = reinterpret_cast<const DebugReply *>(data + 7);
|
||||||
|
LOG(info) << "module debug: " << replyData->message;
|
||||||
|
std::error_code error;
|
||||||
|
auto replyValue = boost::json::parse(replyData->message, error);
|
||||||
|
if (error) {
|
||||||
|
LOG(error) << error.message();
|
||||||
|
} else {
|
||||||
|
auto &reply = replyValue.as_object();
|
||||||
|
if (reply.contains("command")) {
|
||||||
|
auto &command = reply.at("command");
|
||||||
|
if ((command == "get_configurations") || (command == "set_configurations")) {
|
||||||
|
if (reply.contains("cdc_log_enabled")) {
|
||||||
|
auto &cdcEnabled = reply.at("cdc_log_enabled").as_bool();
|
||||||
|
if (m_cdcDebugEnabled != cdcEnabled) {
|
||||||
|
m_cdcDebugEnabled = cdcEnabled;
|
||||||
|
emit cdcDebugEnabledChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -14,9 +14,9 @@ class ModuleCommunication : public QObject {
|
|||||||
static constexpr uint32_t UsernameSize = 32;
|
static constexpr uint32_t UsernameSize = 32;
|
||||||
static constexpr uint32_t VersionSize = 32;
|
static constexpr uint32_t VersionSize = 32;
|
||||||
static constexpr const char *Separator = "----------";
|
static constexpr const char *Separator = "----------";
|
||||||
Q_PROPERTY(MessageId currentMessageId READ currentMessageId NOTIFY currentMessageIdChanged)
|
|
||||||
Q_PROPERTY(QString verison MEMBER m_verison NOTIFY verisonChanged)
|
Q_PROPERTY(QString verison MEMBER m_verison NOTIFY verisonChanged)
|
||||||
Q_PROPERTY(int otaVerison MEMBER m_otaVerison NOTIFY verisonChanged)
|
Q_PROPERTY(int otaVerison MEMBER m_otaVerison NOTIFY verisonChanged)
|
||||||
|
Q_PROPERTY(bool cdcDebugEnabled MEMBER m_cdcDebugEnabled NOTIFY cdcDebugEnabledChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr static uint16_t VendorIdentifier = 0x3346;
|
constexpr static uint16_t VendorIdentifier = 0x3346;
|
||||||
@ -87,8 +87,10 @@ public:
|
|||||||
|
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
struct VerifyRequest {
|
struct VerifyRequest {
|
||||||
uint8_t reserved;
|
uint8_t save_image;
|
||||||
uint8_t timeout;
|
uint8_t timeout; // timeout, unit second, default 10s
|
||||||
|
uint16_t interval = 0;
|
||||||
|
uint8_t reserved[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PalmStateNote {
|
struct PalmStateNote {
|
||||||
@ -102,14 +104,16 @@ public:
|
|||||||
|
|
||||||
struct EnrollRequest {
|
struct EnrollRequest {
|
||||||
uint8_t username[32];
|
uint8_t username[32];
|
||||||
uint8_t reserved1[3];
|
uint8_t strictMode = 0;
|
||||||
|
uint8_t excludeMode = 0; // 掌静脉库里面是否已经存在 0:不进行验证 1:只进行比对 2:如果存在,则现在不予录入
|
||||||
|
uint8_t skipSave = 0;
|
||||||
uint8_t timeout;
|
uint8_t timeout;
|
||||||
uint8_t reserved2[4];
|
uint8_t reserved[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EnrollReply {
|
struct EnrollReply {
|
||||||
uint16_t userid;
|
uint16_t userid;
|
||||||
uint8_t reserved;
|
uint8_t operation;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PalmVeinInformation {
|
struct PalmVeinInformation {
|
||||||
@ -146,7 +150,7 @@ public:
|
|||||||
struct VerifyReply {
|
struct VerifyReply {
|
||||||
uint16_t userid;
|
uint16_t userid;
|
||||||
uint8_t username[UsernameSize]; // 32Bytes
|
uint8_t username[UsernameSize]; // 32Bytes
|
||||||
uint8_t reserved[2];
|
uint16_t elapsed; // 此时识别耗时时间
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VerifyExtendReply : public VerifyReply {
|
struct VerifyExtendReply : public VerifyReply {
|
||||||
@ -189,13 +193,22 @@ public:
|
|||||||
uint8_t userids[512]; // bit位
|
uint8_t userids[512]; // bit位
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DebugRequest {
|
||||||
|
uint16_t length;
|
||||||
|
char message[2000];
|
||||||
|
};
|
||||||
|
using DebugReply = DebugRequest;
|
||||||
|
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
explicit ModuleCommunication(QObject *parent = nullptr);
|
explicit ModuleCommunication(QObject *parent = nullptr);
|
||||||
bool open(const QString &portName, int baudRate);
|
bool open(const QString &portName, int baudRate);
|
||||||
void verify(uint8_t timeout);
|
void verify(uint8_t timeout);
|
||||||
Q_INVOKABLE void reset();
|
void verifyExtended(bool captureImage, uint8_t timeout);
|
||||||
|
void reset();
|
||||||
|
|
||||||
void enroll(const std::string &username, uint8_t timeout);
|
void enroll(const std::string &username, bool strictMode, uint8_t excludeMode, bool persistence, uint8_t timeout);
|
||||||
|
void enrollExtended(const std::string &username, bool strictMode, uint8_t excludeMode, bool persistence,
|
||||||
|
uint8_t timeout);
|
||||||
Q_INVOKABLE void deleteUser(uint16_t userid);
|
Q_INVOKABLE void deleteUser(uint16_t userid);
|
||||||
Q_INVOKABLE void deleteAll();
|
Q_INVOKABLE void deleteAll();
|
||||||
Q_INVOKABLE void requestUniqueId();
|
Q_INVOKABLE void requestUniqueId();
|
||||||
@ -206,6 +219,7 @@ public:
|
|||||||
void uploadImageData(uint32_t offset, const uint8_t *data, uint32_t size);
|
void uploadImageData(uint32_t offset, const uint8_t *data, uint32_t size);
|
||||||
Q_INVOKABLE void requestCurrentStatus();
|
Q_INVOKABLE void requestCurrentStatus();
|
||||||
Q_INVOKABLE void setDebugEnabled(bool enabled);
|
Q_INVOKABLE void setDebugEnabled(bool enabled);
|
||||||
|
void requestDebugSettings();
|
||||||
void startOta();
|
void startOta();
|
||||||
|
|
||||||
MessageId currentMessageId() const;
|
MessageId currentMessageId() const;
|
||||||
@ -217,7 +231,7 @@ signals:
|
|||||||
* @param username
|
* @param username
|
||||||
* @param elapsed ms毫秒
|
* @param elapsed ms毫秒
|
||||||
*/
|
*/
|
||||||
void newVerifyResult(uint16_t userid, const QString &username);
|
void newVerifyResult(uint16_t userid, const QString &username, uint16_t elapsed);
|
||||||
void newEnrollResult(uint16_t userid);
|
void newEnrollResult(uint16_t userid);
|
||||||
void newPalmFeature(const PalmFeature &feature);
|
void newPalmFeature(const PalmFeature &feature);
|
||||||
void newImageInfo(MessageId id, uint32_t size, const uint8_t *md5);
|
void newImageInfo(MessageId id, uint32_t size, const uint8_t *md5);
|
||||||
@ -227,6 +241,7 @@ signals:
|
|||||||
void commandFinished(MessageId messageId, MessageStatus status);
|
void commandFinished(MessageId messageId, MessageStatus status);
|
||||||
void currentMessageIdChanged();
|
void currentMessageIdChanged();
|
||||||
void verisonChanged();
|
void verisonChanged();
|
||||||
|
void cdcDebugEnabledChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void processPackage(const uint8_t *data, uint16_t size);
|
void processPackage(const uint8_t *data, uint16_t size);
|
||||||
@ -241,6 +256,7 @@ private:
|
|||||||
MessageId m_currentMessageId = ModuleCommunication::Idle;
|
MessageId m_currentMessageId = ModuleCommunication::Idle;
|
||||||
QString m_verison;
|
QString m_verison;
|
||||||
int m_otaVerison;
|
int m_otaVerison;
|
||||||
|
bool m_cdcDebugEnabled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
@ -32,7 +32,7 @@ bool VideoPlayer::open(const std::string &deviceName) {
|
|||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
constexpr auto format = "dshow";
|
constexpr auto format = "dshow";
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << "video=" << deviceName;
|
oss << "video=@device_pnp_" << deviceName;
|
||||||
auto device = oss.str();
|
auto device = oss.str();
|
||||||
#else
|
#else
|
||||||
constexpr auto format = "v4l2";
|
constexpr auto format = "v4l2";
|
||||||
@ -41,7 +41,10 @@ bool VideoPlayer::open(const std::string &deviceName) {
|
|||||||
auto inputFormat = av_find_input_format(format);
|
auto inputFormat = av_find_input_format(format);
|
||||||
|
|
||||||
AVDictionary *dictionary = nullptr;
|
AVDictionary *dictionary = nullptr;
|
||||||
|
// clang-format off
|
||||||
|
// ffplay -f dshow -i video="@device_pnp_\\?\usb#vid_3346&pid_0001&mi_00#6&6ff22b9&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
|
||||||
// ffmpeg -f dshow -list_options true -i video="UVC Camera"
|
// ffmpeg -f dshow -list_options true -i video="UVC Camera"
|
||||||
|
// clang-format on
|
||||||
av_dict_set(&dictionary, "video_size", "800*600", 0);
|
av_dict_set(&dictionary, "video_size", "800*600", 0);
|
||||||
|
|
||||||
int status = avformat_open_input(&m_formatContext, device.c_str(), inputFormat, &dictionary);
|
int status = avformat_open_input(&m_formatContext, device.c_str(), inputFormat, &dictionary);
|
||||||
|
@ -57,6 +57,7 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: uvcs
|
id: uvcs
|
||||||
|
textRole: "name"
|
||||||
enabled: !App.uvcOpened
|
enabled: !App.uvcOpened
|
||||||
implicitWidth: 150
|
implicitWidth: 150
|
||||||
}
|
}
|
||||||
@ -67,8 +68,7 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
text: App.uvcOpened ? "关闭" : "连接"
|
text: App.uvcOpened ? "关闭" : "连接"
|
||||||
onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC(
|
onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC(uvcs.currentValue.path)
|
||||||
uvcs.currentText)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,19 @@ RowLayout {
|
|||||||
id: enrollName
|
id: enrollName
|
||||||
implicitWidth: 100
|
implicitWidth: 100
|
||||||
}
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("严格模式")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
id: strictMode
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("互斥模式")
|
||||||
|
}
|
||||||
|
ComboBox {
|
||||||
|
id: excludeMode
|
||||||
|
model: ["无", "仅比对", "互斥"]
|
||||||
|
}
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("超时时间")
|
text: qsTr("超时时间")
|
||||||
}
|
}
|
||||||
@ -25,6 +38,21 @@ RowLayout {
|
|||||||
text: "30"
|
text: "30"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("持久化")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
id: persistence
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("保存图片")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
id: extendedMode
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
property bool enrolling: App.module ? (App.module.currentMessageId
|
property bool enrolling: App.module ? (App.module.currentMessageId
|
||||||
=== ModuleCommunication.EnrollSingle)
|
=== ModuleCommunication.EnrollSingle)
|
||||||
@ -33,8 +61,16 @@ RowLayout {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
if (enrolling) {
|
if (enrolling) {
|
||||||
App.module.reset()
|
App.module.reset()
|
||||||
|
} else if (extendedMode.checked) {
|
||||||
|
App.enrollExtended(enrollName.text, strictMode.checked,
|
||||||
|
excludeMode.currentIndex,
|
||||||
|
persistence.checked,
|
||||||
|
parseInt(enrollTimeout.text))
|
||||||
} else {
|
} else {
|
||||||
App.enroll(enrollName.text, parseInt(enrollTimeout.text))
|
App.enroll(enrollName.text, strictMode.checked,
|
||||||
|
excludeMode.currentIndex,
|
||||||
|
persistence.checked,
|
||||||
|
parseInt(enrollTimeout.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +78,9 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GroupBox {
|
GroupBox {
|
||||||
|
id: verifyBox
|
||||||
title: "识别用户"
|
title: "识别用户"
|
||||||
|
property bool verifying: (App.currentMessageId == ModuleCommunication.Verify) || (App.currentMessageId == ModuleCommunication.VerifyExtended)
|
||||||
GridLayout {
|
GridLayout {
|
||||||
columns: 2
|
columns: 2
|
||||||
Label {
|
Label {
|
||||||
@ -57,8 +95,20 @@ RowLayout {
|
|||||||
text: qsTr("持续识别")
|
text: qsTr("持续识别")
|
||||||
}
|
}
|
||||||
Switch {
|
Switch {
|
||||||
checked: App.persistenceMode
|
checked: (App.persistenceCommand == ModuleCommunication.Verify) || (App.persistenceCommand == ModuleCommunication.VerifyExtended)
|
||||||
onToggled: App.persistenceMode = !App.persistenceMode
|
onToggled: {
|
||||||
|
if (checked) {
|
||||||
|
App.persistenceCommand = extendedVerifyMode.checked ? ModuleCommunication.VerifyExtended : ModuleCommunication.Verify
|
||||||
|
} else {
|
||||||
|
App.persistenceCommand = ModuleCommunication.Idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("保存图片")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
id: extendedVerifyMode
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: qsTr("识别间隔(s)")
|
text: qsTr("识别间隔(s)")
|
||||||
@ -70,13 +120,13 @@ RowLayout {
|
|||||||
}
|
}
|
||||||
Item {}
|
Item {}
|
||||||
Button {
|
Button {
|
||||||
text: App.isVerifying ? "停止" : "识别"
|
text: verifyBox.verifying ? "停止" : "识别"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (App.isVerifying) {
|
if (verifyBox.verifying) {
|
||||||
App.module.reset()
|
App.resetModule()
|
||||||
} else {
|
} else {
|
||||||
App.persistenceVerifyInterval = parseInt(verifyIntetval.text)
|
App.persistenceVerifyInterval = parseInt(verifyIntetval.text)
|
||||||
App.verify(parseInt(verifyTimeout.text))
|
App.verify(extendedVerifyMode.checked, parseInt(verifyTimeout.text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Dialogs
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Analyser
|
import Analyser
|
||||||
|
|
||||||
@ -52,10 +54,83 @@ Item {
|
|||||||
text: "OTA升级"
|
text: "OTA升级"
|
||||||
onClicked: loader.active = true
|
onClicked: loader.active = true
|
||||||
}
|
}
|
||||||
|
Row {
|
||||||
|
Label {
|
||||||
|
text: qsTr("日志")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
id: debugMode
|
||||||
|
checked: App.module ? App.module.cdcDebugEnabled : false
|
||||||
|
onToggled: App.module.setDebugEnabled(debugMode.checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupBox {
|
||||||
|
id: imageBox
|
||||||
|
title: "图片注册"
|
||||||
|
visible: true
|
||||||
|
property bool imaging: (App.currentMessageId == ModuleCommunication.UploadImageInfo) || (App.currentMessageId == ModuleCommunication.UploadImageData)
|
||||||
|
GridLayout {
|
||||||
|
columns: 2
|
||||||
|
TextField {
|
||||||
|
id: imagePath
|
||||||
|
Layout.columnSpan: 2
|
||||||
|
implicitWidth: 200
|
||||||
|
placeholderText: "请选择图片"
|
||||||
|
onPressed: {
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("用户姓名")
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: imageEnrollName
|
||||||
|
implicitWidth: 100
|
||||||
|
text: "测试下发"
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("操作")
|
||||||
|
}
|
||||||
|
ComboBox {
|
||||||
|
id: imageMode
|
||||||
|
implicitWidth: 100
|
||||||
|
model: ["注册", "识别"]
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("循环")
|
||||||
|
}
|
||||||
|
Switch {
|
||||||
|
checked: App.persistenceCommand == ModuleCommunication.UploadImageInfo
|
||||||
|
onToggled: App.persistenceCommand = checked ? ModuleCommunication.UploadImageInfo : ModuleCommunication.Idle
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: imageBox.imaging ? "取消" : "执行"
|
||||||
|
onClicked: {
|
||||||
|
if (imageBox.imaging) {
|
||||||
|
App.resetModule()
|
||||||
|
} else {
|
||||||
|
App.uploadImage(imagePath.text, imageEnrollName.text, imageMode.currentIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
id: fileDialog
|
||||||
|
nameFilters: ["图片 (*.jpg *.yuv)"]
|
||||||
|
currentFolder: StandardPaths.standardLocations(StandardPaths.DesktopLocation)[0]
|
||||||
|
onAccepted: {
|
||||||
|
var fileUrl = fileDialog.selectedFile.toString()
|
||||||
|
var localFilePath = fileUrl.startsWith("file:///") ? fileUrl.substring(8) : fileUrl
|
||||||
|
imagePath.text = localFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: otaMouseArea
|
id: otaMouseArea
|
||||||
|
34
Analyser/qml/LogView.qml
Normal file
34
Analyser/qml/LogView.qml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import QtQuick
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: root
|
||||||
|
model: ListModel {
|
||||||
|
id: logModel
|
||||||
|
ListElement {
|
||||||
|
message: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate: TextEdit {
|
||||||
|
width: ListView.view.width
|
||||||
|
readOnly: true
|
||||||
|
text: message
|
||||||
|
selectByMouse: true
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function append(message){
|
||||||
|
let item = {
|
||||||
|
message: message
|
||||||
|
};
|
||||||
|
logModel.append(item)
|
||||||
|
if(root.atYEnd){
|
||||||
|
Qt.callLater(()=>{
|
||||||
|
root.positionViewAtEnd() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear(){
|
||||||
|
logModel.clear()
|
||||||
|
}
|
||||||
|
}
|
@ -54,14 +54,9 @@ Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Item {
|
Item {
|
||||||
ScrollView {
|
LogView {
|
||||||
id: view
|
|
||||||
anchors.fill: parent
|
|
||||||
TextArea {
|
|
||||||
id: logBrowser
|
id: logBrowser
|
||||||
readOnly: true
|
anchors.fill: parent
|
||||||
wrapMode: TextArea.WordWrap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
text: "清空"
|
text: "清空"
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import QtCore
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Dialogs
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Analyser
|
import Analyser
|
||||||
|
|
||||||
@ -40,18 +38,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
FileDialog {
|
|
||||||
id: fileDialog
|
|
||||||
nameFilters: ["图片 (*.jpg *.yuv)"]
|
|
||||||
currentFolder: StandardPaths.standardLocations(
|
|
||||||
StandardPaths.DesktopLocation)[0]
|
|
||||||
onAccepted: {
|
|
||||||
var fileUrl = fileDialog.selectedFile.toString()
|
|
||||||
var localFilePath = fileUrl.startsWith(
|
|
||||||
"file:///") ? fileUrl.substring(8) : fileUrl
|
|
||||||
imagePath.text = localFilePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.28)
|
cmake_minimum_required(VERSION 3.28)
|
||||||
|
|
||||||
project(SmartLockerTools VERSION 0.3 LANGUAGES C CXX)
|
project(SmartLockerTools VERSION 0.4 LANGUAGES C CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@ -12,6 +12,11 @@ if(WIN32)
|
|||||||
add_compile_definitions(
|
add_compile_definitions(
|
||||||
BOOST_USE_WINAPI_VERSION=BOOST_WINAPI_VERSION_WIN10
|
BOOST_USE_WINAPI_VERSION=BOOST_WINAPI_VERSION_WIN10
|
||||||
)
|
)
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
set(MBEDTLS_ROOT ${Libraries_ROOT}/mbedtls-3.6.2_msvc2022_64bit_release)
|
||||||
|
else()
|
||||||
|
set(MBEDTLS_ROOT ${Libraries_ROOT}/mbedtls-3.6.2_msvc2022_64bit_debug)
|
||||||
|
endif()
|
||||||
else()
|
else()
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND sh -c "echo $HOME"
|
COMMAND sh -c "echo $HOME"
|
||||||
@ -21,6 +26,7 @@ else()
|
|||||||
set(Libraries_ROOT /opt/Libraries)
|
set(Libraries_ROOT /opt/Libraries)
|
||||||
set(BOOST_ROOT ${Libraries_ROOT}/boost_1_86_0)
|
set(BOOST_ROOT ${Libraries_ROOT}/boost_1_86_0)
|
||||||
set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include)
|
set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include)
|
||||||
|
set(MBEDTLS_ROOT ${Libraries_ROOT}/mbedtls-3.6.2)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(Boost_USE_STATIC_LIBS OFF)
|
option(Boost_USE_STATIC_LIBS OFF)
|
||||||
@ -34,6 +40,9 @@ set(FFmpeg_ROOT ${Libraries_ROOT}/ffmpeg-7.0.2-full_build-shared)
|
|||||||
set(FFmpeg_INCLUDE_DIR ${FFmpeg_ROOT}/include)
|
set(FFmpeg_INCLUDE_DIR ${FFmpeg_ROOT}/include)
|
||||||
set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib)
|
set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib)
|
||||||
|
|
||||||
|
set(MBEDTLS_INCLUDE_DIR ${MBEDTLS_ROOT}/include)
|
||||||
|
set(MBEDTLS_LIBRARY_DIRS ${MBEDTLS_ROOT}/lib)
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND D:/msys64/usr/bin/git rev-parse --short HEAD
|
COMMAND D:/msys64/usr/bin/git rev-parse --short HEAD
|
||||||
OUTPUT_VARIABLE GIT_COMMIT_ID
|
OUTPUT_VARIABLE GIT_COMMIT_ID
|
||||||
@ -43,7 +52,7 @@ execute_process(
|
|||||||
include(CPack)
|
include(CPack)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(Kylin
|
FetchContent_Declare(Kylin
|
||||||
GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git
|
GIT_REPOSITORY https://amass.fun/gitea/amass/Kylin.git
|
||||||
)
|
)
|
||||||
set(KYLIN_WITH_FLUENT ON)
|
set(KYLIN_WITH_FLUENT ON)
|
||||||
FetchContent_MakeAvailable(Kylin)
|
FetchContent_MakeAvailable(Kylin)
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
set(APPLICATION_NAME "掌静脉升级工具")
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
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)
|
||||||
|
|
||||||
|
configure_file(Configuration.h.in Configuration.h)
|
||||||
|
|
||||||
set(PROJECT_SOURCES OtaUpdate.rc
|
set(PROJECT_SOURCES OtaUpdate.rc
|
||||||
main.cpp
|
main.cpp
|
||||||
Widget.cpp
|
Widget.cpp
|
||||||
@ -15,6 +19,10 @@ qt_add_executable(SmartLockerUpdater
|
|||||||
${PROJECT_SOURCES}
|
${PROJECT_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(SmartLockerUpdater
|
||||||
|
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(SmartLockerUpdater
|
target_link_libraries(SmartLockerUpdater
|
||||||
PRIVATE Peripheral
|
PRIVATE Peripheral
|
||||||
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
|
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
|
||||||
|
3
OtaUpdate/Configuration.h.in
Normal file
3
OtaUpdate/Configuration.h.in
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#define APPLICATION_NAME "@APPLICATION_NAME@"
|
||||||
|
#define GIT_COMMIT_ID "@GIT_COMMIT_ID@"
|
||||||
|
#define APP_VERSION "@PROJECT_VERSION@"
|
@ -1,4 +1,5 @@
|
|||||||
#include "BoostLog.h"
|
#include "BoostLog.h"
|
||||||
|
#include "Configuration.h"
|
||||||
#include "Widget.h"
|
#include "Widget.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
@ -7,11 +8,14 @@ int main(int argc, char *argv[]) {
|
|||||||
boost::log::initialize("logs/app");
|
boost::log::initialize("logs/app");
|
||||||
|
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
a.setApplicationName(APPLICATION_NAME);
|
||||||
|
a.setApplicationVersion(QString("v%1_%2 build: %3 %4").arg(APP_VERSION, GIT_COMMIT_ID, __DATE__, __TIME__));
|
||||||
|
|
||||||
QFont font;
|
QFont font;
|
||||||
font.setPointSize(16);
|
font.setPointSize(16);
|
||||||
a.setFont(font);
|
a.setFont(font);
|
||||||
Widget w;
|
Widget w;
|
||||||
w.setWindowTitle("L015模组升级工具");
|
w.setWindowTitle(QString("%1 %2").arg(a.applicationName()).arg(a.applicationVersion()));
|
||||||
w.setMinimumWidth(520);
|
w.setMinimumWidth(520);
|
||||||
w.setMinimumHeight(100);
|
w.setMinimumHeight(100);
|
||||||
w.show();
|
w.show();
|
||||||
|
@ -15,6 +15,6 @@ target_include_directories(Peripheral
|
|||||||
target_link_libraries(Peripheral
|
target_link_libraries(Peripheral
|
||||||
PUBLIC Universal
|
PUBLIC Universal
|
||||||
PRIVATE Encrypt
|
PRIVATE Encrypt
|
||||||
$<$<PLATFORM_ID:Windows>:Mfreadwrite Mf mfplat mfuuid>
|
$<$<PLATFORM_ID:Windows>:Mfreadwrite Mf mfplat mfuuid strmiids comsuppw>
|
||||||
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
|
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
#include "DeviceDiscovery.h"
|
#include "DeviceDiscovery.h"
|
||||||
#include "BoostLog.h"
|
#include "BoostLog.h"
|
||||||
#include "StringUtility.h"
|
|
||||||
#include <boost/scope/scope_exit.hpp>
|
#include <boost/scope/scope_exit.hpp>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
#include <comutil.h>
|
||||||
|
#include <dshow.h>
|
||||||
#include <mfapi.h>
|
#include <mfapi.h>
|
||||||
#include <mfcaptureengine.h>
|
#include <mfcaptureengine.h>
|
||||||
|
#include <strmif.h>
|
||||||
#else
|
#else
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <linux/videodev2.h>
|
#include <linux/videodev2.h>
|
||||||
@ -27,6 +29,23 @@ DeviceDiscovery::DeviceDiscovery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
|
||||||
|
void DeleteMediaType(AM_MEDIA_TYPE *pmt) {
|
||||||
|
if (pmt != nullptr) {
|
||||||
|
if (pmt->cbFormat != 0) {
|
||||||
|
CoTaskMemFree((PVOID)pmt->pbFormat);
|
||||||
|
pmt->cbFormat = 0;
|
||||||
|
pmt->pbFormat = nullptr;
|
||||||
|
}
|
||||||
|
if (pmt->pUnk != nullptr) {
|
||||||
|
// Unecessary because pUnk should not be used, but safest.
|
||||||
|
pmt->pUnk->Release();
|
||||||
|
pmt->pUnk = nullptr;
|
||||||
|
}
|
||||||
|
CoTaskMemFree((PVOID)pmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static std::string deviceName(IMFActivate *device) {
|
static std::string deviceName(IMFActivate *device) {
|
||||||
std::string ret;
|
std::string ret;
|
||||||
WCHAR *friendlyName = nullptr;
|
WCHAR *friendlyName = nullptr;
|
||||||
@ -34,7 +53,7 @@ static std::string deviceName(IMFActivate *device) {
|
|||||||
auto result = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength);
|
auto result = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength);
|
||||||
|
|
||||||
if (SUCCEEDED(result)) {
|
if (SUCCEEDED(result)) {
|
||||||
ret = Amass::StringUtility::wstringToString(std::wstring(friendlyName, nameLength));
|
ret = _com_util::ConvertBSTRToString(friendlyName);
|
||||||
}
|
}
|
||||||
if (friendlyName != nullptr) {
|
if (friendlyName != nullptr) {
|
||||||
CoTaskMemFree(friendlyName);
|
CoTaskMemFree(friendlyName);
|
||||||
@ -128,36 +147,108 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
|
|||||||
&llTimeStamp, &pSample);
|
&llTimeStamp, &pSample);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> DeviceDiscovery::devices() {
|
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() {
|
||||||
std::vector<std::string> ret;
|
std::vector<Device> devices;
|
||||||
IMFAttributes *attributes = nullptr;
|
ICreateDevEnum *pDevEnum = nullptr;
|
||||||
boost::scope::scope_exit guard([&attributes] { SafeRelease(&attributes); });
|
IEnumMoniker *pEnum = nullptr;
|
||||||
auto result = MFCreateAttributes(&attributes, 1);
|
|
||||||
if (FAILED(result)) {
|
HRESULT hr =
|
||||||
return ret;
|
CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
|
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
|
||||||
if (FAILED(result)) {
|
if (hr != S_OK) {
|
||||||
return ret;
|
pDevEnum->Release();
|
||||||
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
UINT32 count;
|
IMoniker *pMoniker = nullptr;
|
||||||
IMFActivate **devices = nullptr;
|
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) {
|
||||||
result = MFEnumDeviceSources(attributes, &devices, &count);
|
Device info;
|
||||||
if (FAILED(result)) {
|
GetDeviceNames(pMoniker, info);
|
||||||
return ret;
|
|
||||||
|
IBaseFilter *pFilter;
|
||||||
|
hr = pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void **)&pFilter);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
IEnumPins *pEnumPins;
|
||||||
|
hr = pFilter->EnumPins(&pEnumPins);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
IPin *pPin;
|
||||||
|
while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) {
|
||||||
|
PIN_DIRECTION pinDir;
|
||||||
|
pPin->QueryDirection(&pinDir);
|
||||||
|
if (pinDir == PINDIR_OUTPUT) {
|
||||||
|
GetSupportedResolutions(pPin, info.resolutions);
|
||||||
|
}
|
||||||
|
pPin->Release();
|
||||||
|
}
|
||||||
|
pEnumPins->Release();
|
||||||
|
}
|
||||||
|
pFilter->Release();
|
||||||
|
}
|
||||||
|
devices.push_back(info);
|
||||||
|
pMoniker->Release();
|
||||||
|
}
|
||||||
|
pEnum->Release();
|
||||||
|
pDevEnum->Release();
|
||||||
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0) {
|
bool DeviceDiscovery::SetResolution(const std::string &deviceName, int width, int height) {
|
||||||
return ret;
|
ICreateDevEnum *pDevEnum = nullptr;
|
||||||
|
IEnumMoniker *pEnum = nullptr;
|
||||||
|
|
||||||
|
HRESULT hr =
|
||||||
|
CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum);
|
||||||
|
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
|
||||||
auto name = ::deviceName(devices[i]);
|
if (hr != S_OK) {
|
||||||
ret.push_back(name);
|
pDevEnum->Release();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
IMoniker *pMoniker = nullptr;
|
||||||
|
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) {
|
||||||
|
IPropertyBag *pPropBag;
|
||||||
|
hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
VARIANT varName;
|
||||||
|
VariantInit(&varName);
|
||||||
|
hr = pPropBag->Read(L"FriendlyName", &varName, nullptr);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
std::string currentName = _com_util::ConvertBSTRToString(varName.bstrVal);
|
||||||
|
if (currentName == deviceName) {
|
||||||
|
IBaseFilter *pFilter;
|
||||||
|
hr = pMoniker->BindToObject(nullptr, nullptr, IID_IBaseFilter, (void **)&pFilter);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
if (SetPinResolution(pFilter, width, height)) {
|
||||||
|
pFilter->Release();
|
||||||
|
VariantClear(&varName);
|
||||||
|
pPropBag->Release();
|
||||||
|
pMoniker->Release();
|
||||||
|
pEnum->Release();
|
||||||
|
pDevEnum->Release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
pFilter->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VariantClear(&varName);
|
||||||
|
pPropBag->Release();
|
||||||
|
}
|
||||||
|
pMoniker->Release();
|
||||||
|
}
|
||||||
|
pEnum->Release();
|
||||||
|
pDevEnum->Release();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr<Device> &source) {
|
DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr<Device> &source) {
|
||||||
@ -182,6 +273,89 @@ DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::share
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeviceDiscovery::GetDeviceNames(IMoniker *pMoniker, Device &info) {
|
||||||
|
IPropertyBag *pPropBag;
|
||||||
|
HRESULT hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
VARIANT varName;
|
||||||
|
VariantInit(&varName);
|
||||||
|
hr = pPropBag->Read(L"FriendlyName", &varName, nullptr);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
|
||||||
|
info.friendlyName = _com_util::ConvertBSTRToString(varName.bstrVal);
|
||||||
|
}
|
||||||
|
VariantClear(&varName);
|
||||||
|
|
||||||
|
hr = pPropBag->Read(L"DevicePath", &varName, nullptr);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
info.alternativeName = _com_util::ConvertBSTRToString(varName.bstrVal);
|
||||||
|
}
|
||||||
|
VariantClear(&varName);
|
||||||
|
|
||||||
|
pPropBag->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceDiscovery::GetSupportedResolutions(IPin *pPin, std::vector<std::pair<int, int>> &resolutions) {
|
||||||
|
IEnumMediaTypes *pEnumMediaTypes = nullptr;
|
||||||
|
HRESULT hr = pPin->EnumMediaTypes(&pEnumMediaTypes);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AM_MEDIA_TYPE *pMediaType = nullptr;
|
||||||
|
while (pEnumMediaTypes->Next(1, &pMediaType, nullptr) == S_OK) {
|
||||||
|
if (pMediaType->formattype == FORMAT_VideoInfo) {
|
||||||
|
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER *>(pMediaType->pbFormat);
|
||||||
|
int width = pVih->bmiHeader.biWidth;
|
||||||
|
int height = pVih->bmiHeader.biHeight;
|
||||||
|
resolutions.emplace_back(width, height);
|
||||||
|
}
|
||||||
|
DeleteMediaType(pMediaType);
|
||||||
|
}
|
||||||
|
pEnumMediaTypes->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceDiscovery::SetPinResolution(IBaseFilter *pFilter, int width, int height) {
|
||||||
|
IEnumPins *pEnumPins;
|
||||||
|
HRESULT hr = pFilter->EnumPins(&pEnumPins);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPin *pPin;
|
||||||
|
bool success = false;
|
||||||
|
while (pEnumPins->Next(1, &pPin, nullptr) == S_OK) {
|
||||||
|
PIN_DIRECTION pinDir;
|
||||||
|
pPin->QueryDirection(&pinDir);
|
||||||
|
if (pinDir == PINDIR_OUTPUT) {
|
||||||
|
IEnumMediaTypes *pEnumMediaTypes = nullptr;
|
||||||
|
hr = pPin->EnumMediaTypes(&pEnumMediaTypes);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
AM_MEDIA_TYPE *pMediaType = nullptr;
|
||||||
|
while (pEnumMediaTypes->Next(1, &pMediaType, nullptr) == S_OK) {
|
||||||
|
if (pMediaType->formattype == FORMAT_VideoInfo) {
|
||||||
|
VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER *>(pMediaType->pbFormat);
|
||||||
|
if (pVih->bmiHeader.biWidth == width && pVih->bmiHeader.biHeight == height) {
|
||||||
|
hr = pPin->QueryAccept(pMediaType);
|
||||||
|
if (hr == S_OK) {
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeleteMediaType(pMediaType);
|
||||||
|
if (success) break;
|
||||||
|
}
|
||||||
|
pEnumMediaTypes->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pPin->Release();
|
||||||
|
if (success) break;
|
||||||
|
}
|
||||||
|
pEnumPins->Release();
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) {
|
DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) {
|
||||||
source->AddRef();
|
source->AddRef();
|
||||||
auto result = MFCreateSourceReaderFromMediaSource(source, nullptr, &reader);
|
auto result = MFCreateSourceReaderFromMediaSource(source, nullptr, &reader);
|
||||||
@ -190,8 +364,8 @@ DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
std::vector<std::string> DeviceDiscovery::devices() {
|
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() {
|
||||||
std::vector<std::string> ret;
|
std::vector<Device> ret;
|
||||||
for (const auto &entry : std::filesystem::directory_iterator("/dev")) {
|
for (const auto &entry : std::filesystem::directory_iterator("/dev")) {
|
||||||
if (entry.is_character_file() && entry.path().string().find("video") != std::string::npos) {
|
if (entry.is_character_file() && entry.path().string().find("video") != std::string::npos) {
|
||||||
int fd = open(entry.path().c_str(), O_RDWR);
|
int fd = open(entry.path().c_str(), O_RDWR);
|
||||||
@ -203,7 +377,10 @@ std::vector<std::string> DeviceDiscovery::devices() {
|
|||||||
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
|
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) {
|
||||||
if ((strstr(reinterpret_cast<const char *>(cap.card), DeviceName) != nullptr) &&
|
if ((strstr(reinterpret_cast<const char *>(cap.card), DeviceName) != nullptr) &&
|
||||||
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
|
||||||
ret.push_back(entry.path().string());
|
Device device;
|
||||||
|
device.friendlyName = entry.path().string();
|
||||||
|
device.alternativeName = entry.path().string();
|
||||||
|
ret.push_back(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
@ -240,7 +417,7 @@ static std::string find_video_device_by_name(const std::string &targetName) {
|
|||||||
|
|
||||||
std::shared_ptr<DeviceDiscovery::Device> DeviceDiscovery::find(const std::string &deviceName, std::error_code &error) {
|
std::shared_ptr<DeviceDiscovery::Device> DeviceDiscovery::find(const std::string &deviceName, std::error_code &error) {
|
||||||
auto ret = std::make_shared<Device>();
|
auto ret = std::make_shared<Device>();
|
||||||
ret->name = find_video_device_by_name(deviceName);
|
ret->friendlyName = find_video_device_by_name(deviceName);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,9 +440,9 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
|
|||||||
} else {
|
} else {
|
||||||
LOG(info) << "found ota specific resolution: " << OtaSpecificWidth << "x" << otaSpecificHeight;
|
LOG(info) << "found ota specific resolution: " << OtaSpecificWidth << "x" << otaSpecificHeight;
|
||||||
}
|
}
|
||||||
int fd = open(device->name.c_str(), O_RDWR);
|
int fd = open(device->friendlyName.c_str(), O_RDWR);
|
||||||
if (fd <= 0) {
|
if (fd <= 0) {
|
||||||
LOG(error) << "Failed to open device " << device->name;
|
LOG(error) << "Failed to open device " << device->friendlyName;
|
||||||
} else {
|
} else {
|
||||||
struct v4l2_format format;
|
struct v4l2_format format;
|
||||||
memset(&format, 0, sizeof(format));
|
memset(&format, 0, sizeof(format));
|
||||||
@ -343,9 +520,9 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
|
|||||||
|
|
||||||
DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr<Device> &source) {
|
DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::shared_ptr<Device> &source) {
|
||||||
Resolutions ret;
|
Resolutions ret;
|
||||||
int fd = open(source->name.c_str(), O_RDWR);
|
int fd = open(source->friendlyName.c_str(), O_RDWR);
|
||||||
if (fd <= 0) {
|
if (fd <= 0) {
|
||||||
LOG(error) << "Failed to open device " << source->name;
|
LOG(error) << "Failed to open device " << source->friendlyName;
|
||||||
} else {
|
} else {
|
||||||
struct v4l2_fmtdesc fmt;
|
struct v4l2_fmtdesc fmt;
|
||||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <mfidl.h>
|
#include <mfidl.h>
|
||||||
#include <mfreadwrite.h>
|
#include <mfreadwrite.h>
|
||||||
|
|
||||||
|
class IPin;
|
||||||
|
class IBaseFilter;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class DeviceDiscovery {
|
class DeviceDiscovery {
|
||||||
@ -15,25 +18,33 @@ class DeviceDiscovery {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr static auto DeviceName = "UVC Camera";
|
constexpr static auto DeviceName = "UVC Camera";
|
||||||
|
using Resolution = std::pair<int32_t, int32_t>;
|
||||||
|
using Resolutions = std::vector<Resolution>;
|
||||||
struct Device {
|
struct Device {
|
||||||
|
Device() = default;
|
||||||
|
std::string friendlyName;
|
||||||
|
std::string alternativeName;
|
||||||
|
Resolutions resolutions;
|
||||||
~Device();
|
~Device();
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
Device(IMFMediaSource *source);
|
Device(IMFMediaSource *source);
|
||||||
IMFMediaSource *source = nullptr;
|
IMFMediaSource *source = nullptr;
|
||||||
IMFSourceReader *reader = nullptr;
|
IMFSourceReader *reader = nullptr;
|
||||||
#else
|
|
||||||
std::string name;
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
using Resolution = std::pair<int32_t, int32_t>;
|
|
||||||
using Resolutions = std::vector<Resolution>;
|
|
||||||
DeviceDiscovery();
|
DeviceDiscovery();
|
||||||
std::shared_ptr<Device> find(const std::string &deviceName, std::error_code &error);
|
std::shared_ptr<Device> find(const std::string &deviceName, std::error_code &error);
|
||||||
void enterOtaMode(const std::shared_ptr<Device> &device, std::error_code &error);
|
void enterOtaMode(const std::shared_ptr<Device> &device, std::error_code &error);
|
||||||
std::vector<std::string> devices();
|
std::vector<Device> devices();
|
||||||
|
bool SetResolution(const std::string &deviceName, int width, int height);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Resolutions deviceResolutions(const std::shared_ptr<Device> &source);
|
Resolutions deviceResolutions(const std::shared_ptr<Device> &source);
|
||||||
|
#ifdef WIN32
|
||||||
|
void GetDeviceNames(IMoniker *pMoniker, Device &info);
|
||||||
|
void GetSupportedResolutions(IPin *pPin, std::vector<std::pair<int, int>> &resolutions);
|
||||||
|
bool SetPinResolution(IBaseFilter *pFilter, int width, int height);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
#endif // __DEVICEDISCOVERY_H__
|
#endif // __DEVICEDISCOVERY_H__
|
||||||
|
11
Readme.md
11
Readme.md
@ -26,6 +26,10 @@ frame_sync_task() // L015 V200 __DUAL_SNS_FAKE__ 双sensor活体
|
|||||||
|
|
||||||
## 门锁开发环境搭建
|
## 门锁开发环境搭建
|
||||||
|
|
||||||
|
smart_doorbell 文件夹、cv181xc_qfn。
|
||||||
|
CONSOLE_UART_IDX
|
||||||
|
业务串口:2
|
||||||
|
|
||||||
安装如下 python 环境:
|
安装如下 python 环境:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -61,12 +65,15 @@ HOST_TOOLS := /opt/Xuantie-900-gcc-elf-newlib-x86_64-V2.6.1/bin
|
|||||||
|
|
||||||
# 编译OTA固件,11为OTA版本号,这个版本号只做固件文件名显示。
|
# 编译OTA固件,11为OTA版本号,这个版本号只做固件文件名显示。
|
||||||
# 实际的版本设置在 cv181x_alios/solutions/smart_doorbell/package.yaml.L015_V200R002
|
# 实际的版本设置在 cv181x_alios/solutions/smart_doorbell/package.yaml.L015_V200R002
|
||||||
./rebuild-app-ota.sh y L015 V200 R002 05
|
|
||||||
600X800
|
600X800
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# cv181x_alios/rebuild-app.sh 、cv181x_alios/rebuild-app-ota.sh 这两个文件需要改成 #!/bin/bash, 否则编译不过
|
||||||
|
|
||||||
docker run -it --rm --user 1000:1000 -v /opt:/opt -v $(pwd)/..:$(pwd)/.. -w $(pwd) registry.cn-shenzhen.aliyuncs.com/amass_toolset/yoctools:22.04 ./rebuild-app.sh y L015 V200 R002
|
docker run -it --rm --user 1000:1000 -v /opt:/opt -v $(pwd)/..:$(pwd)/.. -w $(pwd) registry.cn-shenzhen.aliyuncs.com/amass_toolset/yoctools:22.04 ./rebuild-app.sh y L015 V200 R002
|
||||||
|
|
||||||
docker run -it --rm --user 1000:1000 -v /opt:/opt -v $(pwd)/..:$(pwd)/.. -w $(pwd) registry.cn-shenzhen.aliyuncs.com/amass_toolset/yoctools:22.04 ./rebuild-app-ota.sh y L015 V200 R002 06
|
docker run -it --rm --user 1000:1000 -v /opt:/opt -v $(pwd)/..:$(pwd)/.. -w $(pwd) registry.cn-shenzhen.aliyuncs.com/amass_toolset/yoctools:22.04 ./rebuild-app-ota.sh y L015 V200 R002 09
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ BOOST_AUTO_TEST_CASE(EnumDevice) {
|
|||||||
auto device = discovery.find("UVC Camera", error);
|
auto device = discovery.find("UVC Camera", error);
|
||||||
auto devices = discovery.devices();
|
auto devices = discovery.devices();
|
||||||
for (int i = 0; i < devices.size(); i++) {
|
for (int i = 0; i < devices.size(); i++) {
|
||||||
LOG(info) << "device[" << i << "] " << devices.at(i);
|
LOG(info) << "device[" << i << "] " << devices.at(i).friendlyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
discovery.enterOtaMode(device, error);
|
discovery.enterOtaMode(device, error);
|
||||||
|
BIN
resources/7za.exe
Normal file
BIN
resources/7za.exe
Normal file
Binary file not shown.
26
resources/L015掌静脉模组集成拓扑图.md
Normal file
26
resources/L015掌静脉模组集成拓扑图.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
掌静脉模组支持掌静脉注册,识别功能,同时也提供通过图片注册掌静脉功能。
|
||||||
|
|
||||||
|
常见有两种应用场景:单模组使用,多设备集群分发注册识别。
|
||||||
|
|
||||||
|
## 单模组使用
|
||||||
|
|
||||||
|
![](./local_use.svg)
|
||||||
|
|
||||||
|
在此应用场景下,【门禁面板机】需要完成和【掌静脉模组】通信协议的对接。
|
||||||
|
|
||||||
|
当注册用户时,门禁面板机向掌静脉模组发送注册命令使模块进入【注册模式】,并提示【用户】将【手掌】置于掌静脉模组上方进行注册,注册成功后,掌静脉模组将会上报一个【唯一标识ID】至门禁面板机,面板机需要将该ID与用户进行绑定。
|
||||||
|
|
||||||
|
注册成功后,门禁面板机可发送指令使模组切换至【识别模式】,当用户将手置于掌静脉模组上方,模组会和之前注册的掌静脉底库进行比对,比对结束之后,将会向门禁面板机上报ID以标识此次识别结果,此时面板机可依靠此ID和之前记录的注册ID进行比对,如果ID相同则表示匹配上该用户。
|
||||||
|
|
||||||
|
## 多模组集群使用
|
||||||
|
|
||||||
|
![](muti_use.svg)
|
||||||
|
|
||||||
|
在此应用场景下,【门禁面板机】需要完成和【掌静脉模组】通行协议的对接,且需要服务器进行掌静脉注册图片管理及分发。
|
||||||
|
|
||||||
|
考虑到常见的门禁通行场景,掌静脉模组支持注册时获取掌静脉注册图片,然后再使用该图片进行掌静脉注册。
|
||||||
|
|
||||||
|
通过这种方式,用户在注册时,可以通掌静脉注册机获取掌静脉注册图片,然后在将掌静脉注册图片发送至服务器,服务器接收到图片之后,再将图片下发至各个门禁面板机以图片方式进行注册。这样就可以达到一端注册,多端使用的效果。
|
||||||
|
|
||||||
|
|
||||||
|
|
403
resources/L015掌静脉识别模组串口通信协议 - 简化版.md
Normal file
403
resources/L015掌静脉识别模组串口通信协议 - 简化版.md
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
## 1 串口配置
|
||||||
|
|
||||||
|
目前模组支持两种通信接口:UART,USB(CDC)。其中
|
||||||
|
|
||||||
|
UART:
|
||||||
|
|
||||||
|
- 波特率:115200
|
||||||
|
- 数据位:8
|
||||||
|
- 停止位:1
|
||||||
|
- 奇偶检验:无
|
||||||
|
- 流控制:无
|
||||||
|
|
||||||
|
USB(CDC):
|
||||||
|
|
||||||
|
- 波特率:2000000
|
||||||
|
- 数据位:8
|
||||||
|
- 停止位:1
|
||||||
|
- 奇偶检验:无
|
||||||
|
- 流控制:无
|
||||||
|
|
||||||
|
## 2 消息格式
|
||||||
|
|
||||||
|
主控和模块通讯的基本格式如下表所示,字节序为 **大端字节序(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运算 |
|
||||||
|
|
||||||
|
## 3 消息列表
|
||||||
|
|
||||||
|
| MsgID | Code | 说明 |
|
||||||
|
| ----------------- | ---- | ------------------------------------------------------------ |
|
||||||
|
| MID_REPLY | 0x00 | 模组对主控发送出的命令的应答,对于主控下发的每条命令,模组最终都会使用 MID_REPLY 进行结果应答上报 |
|
||||||
|
| MID_NOTE | 0x01 | 摸组主动上报给主控的信息,根据 NID 判断消息类型和对应的 Data 结构(详细内容见下文) |
|
||||||
|
| MID_RESET | 0x10 | 主控下发,用于打断模组当前执行的任务 |
|
||||||
|
| MID_VERIFY | 0x12 | 掌静脉识别比对 |
|
||||||
|
| MID_ENROLL_SINGLE | 0x1D | 掌静脉录入 |
|
||||||
|
| MID_DELUSER | 0x20 | 删除一个已录入的掌静脉 |
|
||||||
|
| MID_DELALL | 0x21 | 删除所有已录入的掌静脉 |
|
||||||
|
| MID_MODULE_ID | 0xAC | 获取模组ID,唯一标识 |
|
||||||
|
|
||||||
|
### 3.1 设备初始化完成
|
||||||
|
|
||||||
|
模组上电初始化完成后,会通过串口向主控发送一条 NID 为 NID_READY 的 MID_NOTE 消息: 0xEF 0xAA 0x01 0x00 0x01 0x00 0x00。(消息详细解释见 `模组状态上报(MID_NOTE)`)。
|
||||||
|
|
||||||
|
> 该消息仅支持使用串口连接,使用CDC不会上报此消息
|
||||||
|
|
||||||
|
主控在接收到 MID_NOTE 信息后,可以和模组进行指令交互。
|
||||||
|
|
||||||
|
### 3.2 模组复位(MID_RESET)
|
||||||
|
|
||||||
|
模组同一时刻,只能执行一个任务,当模组在执行耗时长的任务时(例如掌静脉识别 MID_VERIFY),主控可以向模组下发 MID_RESET 打断取消当前任务,进而再去执行其它任务。
|
||||||
|
|
||||||
|
该指令无需携带参数。
|
||||||
|
|
||||||
|
指令执行结束后,通过消息 MID_REPLY 返回结果:
|
||||||
|
|
||||||
|
- 0x00(MR_SUCCESS):复位成功
|
||||||
|
- 0x05(MR_FAILED4_UNKNOWNREASON):未知错误
|
||||||
|
|
||||||
|
### 3.3 掌静脉识别(MID_VERIFY)
|
||||||
|
|
||||||
|
主控下发该指令给模组,模组开始检测摄像头图像中的掌静脉,并和底库中的掌静脉进行比对。指令下发携带的参数 msg_verify_data 定义如下:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
struct msg_verify_data {
|
||||||
|
uint8_t reserved1;
|
||||||
|
uint8_t timeout;
|
||||||
|
uint8_t reserved2[6];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- reserved1:保留字段,暂未使用。需设置为 0x00。
|
||||||
|
- timeout:识别超时时间,默认为10s,用户可以自行设置(最大不超过255s)。**主控等待模组录入应答的超时时间应大于此参数设置值。**
|
||||||
|
- reserved2:保留字段,暂未使用。需设置为 0x00。
|
||||||
|
|
||||||
|
主控下发消息格式如下:
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<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">8 bytes</td>
|
||||||
|
<td>1 byte</td>
|
||||||
|
</tr>
|
||||||
|
<tr align="center">
|
||||||
|
<td>0xEF 0xAA</td>
|
||||||
|
<td>MID_VERIFY<br/>(0x12)</td>
|
||||||
|
<td>0x08</td>
|
||||||
|
<td>reserved1<br/>(1 byte)</td>
|
||||||
|
<td>timeout<br/>(1 byte)</td>
|
||||||
|
<td>reserved2<br/>(6 bytes)</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 reserved[2];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- user_id:识别成功的用户 ID
|
||||||
|
- username:识别成功的用户名字
|
||||||
|
- reserved:保留字段,暂未使用。需设置为 0x00。
|
||||||
|
|
||||||
|
模组识别成功上报消息格式如下:
|
||||||
|
|
||||||
|
<table align="center">
|
||||||
|
<tr align="center">
|
||||||
|
<th rowspan="3">SyncWord</th>
|
||||||
|
<th rowspan="3">MsgID</th>
|
||||||
|
<th rowspan="3">DataSize</th>
|
||||||
|
<th colspan="5">Data</th>
|
||||||
|
<th rowspan="2">ParityCheck</th>
|
||||||
|
</tr>
|
||||||
|
<td>RID</td>
|
||||||
|
<td>Result</td>
|
||||||
|
<td colspan="3">ResultData</td>
|
||||||
|
<tr>
|
||||||
|
</tr>
|
||||||
|
<tr align="center">
|
||||||
|
<td>2 bytes</td>
|
||||||
|
<td>1 byte</td>
|
||||||
|
<td>2 bytes</td>
|
||||||
|
<td colspan="5">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 bytes)</td>
|
||||||
|
<td>username<br/>(32 bytes)</td>
|
||||||
|
<td>reserved<br/>(2 bytes)</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.4 掌静脉录入(MID_ENROLL_SINGLE)
|
||||||
|
|
||||||
|
掌静脉录入指令下发携带的参数 `msg_enroll_data` 定义如下:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
struct msg_enroll_data {
|
||||||
|
uint8_t username[32];
|
||||||
|
uint8_t reserved1[3];
|
||||||
|
uint8_t timeout;
|
||||||
|
uint8_t reserved2[4];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- username:录入用户的用户名。
|
||||||
|
- reserved1:保留字段,暂未使用。需设置为 0x00。
|
||||||
|
- timeout:录入过程的超时时间(s),默认为10s,用户可以自行设置(最大不超过255s)。**主控等待模组录入应答的超时时间应大于此参数设置值。**
|
||||||
|
- reserved2:保留字段,暂未使用。需设置为 0x00。
|
||||||
|
|
||||||
|
录入结果 Result 确认码的返回值见 `命令结果上报(MID_REPLY)`。
|
||||||
|
|
||||||
|
模组掌静脉录入成功后,通过消息 MID_REPLY 返回的数据 ResultData 定义如下:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
struct msg_reply_enroll_data {
|
||||||
|
uint16_t user_id;
|
||||||
|
uint8_t reserved;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- user_id:用户 ID
|
||||||
|
- reserved:保留,暂未使用。
|
||||||
|
|
||||||
|
### 3.5 删除单个掌静脉(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不存在
|
||||||
|
|
||||||
|
### 3.6 删除所有掌静脉(MID_DELALL)
|
||||||
|
|
||||||
|
删除所有已录入的用户。
|
||||||
|
|
||||||
|
该指令无需携带参数。
|
||||||
|
|
||||||
|
指令执行结束后,通过消息 MID_REPLY 返回结果(主控等待应答超时时间建议设置为 5s):
|
||||||
|
|
||||||
|
- 0x00(MR_SUCCESS):删除成功
|
||||||
|
- 0x05(MR_FAILED4_UNKNOWNREASON):未知错误
|
||||||
|
|
||||||
|
### 3.7 获取模组ID
|
||||||
|
|
||||||
|
该命令用于获取模组ID,类型为长度为32的字符串。
|
||||||
|
|
||||||
|
该指令无需携带参数。
|
||||||
|
|
||||||
|
模组通过消息 MID_REPLY 返回的数据 msg_reply_module_id 定义如下:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
struct msg_reply_module_id {
|
||||||
|
char id[32];
|
||||||
|
uint8_t reserved[512];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
参数说明:
|
||||||
|
|
||||||
|
- id:长度不超过32的字符串ID。
|
||||||
|
- reserved:保留,暂未使用。
|
||||||
|
|
||||||
|
### 3.9 命令结果上报(MID_REPLY)
|
||||||
|
|
||||||
|
模组向主控发送的 MID_REPLY 消息的完整协议如下所示:
|
||||||
|
|
||||||
|
<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 | 未采用加密通讯 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3.10 模组状态上报(MID_NOTE)
|
||||||
|
|
||||||
|
MID_NOTE 消息主要作用是主动向主控上报一些信息,报文格式如下:
|
||||||
|
|
||||||
|
<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">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 | 模组已上电初始化成功 |
|
||||||
|
|
||||||
|
## 4 示例报文
|
||||||
|
|
||||||
|
- 掌静脉录入
|
||||||
|
|
||||||
|
录入 username为 test 的用户,超时时间 10s,主控发送:
|
||||||
|
|
||||||
|
0xef 0xaa 0x1d 0x00 0x28 0x74 0x65 0x73 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x0a 0x00 0x00 0x00 0x00 0x29
|
||||||
|
|
||||||
|
录入成功,ID为1,模组上报:
|
||||||
|
|
||||||
|
0xef 0xaa 0x00 0x00 0x05 0x1d 0x00 0x00 0x01 0x00 0x19
|
||||||
|
|
||||||
|
- 掌静脉识别
|
||||||
|
|
||||||
|
开始识别比对,超时时间20s,主控发送:
|
||||||
|
|
||||||
|
0xef 0xaa 0x12 0x00 0x02 0x00 0x14 0x04
|
||||||
|
|
||||||
|
识别比对成功,user_id为1,username为test ,模组上报:
|
||||||
|
|
||||||
|
0xef 0xaa 0x00 0x00 0x26 0x12 0x00 0x00 0x01 0x74 0x65 0x73 0x74 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7a 0x59
|
||||||
|
|
||||||
|
- 删除所有录入掌静脉
|
||||||
|
|
||||||
|
主控发送:
|
||||||
|
|
||||||
|
0xef 0xaa 0x21 0x00 0x00 0x21
|
||||||
|
|
||||||
|
删除成功,模组上报:
|
||||||
|
|
||||||
|
0xef 0xaa 0x00 0x00 0x02 0x21 0x00 0x23
|
||||||
|
|
||||||
|
## 5 文档修改记录
|
||||||
|
|
||||||
|
2024/10/14:V0.1版本,描述模组识别、录入、删除几个基本协议指令。
|
||||||
|
|
||||||
|
2024/11/18:V0.2版本,`MID_VERIFY` 消息新增字段预留空间,用于功能扩展。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
|||||||
param($type)
|
param($type)
|
||||||
|
|
||||||
|
# [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding("GBK")
|
||||||
|
|
||||||
$MsvcScript = 'D:\Program Files\Microsoft Visual Studio\2022\\Community\Common7\Tools\Launch-VsDevShell.ps1'
|
$MsvcScript = 'D:\Program Files\Microsoft Visual Studio\2022\\Community\Common7\Tools\Launch-VsDevShell.ps1'
|
||||||
if (!(Test-Path $MsvcScript)) { $MsvcScript = 'D:\Program Files\Microsoft Visual Studio\2022\\Professional\Common7\Tools\Launch-VsDevShell.ps1' }
|
if (!(Test-Path $MsvcScript)) { $MsvcScript = 'D:\Program Files\Microsoft Visual Studio\2022\\Professional\Common7\Tools\Launch-VsDevShell.ps1' }
|
||||||
. $MsvcScript -SkipAutomaticLocation -Arch amd64
|
. $MsvcScript -SkipAutomaticLocation -Arch amd64
|
||||||
|
|
||||||
$qtHome = "D:\Qt\6.7.3\msvc2019_64"
|
$qtHome = "D:\Qt\6.8.0\msvc2022_64"
|
||||||
$openSSLRoot = "D:\Qt\Tools\OpenSSLv3\Win_x64"
|
$openSSLRoot = "D:\Qt\Tools\OpenSSLv3\Win_x64"
|
||||||
|
|
||||||
$librariesPath = "E:\Projects\Libraries"
|
$librariesPath = "E:\Projects\Libraries"
|
||||||
@ -14,8 +17,15 @@ $ffmpegRoot = "$librariesPath\ffmpeg-7.0.2-full_build-shared"
|
|||||||
|
|
||||||
$projectPath = Get-Location
|
$projectPath = Get-Location
|
||||||
$buildPath = Join-Path -Path $projectPath -ChildPath "build"
|
$buildPath = Join-Path -Path $projectPath -ChildPath "build"
|
||||||
$deployPath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools"
|
|
||||||
$zipFilePath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools.zip"
|
$fileContent = (Get-Content -Path "CMakeLists.txt") -join " "
|
||||||
|
if ($fileContent -match 'project\([^\)]+VERSION\s+([0-9]+\.[0-9]+)') {
|
||||||
|
$version = $Matches[1]
|
||||||
|
} else {
|
||||||
|
Write-Output "未找到版本号"
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployPath = Join-Path -Path $buildPath -ChildPath "掌静脉工具v$version"
|
||||||
$changelogPath = Join-Path -Path $buildPath -ChildPath "CHANGELOG.txt"
|
$changelogPath = Join-Path -Path $buildPath -ChildPath "CHANGELOG.txt"
|
||||||
|
|
||||||
function Build() {
|
function Build() {
|
||||||
@ -41,10 +51,19 @@ function Deploy() {
|
|||||||
Copy-Item $buildPath\Analyser\Analyser.exe $deployPath\Analyser.exe
|
Copy-Item $buildPath\Analyser\Analyser.exe $deployPath\Analyser.exe
|
||||||
Copy-Item $buildPath\OtaUpdate\SmartLockerUpdater.exe $deployPath\SmartLockerUpdater.exe
|
Copy-Item $buildPath\OtaUpdate\SmartLockerUpdater.exe $deployPath\SmartLockerUpdater.exe
|
||||||
|
|
||||||
$executables = "Analyser.exe", "SmartLockerUpdater.exe"
|
$executables = @(
|
||||||
foreach ($executable in $executables) {
|
@("Analyser.exe", "掌静脉测试工具.exe"),
|
||||||
& $qtHome\bin\windeployqt.exe $deployPath\$executable --qmldir=$qtHome\qml
|
@("SmartLockerUpdater.exe", "掌静脉模组升级工具.exe")
|
||||||
|
)
|
||||||
|
foreach ($executablePair in $executables) {
|
||||||
|
$oldName = $executablePair[0]
|
||||||
|
$newName = $executablePair[1]
|
||||||
|
& $qtHome\bin\windeployqt.exe $deployPath\$oldName --qmldir=$qtHome\qml
|
||||||
|
Rename-Item -Path $deployPath\$oldName -NewName $deployPath\$newName
|
||||||
}
|
}
|
||||||
|
Remove-Item -Path $deployPath\Qt6Quick3D*
|
||||||
|
Remove-Item -Path $deployPath\translations -Recurse -Force # 暂时不需要翻译文件
|
||||||
|
Remove-Item -Path $deployPath\qmltooling -Recurse -Force
|
||||||
|
|
||||||
$modules = "QmlCore"
|
$modules = "QmlCore"
|
||||||
foreach ($module in $modules) {
|
foreach ($module in $modules) {
|
||||||
@ -62,17 +81,19 @@ function Deploy() {
|
|||||||
Copy-Item $openSSLRoot\bin\libssl-3-x64.dll $deployPath
|
Copy-Item $openSSLRoot\bin\libssl-3-x64.dll $deployPath
|
||||||
Copy-Item $openSSLRoot\bin\libcrypto-3-x64.dll $deployPath
|
Copy-Item $openSSLRoot\bin\libcrypto-3-x64.dll $deployPath
|
||||||
|
|
||||||
$boosts = "thread", "filesystem", "log"
|
$boosts = "thread", "filesystem", "log", "json"
|
||||||
foreach ($boost in $boosts) {
|
foreach ($boost in $boosts) {
|
||||||
Copy-Item -Path $boostRoot\lib\boost_$boost-vc143-mt-x64-1_86.dll -Destination $deployPath
|
Copy-Item -Path $boostRoot\lib\boost_$boost-vc143-mt-x64-1_86.dll -Destination $deployPath
|
||||||
}
|
}
|
||||||
|
|
||||||
$ffmpegs = "avcodec-61", "avdevice-61", "avfilter-10", "avformat-61", "avutil-59", "postproc-58", "swresample-5", "swscale-8"
|
$ffmpegs = "avcodec-61", "avdevice-61", "avformat-61", "avutil-59", "postproc-58", "swresample-5", "swscale-8" # avfilter-10
|
||||||
foreach ($ffmpeg in $ffmpegs) {
|
foreach ($ffmpeg in $ffmpegs) {
|
||||||
Copy-Item -Path $ffmpegRoot\bin\$ffmpeg.dll -Destination $deployPath
|
Copy-Item -Path $ffmpegRoot\bin\$ffmpeg.dll -Destination $deployPath
|
||||||
}
|
}
|
||||||
|
|
||||||
Compress-Archive -Path $deployPath -DestinationPath $zipFilePath -Force
|
$zipFilePath = Join-Path -Path $buildPath -ChildPath "掌静脉工具v$version.7z"
|
||||||
|
# Compress-Archive -Path $deployPath -DestinationPath $zipFilePath -Force
|
||||||
|
& resources\7za.exe a -t7z -mx=9 $zipFilePath $deployPath
|
||||||
}
|
}
|
||||||
|
|
||||||
function Clean() {
|
function Clean() {
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
base_path=$(pwd)
|
base_path=$(pwd)
|
||||||
|
if [[ $base_path =~ ^/mnt/ ]]; then
|
||||||
|
build_path=/tmp/build
|
||||||
|
else
|
||||||
build_path=${base_path}/build
|
build_path=${base_path}/build
|
||||||
qt_prefix_path="/opt/Qt/6.7.3/gcc_64"
|
fi
|
||||||
|
echo "build path: $build_path"
|
||||||
|
qt_prefix_path="/opt/Qt/6.8.0/gcc_64"
|
||||||
|
|
||||||
debug_deploy=false
|
debug_deploy=false
|
||||||
|
|
||||||
@ -31,7 +36,7 @@ elif [ -d "/opt/Qt/5.15.2/gcc_64" ]; then
|
|||||||
-DQt5Svg_DIR=${qt_prefix_path}/lib/cmake/Qt5Svg "
|
-DQt5Svg_DIR=${qt_prefix_path}/lib/cmake/Qt5Svg "
|
||||||
else
|
else
|
||||||
cmake_qt_parameters=""
|
cmake_qt_parameters=""
|
||||||
echo "please install qt6.7.3 or qt5.15.2 ..."
|
echo "please install qt6.8.0 or qt5.15.2 ..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
function cmake_scan() {
|
function cmake_scan() {
|
||||||
@ -70,6 +75,10 @@ function build() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clean(){
|
||||||
|
rm -fr ${build_path}
|
||||||
|
}
|
||||||
|
|
||||||
function deploy() {
|
function deploy() {
|
||||||
if [ -d ${build_path}/lib ]; then
|
if [ -d ${build_path}/lib ]; then
|
||||||
rm -fr ${build_path}/lib
|
rm -fr ${build_path}/lib
|
||||||
|
288
resources/local_use.svg
Normal file
288
resources/local_use.svg
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- 由 Microsoft Visio, SVG Export 生成 绘图1.svg 页-1 -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||||
|
xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/" width="1.39583in" height="3.17224in"
|
||||||
|
viewBox="0 0 100.5 228.401" xml:space="preserve" color-interpolation-filters="sRGB" class="st20">
|
||||||
|
<v:documentProperties v:langID="2052" v:metric="true" v:viewMarkup="false"/>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
<![CDATA[
|
||||||
|
.st1 {fill:#ff00ff;fill-opacity:0;stroke:#c7c8c8;stroke-opacity:0;stroke-width:0.72}
|
||||||
|
.st2 {fill:url(#grad0-7);stroke:#ffffff;stroke-width:0.72}
|
||||||
|
.st3 {fill:url(#grad11-11)}
|
||||||
|
.st4 {stroke:#000000;stroke-width:0.72}
|
||||||
|
.st5 {stroke:#c8c8c8;stroke-width:1.5}
|
||||||
|
.st6 {fill:#ff00ff;fill-opacity:0}
|
||||||
|
.st7 {stroke:#c7c8c8;stroke-opacity:0;stroke-width:0.72}
|
||||||
|
.st8 {fill:url(#grad7-25);stroke:none;stroke-width:0.72}
|
||||||
|
.st9 {fill:url(#grad0-29);stroke:none;stroke-width:0.72}
|
||||||
|
.st10 {stroke:#ffffff;stroke-width:0.72}
|
||||||
|
.st11 {fill:url(#grad3-40);stroke:#c7c8c8;stroke-width:0.12}
|
||||||
|
.st12 {fill:url(#grad7-44);stroke:#c7c8c8;stroke-width:0.24}
|
||||||
|
.st13 {fill:url(#grad10-48);stroke:#000000;stroke-width:0.72}
|
||||||
|
.st14 {fill:#ffffff;stroke:#000000;stroke-width:0.24}
|
||||||
|
.st15 {fill:url(#grad0-58);stroke:#000000;stroke-width:0.72}
|
||||||
|
.st16 {fill:none;stroke:none;stroke-width:0.25}
|
||||||
|
.st17 {fill:#4672c4;font-family:黑体;font-size:1.16666em}
|
||||||
|
.st18 {marker-end:url(#mrkr13-73);marker-start:url(#mrkr13-71);stroke:#4672c4;stroke-linecap:round;stroke-linejoin:round;stroke-width:1}
|
||||||
|
.st19 {fill:#4672c4;fill-opacity:1;stroke:#4672c4;stroke-opacity:1;stroke-width:0.28409090909091}
|
||||||
|
.st20 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<defs id="Patterns_And_Gradients">
|
||||||
|
<linearGradient id="grad0-7" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(270 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#668ace" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#626262" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<pattern id="grad11-11" x="0" y="0" width="1" height="1" patternContentUnits="objectBoundingBox">
|
||||||
|
<path d="M 0 1 L 0 0 L 1 0 z" style="fill:url(#grad0-12)"/>
|
||||||
|
<path d="M 0 1 L 1 1 L 1 0 z" style="fill:url(#grad0-13)"/>
|
||||||
|
</pattern>
|
||||||
|
<linearGradient id="grad0-12" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(180 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c0c0c0" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grad0-13" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(90 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c0c0c0" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="grad7-25" cx="0" cy="0" r="1.4">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c0c0c0" stop-opacity="1"/>
|
||||||
|
</radialGradient>
|
||||||
|
<linearGradient id="grad0-29" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(180 0.5 0.5)">
|
||||||
|
<stop offset="0.01" stop-color="#3e3e3e" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="#365fa9" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#3e3e3e" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="grad3-40" cx="0.5" cy="0.5" r="0.73">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#008000" stop-opacity="1"/>
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="grad7-44" cx="0" cy="0" r="1.4">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#000000" stop-opacity="1"/>
|
||||||
|
</radialGradient>
|
||||||
|
<pattern id="grad10-48" x="0" y="0" width="1" height="1" patternContentUnits="objectBoundingBox">
|
||||||
|
<path d="M 0.5 0.5 L 0 0 L 0 1 z" style="fill:url(#grad0-49)"/>
|
||||||
|
<path d="M 0.5 0.5 L 1 0 L 1 1 z" style="fill:url(#grad0-50)"/>
|
||||||
|
<path d="M 0.5 0.5 L 0 0 L 1 0 z" style="fill:url(#grad0-51)"/>
|
||||||
|
<path d="M 0.5 0.5 L 0 1 L 1 1 z" style="fill:url(#grad0-52)"/>
|
||||||
|
</pattern>
|
||||||
|
<linearGradient id="grad0-49" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(180 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c6d1e3" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grad0-50" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(360 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c6d1e3" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grad0-51" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(270 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c6d1e3" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grad0-52" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(90 0.5 0.5)">
|
||||||
|
<stop offset="0" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#c6d1e3" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grad0-58" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(90 0.5 0.5)">
|
||||||
|
<stop offset="0.01" stop-color="#000000" stop-opacity="1"/>
|
||||||
|
<stop offset="0.5" stop-color="#ffffff" stop-opacity="1"/>
|
||||||
|
<stop offset="1" stop-color="#000000" stop-opacity="1"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<defs id="Markers">
|
||||||
|
<g id="lend13">
|
||||||
|
<path d="M 3 1 L 0 0 L 3 -1 L 3 1 " style="stroke:none"/>
|
||||||
|
</g>
|
||||||
|
<marker id="mrkr13-71" class="st19" v:arrowType="13" v:arrowSize="2" v:setback="10.2" refX="10.2" orient="auto"
|
||||||
|
markerUnits="strokeWidth" overflow="visible">
|
||||||
|
<use xlink:href="#lend13" transform="scale(3.52) "/>
|
||||||
|
</marker>
|
||||||
|
<marker id="mrkr13-73" class="st19" v:arrowType="13" v:arrowSize="2" v:setback="10.56" refX="-10.56" orient="auto"
|
||||||
|
markerUnits="strokeWidth" overflow="visible">
|
||||||
|
<use xlink:href="#lend13" transform="scale(-3.52,-3.52) "/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<g v:mID="0" v:index="1" v:groupContext="foregroundPage">
|
||||||
|
<title>页-1</title>
|
||||||
|
<v:pageProperties v:drawingScale="0.0393701" v:pageScale="0.0393701" v:drawingUnits="24" v:shadowOffsetX="8.50394"
|
||||||
|
v:shadowOffsetY="-8.50394"/>
|
||||||
|
<v:layer v:name="连接线" v:index="0"/>
|
||||||
|
<g id="group66-1" transform="translate(33.9508,-22.4051)" v:mID="66" v:groupContext="group">
|
||||||
|
<v:custProps>
|
||||||
|
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:type="0" v:invis="true" v:ask="false" v:langID="2052"
|
||||||
|
v:val="VT4(连接性)"/>
|
||||||
|
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:type="0" v:invis="true" v:ask="false" v:langID="2052" v:val="VT4(概念)"/>
|
||||||
|
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:type="0" v:invis="true" v:ask="false" v:langID="2052"
|
||||||
|
v:val="VT4(数据)"/>
|
||||||
|
</v:custProps>
|
||||||
|
<v:userDefs>
|
||||||
|
<v:ud v:nameU="visLegendShape" v:val="VT0(2):26"/>
|
||||||
|
<v:ud v:nameU="HasText" v:val="VT0(0):5"/>
|
||||||
|
<v:ud v:nameU="SolSH" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||||
|
<v:ud v:nameU="ShapeClass" v:val="VT0(4):26"/>
|
||||||
|
<v:ud v:nameU="ShapeType" v:val="VT0(7):26"/>
|
||||||
|
<v:ud v:nameU="SubShapeType" v:val="VT0(15):26"/>
|
||||||
|
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||||
|
</v:userDefs>
|
||||||
|
<title>数据</title>
|
||||||
|
<g id="shape67-2" v:mID="67" v:groupContext="shape" transform="translate(0,0.000575512)">
|
||||||
|
<title>工作表.67</title>
|
||||||
|
<path d="M32.54 205.26 A16.2701 9.45359 0.18 0 1 32.15 207.35 A16.2701 9.45359 -179.82 0 0 32.54 205.26 ZM32.54 205.26
|
||||||
|
A16.2701 9.45359 -179.82 0 0 0 205.26 L0 218.95 A16.2702 9.45363 -179.82 0 0 32.54 218.95 L32.6 218.95
|
||||||
|
L32.6 205.26 L32.54 205.26 Z" class="st1"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape68-4" v:mID="68" v:groupContext="shape" transform="translate(-1.65867E-13,-13.6911)">
|
||||||
|
<title>工作表.68</title>
|
||||||
|
<path d="M0 218.9 A16.2701 9.45359 -179.82 1 1 32.54 219 A16.2701 9.45359 -179.82 1 1 0 218.9 Z" class="st2"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape69-8" v:mID="69" v:groupContext="shape">
|
||||||
|
<title>工作表.69</title>
|
||||||
|
<path d="M0 205.26 L0 218.9 L0 218.95 A16.2702 9.45363 -179.82 0 0 32.54 218.95 L32.6 218.95 L32.6 205.26 L32.54
|
||||||
|
205.26 A16.2701 9.45359 0.18 0 1 0 205.26 L0 205.26 Z" class="st3"/>
|
||||||
|
<path d="M0 205.26 L0 218.9 L0 218.95 A16.2702 9.45363 -179.82 0 0 32.54 218.95 L32.6 218.95 L32.6 205.26 L32.54
|
||||||
|
205.26 A16.2701 9.45359 0.18 0 1 0 205.26" class="st4"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape70-15" v:mID="70" v:groupContext="shape" transform="translate(0,0.000264735)">
|
||||||
|
<title>工作表.70</title>
|
||||||
|
<path d="M32.54 205.26 A16.2701 9.45359 -179.82 1 0 0 205.26 L0 218.95 A16.2702 9.45363 -179.82 0 0 32.54 218.95
|
||||||
|
L32.6 218.95 L32.6 205.26 L32.54 205.26" class="st5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="group71-18" transform="translate(16.2343,-106.027)" v:mID="71" v:groupContext="group">
|
||||||
|
<v:custProps>
|
||||||
|
<v:cp v:nameU="ShapeClass" v:lbl="ShapeClass" v:type="0" v:invis="true" v:ask="false" v:langID="2052"
|
||||||
|
v:val="VT4(设备)"/>
|
||||||
|
<v:cp v:nameU="ShapeType" v:lbl="ShapeType" v:type="0" v:invis="true" v:ask="false" v:langID="2052"
|
||||||
|
v:val="VT4(计算机)"/>
|
||||||
|
<v:cp v:nameU="SubShapeType" v:lbl="SubShapeType" v:type="0" v:invis="true" v:ask="false" v:langID="2052"
|
||||||
|
v:val="VT4(平板)"/>
|
||||||
|
<v:cp v:nameU="Manufacturer" v:lbl="制造商" v:type="0" v:sortKey="Equipment" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="ProductNumber" v:lbl="产品编号" v:type="0" v:sortKey="Equipment" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="PartNumber" v:lbl="部件号" v:type="0" v:sortKey="Equipment" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="ProductDescription" v:lbl="产品说明" v:type="0" v:sortKey="Equipment" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="AssetNumber" v:lbl="资产号" v:type="0" v:sortKey="Asset" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="SerialNumber" v:lbl="序列号" v:type="0" v:sortKey="Asset" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="Location" v:lbl="位置" v:type="0" v:sortKey="Asset" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="Building" v:lbl="建筑物" v:type="0" v:sortKey="Asset" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="Room" v:lbl="房间" v:type="0" v:sortKey="Asset" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="NetworkName" v:lbl="网络名称" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="IPAddress" v:lbl="IP 地址" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="SubnetMask" v:lbl="子网掩码" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="AdminInterface" v:lbl="管理接口" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="NumberofPorts" v:lbl="端口数目" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="MACAddress" v:lbl="MAC 地址" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="CommunityString" v:lbl="团体字符串" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="NetworkDescription" v:lbl="网络说明" v:type="0" v:sortKey="Network" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="HardDriveSize" v:lbl="硬盘容量" v:type="0" v:sortKey="Workstation" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="CPU" v:lbl="CPU" v:type="0" v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="Memory" v:lbl="内存" v:type="0" v:sortKey="Workstation" v:invis="false" v:ask="false" v:langID="2052"/>
|
||||||
|
<v:cp v:nameU="OperatingSystem" v:lbl="操作系统" v:type="0" v:sortKey="Workstation" v:invis="false" v:ask="false"
|
||||||
|
v:langID="2052"/>
|
||||||
|
</v:custProps>
|
||||||
|
<v:userDefs>
|
||||||
|
<v:ud v:nameU="HasText" v:val="VT0(0):5"/>
|
||||||
|
<v:ud v:nameU="ShapeClass" v:val="VT0(5):26"/>
|
||||||
|
<v:ud v:nameU="ShapeType" v:val="VT0(6):26"/>
|
||||||
|
<v:ud v:nameU="SubShapeType" v:val="VT0(69):26"/>
|
||||||
|
<v:ud v:nameU="SolSH" v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41"/>
|
||||||
|
<v:ud v:nameU="visLegendShape" v:val="VT0(2):26"/>
|
||||||
|
<v:ud v:nameU="visVersion" v:prompt="" v:val="VT0(15):26"/>
|
||||||
|
</v:userDefs>
|
||||||
|
<title>平板电脑</title>
|
||||||
|
<g id="shape72-19" v:mID="72" v:groupContext="shape" transform="translate(0,8.6288E-06)">
|
||||||
|
<title>工作表.72</title>
|
||||||
|
<path d="M22.75 134.89 A158.111 145.912 89.84 0 1 41.19 144.31 A158.315 146.1 -89.8 0 0 22.75 134.89 ZM2.33 154.56
|
||||||
|
A2.43186 1.43688 -80 1 0 1.93 158.87 A3.97821 3.67126 90 0 1 1.53 159.34 L22.75 163.3 L22.75 205.59
|
||||||
|
A105.542 97.3984 -90.11 0 0 59.76 228.4 A11.7512 10.8448 -89.38 0 0 66.73 223.18 L66.84 153.37 A5.68818
|
||||||
|
5.2493 89.94 0 1 59.76 157.7 A6.18103 5.7045 -89 0 0 66.84 153.37 A126.613 116.845 -90.37 0 0 29.5 130.79
|
||||||
|
A6.74865 6.22798 -89.71 0 0 22.75 134.89 L22.75 159.32 L2.27 154.55 L2.33 154.56 Z" class="st6"/>
|
||||||
|
<path d="M22.75 134.89 A158.111 145.912 89.84 0 1 41.19 144.31 A158.315 146.1 -89.8 0 0 22.75 134.89M2.33 154.56
|
||||||
|
A2.43186 1.43688 -80 1 0 1.93 158.87 A3.97821 3.67126 90 0 1 1.53 159.34 L22.75 163.3 L22.75 205.59
|
||||||
|
A105.542 97.3984 -90.11 0 0 59.76 228.4 A11.7512 10.8448 -89.38 0 0 66.73 223.18 L66.84 153.37 A5.68818
|
||||||
|
5.2493 89.94 0 1 59.76 157.7 A6.18103 5.7045 -89 0 0 66.84 153.37 A126.613 116.845 -90.37 0 0 29.5 130.79
|
||||||
|
A6.74865 6.22798 -89.71 0 0 22.75 134.89 L22.75 159.32 L2.27 154.55" class="st7"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape73-22" v:mID="73" v:groupContext="shape" transform="translate(23.9471,0)">
|
||||||
|
<title>工作表.73</title>
|
||||||
|
<path d="M0 205.59 A105.542 97.3984 -90.11 0 0 37.01 228.4 L37.01 157.7 A158.315 146.1 -89.8 0 0 0 134.89 L0 205.59
|
||||||
|
Z" class="st8"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape74-26" v:mID="74" v:groupContext="shape" transform="translate(60.9562,0)">
|
||||||
|
<title>工作表.74</title>
|
||||||
|
<path d="M-0 228.4 A11.7512 10.8448 -89.38 0 0 6.97 223.18 L7.08 153.37 A5.68818 5.2493 89.94 0 1 0 157.7 L0 228.4
|
||||||
|
Z" class="st9"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape75-30" v:mID="75" v:groupContext="shape" transform="translate(23.9471,-70.4662)">
|
||||||
|
<title>工作表.75</title>
|
||||||
|
<path d="M-0 205.36 A158.111 145.912 89.84 0 1 37.01 228.17 A6.18103 5.7045 -89 0 0 44.08 223.83 A126.613 116.845
|
||||||
|
-90.37 0 0 6.75 201.25 A6.74865 6.22798 -89.71 0 0 0 205.36 Z" class="st2"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape76-33" v:mID="76" v:groupContext="shape" transform="translate(27.648,-10.263)">
|
||||||
|
<title>工作表.76</title>
|
||||||
|
<path d="M30.59 228.4 L0 209.93 L0 151.2" class="st10"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape77-37" v:mID="77" v:groupContext="shape" transform="translate(-62.8892,22.9027) rotate(-30)">
|
||||||
|
<title>工作表.77</title>
|
||||||
|
<ellipse cx="1.27835" cy="226.724" rx="1.27835" ry="1.67692" class="st11"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape78-41" v:mID="78" v:groupContext="shape" transform="translate(40.4972,-66.0307) rotate(10)">
|
||||||
|
<title>工作表.78</title>
|
||||||
|
<ellipse cx="1.43688" cy="225.969" rx="1.43688" ry="2.43186" class="st12"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape79-45" v:mID="79" v:groupContext="shape" transform="translate(27.648,-10.263)">
|
||||||
|
<title>工作表.79</title>
|
||||||
|
<path d="M1.2 209.24 L30.59 227.26 L30.59 228.4 L30.59 169.67 L0 151.2 L1.2 152 L1.2 209.24 Z" class="st13"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape80-53" v:mID="80" v:groupContext="shape" transform="translate(36.0639,-63.1276)">
|
||||||
|
<title>工作表.80</title>
|
||||||
|
<path d="M0 228.4 L3.08 227.75 L0.23 225.57 L0 228.4 Z" class="st14"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape81-55" v:mID="81" v:groupContext="shape" transform="translate(2.72921,-62.8833)">
|
||||||
|
<title>工作表.81</title>
|
||||||
|
<path d="M0 222.22 L33.1 228.4 L33.6 225.08 L0.74 217.43 A3.67126 3.97821 0 0 1 0 222.22 Z" class="st15"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape82-59" v:mID="82" v:groupContext="shape" transform="translate(0,1.38061E-05)">
|
||||||
|
<title>工作表.82</title>
|
||||||
|
<path d="M23.95 205.59 A108.227 98.8731 -90.39 0 0 60.96 228.4 A8.47998 7.74722 -90.69 0 0 67.93 223.63 L68.03 153.37
|
||||||
|
A137.519 125.635 -90.57 0 0 30.7 130.79 A6.89496 6.29907 -89.53 0 0 23.95 134.89 L23.96 157.82 L3.63
|
||||||
|
153.09 A3.13064 3.42684 -180 0 0 0.02 156.81 A3.85578 4.22058 -180 0 0 2.7 160.59 L23.95 164.28 L23.95
|
||||||
|
205.59" class="st5"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="shape83-62" v:mID="83" v:groupContext="shape" transform="translate(0.25,-3.31851)">
|
||||||
|
<title>工作表.83</title>
|
||||||
|
<desc>掌静脉模组</desc>
|
||||||
|
<v:textBlock v:margins="rect(2,2,2,2)" v:tabSpace="42.5197"/>
|
||||||
|
<v:textRect cx="50" cy="222.401" width="100.01" height="12"/>
|
||||||
|
<rect x="0" y="216.401" width="100" height="12" class="st16"/>
|
||||||
|
<text x="15" y="227.06" class="st17" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>掌静脉模组</text> </g>
|
||||||
|
<g id="shape84-65" v:mID="84" v:groupContext="shape" v:layerMember="0" transform="translate(43.1634,-55.0034)">
|
||||||
|
<title>动态连接线</title>
|
||||||
|
<path d="M7.09 218.2 L7.09 217.84 L7.09 174.68" class="st18"/>
|
||||||
|
</g>
|
||||||
|
<g id="shape85-74" v:mID="85" v:groupContext="shape" transform="translate(0.25,-213.082)">
|
||||||
|
<title>工作表.85</title>
|
||||||
|
<desc>门禁面板机</desc>
|
||||||
|
<v:textBlock v:margins="rect(2,2,2,2)" v:tabSpace="42.5197"/>
|
||||||
|
<v:textRect cx="50" cy="222.401" width="100.01" height="12"/>
|
||||||
|
<rect x="0" y="216.401" width="100" height="12" class="st16"/>
|
||||||
|
<text x="15" y="227.06" class="st17" v:langID="2052"><v:paragraph v:horizAlign="1"/><v:tabList/>门禁面板机</text> </g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 18 KiB |
1558
resources/muti_use.svg
Normal file
1558
resources/muti_use.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 98 KiB |
Loading…
Reference in New Issue
Block a user