Compare commits

..

14 Commits

Author SHA1 Message Date
amass
a2764a9ade check upload. 2024-11-21 21:39:49 +08:00
943fb6fb4b adapt for linux.
All checks were successful
Build Applications / Build (push) Successful in 3m3s
Windows CI / build (push) Successful in 3m4s
2024-11-21 13:12:45 +00:00
luocai
ce673bf330 add dshow api.
Some checks failed
Build Applications / Build (push) Failing after 1m17s
Windows CI / build (push) Successful in 3m4s
2024-11-21 19:34:13 +08:00
luocai
28f2f60532 use 7z for deploy. 2024-11-21 10:54:22 +08:00
luocai
c846ae81a0 show version for updater.
All checks were successful
Build Applications / Build (push) Successful in 6m12s
Windows CI / build (push) Successful in 7m58s
2024-11-21 09:31:41 +08:00
luocai
fd72d00925 添加简要的说明。
Some checks failed
Windows CI / build (push) Failing after 11s
Build Applications / Build (push) Successful in 3m6s
2024-11-19 14:34:01 +08:00
luocai
8055f4e70f VerifyRequest新增字段,完善协议。
All checks were successful
Build Applications / Build (push) Successful in 6m4s
Windows CI / build (push) Successful in 8m7s
2024-11-18 18:39:50 +08:00
5ae5c5adc6 构建乱码的问题。
All checks were successful
Build Applications / PullDocker (push) Successful in 4s
Build Applications / Build (push) Successful in 6m22s
Windows CI / build (push) Successful in 8m0s
2024-11-14 17:57:17 +08:00
5b602e607c 构建乱码的问题。
All checks were successful
Build Applications / PullDocker (push) Successful in 4s
Build Applications / Build (push) Successful in 2m31s
Windows CI / build (push) Successful in 5m10s
2024-11-14 17:25:53 +08:00
c3430caf2f 构建乱码的问题。
Some checks failed
Build Applications / PullDocker (push) Successful in 4s
Build Applications / Build (push) Successful in 5m16s
Windows CI / build (push) Failing after 36s
2024-11-14 16:35:34 +08:00
0aa2721f67 构建乱码的问题。
Some checks failed
Build Applications / PullDocker (push) Successful in 5s
Build Applications / Build (push) Has been cancelled
Windows CI / build (push) Has been cancelled
2024-11-14 16:30:56 +08:00
luocai
6ee75bfae1 修正构建文件编码。
Some checks failed
Build Applications / Build (push) Successful in 2m30s
Build Applications / PullDocker (push) Successful in 8m41s
Windows CI / build (push) Failing after 31s
2024-11-14 15:12:50 +08:00
luocai
404889a19c 修正构建文件编码。
Some checks failed
Windows CI / build (push) Failing after 32s
Build Applications / Build (push) Successful in 2m49s
Build Applications / PullDocker (push) Has been cancelled
2024-11-14 15:08:24 +08:00
luocai
881048a286 reset module before ota.
All checks were successful
Build Applications / PullDocker (push) Successful in 3s
Build Applications / Build (push) Successful in 5m14s
Windows CI / build (push) Successful in 8m3s
2024-11-08 18:26:33 +08:00
26 changed files with 2174 additions and 85 deletions

View File

@ -6,22 +6,18 @@ on:
- '**'
tags-ignore:
- 'v*'
paths:
- '**.cpp'
- '**.h'
- '**.conf'
jobs:
PullDocker:
runs-on: [ubuntu-latest, ubuntu-24.04]
steps:
- name: Login to ACR
uses: aliyun/acr-login@v1
with:
login-server: https://registry.cn-shenzhen.aliyuncs.com
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Pull Docker image
run: docker pull registry.cn-shenzhen.aliyuncs.com/amass_toolset/ubuntu_dev:24.04
Build:
runs-on: [ubuntu-latest, ubuntu-24.04]
container:
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/ubuntu_dev:24.04
credentials:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
steps:
- name: Set up SSH
run: |

View File

@ -5,6 +5,10 @@ on:
- '**'
tags-ignore:
- 'v*'
paths:
- '**.cpp'
- '**.h'
- '**.conf'
jobs:
build:
@ -32,4 +36,4 @@ jobs:
with:
body_path: build/CHANGELOG.txt
files: |-
build/SmartLockerTools_v*.zip
build/掌静脉工具v*.zip

View File

@ -1,5 +1,5 @@
name: Deploy Release
run-name: ${{ github.actor }} is building Bilby...
run-name: ${{ github.actor }} is building SmartLockerTools...
on:
push:
tags:
@ -9,6 +9,9 @@ jobs:
runs-on: [ubuntu-latest, ubuntu-24.04]
container:
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/ubuntu_dev:24.04
credentials:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
steps:
- name: Set up SSH
run: |

View File

@ -30,4 +30,4 @@ jobs:
with:
body_path: build/CHANGELOG.txt
files: |-
build/SmartLockerTools_v*.zip
build/掌静脉工具v*.zip

View File

@ -119,12 +119,15 @@ QStringList Application::availableSerialPorts() const {
return ret;
}
QStringList Application::availableUsbVideoCameras() const {
QStringList ret;
QVariantList Application::availableUsbVideoCameras() const {
QVariantList ret;
DeviceDiscovery d;
auto devices = d.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;
}
@ -478,6 +481,7 @@ bool Application::startOta(const QString &path) {
LOG(info) << "device already in ota mode.";
} else {
if (m_communication) {
resetModule();
m_communication->startOta();
} else {
emit otaMessage("请先打开设备");

View File

@ -43,7 +43,7 @@ public:
int exec();
static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
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 openUVC(const QString &deviceName);
Q_INVOKABLE void close();

View File

@ -89,6 +89,8 @@ public:
struct VerifyRequest {
uint8_t save_image;
uint8_t timeout; // timeout, unit second, default 10s
uint16_t interval = 0;
uint8_t reserved[4];
};
struct PalmStateNote {

View File

@ -32,7 +32,7 @@ bool VideoPlayer::open(const std::string &deviceName) {
#ifdef WIN32
constexpr auto format = "dshow";
std::ostringstream oss;
oss << "video=" << deviceName;
oss << "video=@device_pnp_" << deviceName;
auto device = oss.str();
#else
constexpr auto format = "v4l2";
@ -41,7 +41,10 @@ bool VideoPlayer::open(const std::string &deviceName) {
auto inputFormat = av_find_input_format(format);
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"
// clang-format on
av_dict_set(&dictionary, "video_size", "800*600", 0);
int status = avformat_open_input(&m_formatContext, device.c_str(), inputFormat, &dictionary);

View File

@ -57,6 +57,7 @@ RowLayout {
}
ComboBox {
id: uvcs
textRole: "name"
enabled: !App.uvcOpened
implicitWidth: 150
}
@ -67,8 +68,7 @@ RowLayout {
}
Button {
text: App.uvcOpened ? "关闭" : "连接"
onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC(
uvcs.currentText)
onClicked: App.uvcOpened ? App.closeUVC() : App.openUVC(uvcs.currentValue.path)
}
}
}

View File

@ -8,9 +8,11 @@ ListView {
message: ""
}
}
delegate: Text {
delegate: TextEdit {
width: ListView.view.width
readOnly: true
text: message
selectByMouse: true
wrapMode: Text.Wrap
}

View File

@ -54,18 +54,9 @@ Window {
}
}
Item {
// LogView {
// id: logBrowser
// anchors.fill: parent
// }
ScrollView {
id: view
anchors.fill: parent
TextArea {
LogView {
id: logBrowser
readOnly: true
wrapMode: TextArea.WordWrap
}
anchors.fill: parent
}
Button {
text: "清空"

View File

@ -17,7 +17,6 @@ if(WIN32)
else()
set(MBEDTLS_ROOT ${Libraries_ROOT}/mbedtls-3.6.2_msvc2022_64bit_debug)
endif()
add_compile_options(/showIncludes-)
else()
execute_process(
COMMAND sh -c "echo $HOME"

View File

@ -1,9 +1,13 @@
set(APPLICATION_NAME "掌静脉升级工具")
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets SerialPort)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets SerialPort)
configure_file(Configuration.h.in Configuration.h)
set(PROJECT_SOURCES OtaUpdate.rc
main.cpp
Widget.cpp
@ -15,6 +19,10 @@ qt_add_executable(SmartLockerUpdater
${PROJECT_SOURCES}
)
target_include_directories(SmartLockerUpdater
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(SmartLockerUpdater
PRIVATE Peripheral
PRIVATE Qt${QT_VERSION_MAJOR}::Widgets

View File

@ -0,0 +1,3 @@
#define APPLICATION_NAME "@APPLICATION_NAME@"
#define GIT_COMMIT_ID "@GIT_COMMIT_ID@"
#define APP_VERSION "@PROJECT_VERSION@"

View File

@ -1,4 +1,5 @@
#include "BoostLog.h"
#include "Configuration.h"
#include "Widget.h"
#include <QApplication>
#include <QFont>
@ -7,11 +8,14 @@ int main(int argc, char *argv[]) {
boost::log::initialize("logs/app");
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;
font.setPointSize(16);
a.setFont(font);
Widget w;
w.setWindowTitle("掌静脉模组升级工具");
w.setWindowTitle(QString("%1 %2").arg(a.applicationName()).arg(a.applicationVersion()));
w.setMinimumWidth(520);
w.setMinimumHeight(100);
w.show();

View File

@ -15,6 +15,6 @@ target_include_directories(Peripheral
target_link_libraries(Peripheral
PUBLIC Universal
PRIVATE Encrypt
$<$<PLATFORM_ID:Windows>:Mfreadwrite Mf mfplat mfuuid>
$<$<PLATFORM_ID:Windows>:Mfreadwrite Mf mfplat mfuuid strmiids comsuppw>
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
)

View File

@ -1,13 +1,15 @@
#include "DeviceDiscovery.h"
#include "BoostLog.h"
#include "StringUtility.h"
#include <boost/scope/scope_exit.hpp>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#ifdef WIN32
#include <comutil.h>
#include <dshow.h>
#include <mfapi.h>
#include <mfcaptureengine.h>
#include <strmif.h>
#else
#include <fcntl.h>
#include <linux/videodev2.h>
@ -27,6 +29,23 @@ DeviceDiscovery::DeviceDiscovery() {
}
#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) {
std::string ret;
WCHAR *friendlyName = nullptr;
@ -34,7 +53,7 @@ static std::string deviceName(IMFActivate *device) {
auto result = device->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength);
if (SUCCEEDED(result)) {
ret = Amass::StringUtility::wstringToString(std::wstring(friendlyName, nameLength));
ret = _com_util::ConvertBSTRToString(friendlyName);
}
if (friendlyName != nullptr) {
CoTaskMemFree(friendlyName);
@ -128,36 +147,108 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
&llTimeStamp, &pSample);
}
std::vector<std::string> DeviceDiscovery::devices() {
std::vector<std::string> ret;
IMFAttributes *attributes = nullptr;
boost::scope::scope_exit guard([&attributes] { SafeRelease(&attributes); });
auto result = MFCreateAttributes(&attributes, 1);
if (FAILED(result)) {
return ret;
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() {
std::vector<Device> devices;
ICreateDevEnum *pDevEnum = nullptr;
IEnumMoniker *pEnum = nullptr;
HRESULT hr =
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);
if (FAILED(result)) {
return ret;
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
if (hr != S_OK) {
pDevEnum->Release();
return devices;
}
UINT32 count;
IMFActivate **devices = nullptr;
result = MFEnumDeviceSources(attributes, &devices, &count);
if (FAILED(result)) {
return ret;
IMoniker *pMoniker = nullptr;
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) {
Device info;
GetDeviceNames(pMoniker, info);
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) {
return ret;
bool DeviceDiscovery::SetResolution(const std::string &deviceName, int width, int height) {
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++) {
auto name = ::deviceName(devices[i]);
ret.push_back(name);
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
if (hr != S_OK) {
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) {
@ -182,6 +273,89 @@ DeviceDiscovery::Resolutions DeviceDiscovery::deviceResolutions(const std::share
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) {
source->AddRef();
auto result = MFCreateSourceReaderFromMediaSource(source, nullptr, &reader);
@ -190,8 +364,8 @@ DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) {
}
}
#else
std::vector<std::string> DeviceDiscovery::devices() {
std::vector<std::string> ret;
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() {
std::vector<Device> ret;
for (const auto &entry : std::filesystem::directory_iterator("/dev")) {
if (entry.is_character_file() && entry.path().string().find("video") != std::string::npos) {
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 ((strstr(reinterpret_cast<const char *>(cap.card), DeviceName) != nullptr) &&
(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);
@ -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) {
auto ret = std::make_shared<Device>();
ret->name = find_video_device_by_name(deviceName);
ret->friendlyName = find_video_device_by_name(deviceName);
return ret;
}
@ -263,9 +440,9 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
} else {
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) {
LOG(error) << "Failed to open device " << device->name;
LOG(error) << "Failed to open device " << device->friendlyName;
} else {
struct v4l2_format 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) {
Resolutions ret;
int fd = open(source->name.c_str(), O_RDWR);
int fd = open(source->friendlyName.c_str(), O_RDWR);
if (fd <= 0) {
LOG(error) << "Failed to open device " << source->name;
LOG(error) << "Failed to open device " << source->friendlyName;
} else {
struct v4l2_fmtdesc fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

View File

@ -8,6 +8,9 @@
#ifdef WIN32
#include <mfidl.h>
#include <mfreadwrite.h>
class IPin;
class IBaseFilter;
#endif
class DeviceDiscovery {
@ -15,25 +18,33 @@ class DeviceDiscovery {
public:
constexpr static auto DeviceName = "UVC Camera";
using Resolution = std::pair<int32_t, int32_t>;
using Resolutions = std::vector<Resolution>;
struct Device {
Device() = default;
std::string friendlyName;
std::string alternativeName;
Resolutions resolutions;
~Device();
#ifdef WIN32
Device(IMFMediaSource *source);
IMFMediaSource *source = nullptr;
IMFSourceReader *reader = nullptr;
#else
std::string name;
#endif
};
using Resolution = std::pair<int32_t, int32_t>;
using Resolutions = std::vector<Resolution>;
DeviceDiscovery();
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);
std::vector<std::string> devices();
std::vector<Device> devices();
bool SetResolution(const std::string &deviceName, int width, int height);
protected:
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__

View File

@ -73,7 +73,7 @@ HOST_TOOLS := /opt/Xuantie-900-gcc-elf-newlib-x86_64-V2.6.1/bin
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 08
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
```

View File

@ -8,7 +8,7 @@ BOOST_AUTO_TEST_CASE(EnumDevice) {
auto device = discovery.find("UVC Camera", error);
auto devices = discovery.devices();
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);

BIN
resources/7za.exe Normal file

Binary file not shown.

View File

@ -0,0 +1,26 @@
掌静脉模组支持掌静脉注册,识别功能,同时也提供通过图片注册掌静脉功能。
常见有两种应用场景:单模组使用,多设备集群分发注册识别。
## 单模组使用
![](./local_use.svg)
在此应用场景下,【门禁面板机】需要完成和【掌静脉模组】通信协议的对接。
当注册用户时门禁面板机向掌静脉模组发送注册命令使模块进入【注册模式】并提示【用户】将【手掌】置于掌静脉模组上方进行注册注册成功后掌静脉模组将会上报一个【唯一标识ID】至门禁面板机面板机需要将该ID与用户进行绑定。
注册成功后门禁面板机可发送指令使模组切换至【识别模式】当用户将手置于掌静脉模组上方模组会和之前注册的掌静脉底库进行比对比对结束之后将会向门禁面板机上报ID以标识此次识别结果此时面板机可依靠此ID和之前记录的注册ID进行比对如果ID相同则表示匹配上该用户。
## 多模组集群使用
![](muti_use.svg)
在此应用场景下,【门禁面板机】需要完成和【掌静脉模组】通行协议的对接,且需要服务器进行掌静脉注册图片管理及分发。
考虑到常见的门禁通行场景,掌静脉模组支持注册时获取掌静脉注册图片,然后再使用该图片进行掌静脉注册。
通过这种方式,用户在注册时,可以通掌静脉注册机获取掌静脉注册图片,然后在将掌静脉注册图片发送至服务器,服务器接收到图片之后,再将图片下发至各个门禁面板机以图片方式进行注册。这样就可以达到一端注册,多端使用的效果。

View File

@ -74,15 +74,17 @@ USBCDC
```c++
struct msg_verify_data {
uint8_t reserved;
uint8_t reserved1;
uint8_t timeout;
uint8_t reserved2[6];
};
```
参数说明:
- reserved保留字段暂未使用。需设置为 0x00。
- reserved1:保留字段,暂未使用。需设置为 0x00。
- timeout识别超时时间默认为10s用户可以自行设置最大不超过255s。**主控等待模组录入应答的超时时间应大于此参数设置值。**
- reserved2保留字段暂未使用。需设置为 0x00。
主控下发消息格式如下:
@ -93,22 +95,23 @@ struct msg_verify_data {
<th>SyncWord</th>
<th>MsgID</th>
<th>DataSize</th>
<th colspan="2">Data</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="2">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>0x02</td>
<td>reserved<br/>(1 byte)</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>
@ -392,6 +395,8 @@ MID_NOTE 消息主要作用是主动向主控上报一些信息,报文格式
2024/10/14V0.1版本,描述模组识别、录入、删除几个基本协议指令。
2024/11/18V0.2版本,`MID_VERIFY` 消息新增字段预留空间,用于功能扩展。

View File

@ -1,6 +1,7 @@
param($type)
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# [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'
if (!(Test-Path $MsvcScript)) { $MsvcScript = 'D:\Program Files\Microsoft Visual Studio\2022\\Professional\Common7\Tools\Launch-VsDevShell.ps1' }
@ -24,8 +25,7 @@ if ($fileContent -match 'project\([^\)]+VERSION\s+([0-9]+\.[0-9]+)') {
Write-Output "未找到版本号"
}
$deployPath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools_v$version"
$zipFilePath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools_v$version.zip"
$deployPath = Join-Path -Path $buildPath -ChildPath "掌静脉工具v$version"
$changelogPath = Join-Path -Path $buildPath -ChildPath "CHANGELOG.txt"
function Build() {
@ -61,6 +61,9 @@ function Deploy() {
& $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"
foreach ($module in $modules) {
@ -83,12 +86,14 @@ function Deploy() {
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) {
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() {

288
resources/local_use.svg Normal file
View 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

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 98 KiB