Compare commits

..

No commits in common. "master" and "v0.4.1" have entirely different histories.

26 changed files with 85 additions and 2174 deletions

View File

@ -6,18 +6,22 @@ on:
- '**' - '**'
tags-ignore: tags-ignore:
- 'v*' - 'v*'
paths:
- '**.cpp'
- '**.h'
- '**.conf'
jobs: 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: Build:
runs-on: [ubuntu-latest, ubuntu-24.04] runs-on: [ubuntu-latest, ubuntu-24.04]
container: container:
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/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: Set up SSH - name: Set up SSH
run: | run: |

View File

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

View File

@ -1,5 +1,5 @@
name: Deploy Release name: Deploy Release
run-name: ${{ github.actor }} is building SmartLockerTools... run-name: ${{ github.actor }} is building Bilby...
on: on:
push: push:
tags: tags:
@ -9,9 +9,6 @@ jobs:
runs-on: [ubuntu-latest, ubuntu-24.04] runs-on: [ubuntu-latest, ubuntu-24.04]
container: container:
image: registry.cn-shenzhen.aliyuncs.com/amass_toolset/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: Set up SSH - name: Set up SSH
run: | run: |

View File

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

View File

@ -119,15 +119,12 @@ QStringList Application::availableSerialPorts() const {
return ret; return ret;
} }
QVariantList Application::availableUsbVideoCameras() const { QStringList Application::availableUsbVideoCameras() const {
QVariantList ret; QStringList ret;
DeviceDiscovery d; DeviceDiscovery d;
auto devices = d.devices(); auto devices = d.devices();
for (auto &device : devices) { for (auto &device : devices) {
QVariantMap item; ret << QString::fromStdString(device);
item.insert("name", QString::fromStdString(device.friendlyName));
item.insert("path", QString::fromStdString(device.alternativeName));
ret << item;
} }
return ret; return ret;
} }
@ -481,7 +478,6 @@ 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("请先打开设备");

View File

@ -43,7 +43,7 @@ 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 QVariantList availableUsbVideoCameras() const; Q_INVOKABLE QStringList 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();

View File

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

View File

@ -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=@device_pnp_" << deviceName; oss << "video=" << deviceName;
auto device = oss.str(); auto device = oss.str();
#else #else
constexpr auto format = "v4l2"; constexpr auto format = "v4l2";
@ -41,10 +41,7 @@ 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);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,9 @@
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
@ -19,10 +15,6 @@ 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

View File

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

View File

@ -1,5 +1,4 @@
#include "BoostLog.h" #include "BoostLog.h"
#include "Configuration.h"
#include "Widget.h" #include "Widget.h"
#include <QApplication> #include <QApplication>
#include <QFont> #include <QFont>
@ -8,14 +7,11 @@ 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(QString("%1 %2").arg(a.applicationName()).arg(a.applicationVersion())); w.setWindowTitle("掌静脉模组升级工具");
w.setMinimumWidth(520); w.setMinimumWidth(520);
w.setMinimumHeight(100); w.setMinimumHeight(100);
w.show(); w.show();

View File

@ -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 strmiids comsuppw> $<$<PLATFORM_ID:Windows>:Mfreadwrite Mf mfplat mfuuid>
PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort PRIVATE Qt${QT_VERSION_MAJOR}::SerialPort
) )

View File

@ -1,15 +1,13 @@
#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>
@ -29,23 +27,6 @@ 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;
@ -53,7 +34,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 = _com_util::ConvertBSTRToString(friendlyName); ret = Amass::StringUtility::wstringToString(std::wstring(friendlyName, nameLength));
} }
if (friendlyName != nullptr) { if (friendlyName != nullptr) {
CoTaskMemFree(friendlyName); CoTaskMemFree(friendlyName);
@ -147,108 +128,36 @@ void DeviceDiscovery::enterOtaMode(const std::shared_ptr<Device> &device, std::e
&llTimeStamp, &pSample); &llTimeStamp, &pSample);
} }
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() { std::vector<std::string> DeviceDiscovery::devices() {
std::vector<Device> devices; std::vector<std::string> ret;
ICreateDevEnum *pDevEnum = nullptr; IMFAttributes *attributes = nullptr;
IEnumMoniker *pEnum = nullptr; boost::scope::scope_exit guard([&attributes] { SafeRelease(&attributes); });
auto result = MFCreateAttributes(&attributes, 1);
HRESULT hr = if (FAILED(result)) {
CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDevEnum); return ret;
if (FAILED(hr)) {
return devices;
} }
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0); result = attributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (hr != S_OK) { if (FAILED(result)) {
pDevEnum->Release(); return ret;
return devices;
} }
IMoniker *pMoniker = nullptr; UINT32 count;
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) { IMFActivate **devices = nullptr;
Device info; result = MFEnumDeviceSources(attributes, &devices, &count);
GetDeviceNames(pMoniker, info); if (FAILED(result)) {
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;
}
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;
} }
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0); if (count == 0) {
if (hr != S_OK) { return ret;
pDevEnum->Release();
return false;
} }
IMoniker *pMoniker = nullptr; for (int i = 0; i < count; i++) {
while (pEnum->Next(1, &pMoniker, nullptr) == S_OK) { auto name = ::deviceName(devices[i]);
IPropertyBag *pPropBag; ret.push_back(name);
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(); return ret;
}
}
}
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) {
@ -273,89 +182,6 @@ 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);
@ -364,8 +190,8 @@ DeviceDiscovery::Device::Device(IMFMediaSource *source) : source(source) {
} }
} }
#else #else
std::vector<DeviceDiscovery::Device> DeviceDiscovery::devices() { std::vector<std::string> DeviceDiscovery::devices() {
std::vector<Device> ret; std::vector<std::string> 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);
@ -377,10 +203,7 @@ std::vector<DeviceDiscovery::Device> 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)) {
Device device; ret.push_back(entry.path().string());
device.friendlyName = entry.path().string();
device.alternativeName = entry.path().string();
ret.push_back(device);
} }
} }
close(fd); close(fd);
@ -417,7 +240,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->friendlyName = find_video_device_by_name(deviceName); ret->name = find_video_device_by_name(deviceName);
return ret; return ret;
} }
@ -440,9 +263,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->friendlyName.c_str(), O_RDWR); int fd = open(device->name.c_str(), O_RDWR);
if (fd <= 0) { if (fd <= 0) {
LOG(error) << "Failed to open device " << device->friendlyName; LOG(error) << "Failed to open device " << device->name;
} else { } else {
struct v4l2_format format; struct v4l2_format format;
memset(&format, 0, sizeof(format)); memset(&format, 0, sizeof(format));
@ -520,9 +343,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->friendlyName.c_str(), O_RDWR); int fd = open(source->name.c_str(), O_RDWR);
if (fd <= 0) { if (fd <= 0) {
LOG(error) << "Failed to open device " << source->friendlyName; LOG(error) << "Failed to open device " << source->name;
} else { } else {
struct v4l2_fmtdesc fmt; struct v4l2_fmtdesc fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

View File

@ -8,9 +8,6 @@
#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 {
@ -18,33 +15,25 @@ 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<Device> devices(); std::vector<std::string> 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__

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.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 09 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
``` ```

View File

@ -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).friendlyName; LOG(info) << "device[" << i << "] " << devices.at(i);
} }
discovery.enterOtaMode(device, error); discovery.enterOtaMode(device, error);

Binary file not shown.

View File

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

View File

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

View File

@ -1,7 +1,6 @@
param($type) 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' $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' }
@ -25,7 +24,8 @@ if ($fileContent -match 'project\([^\)]+VERSION\s+([0-9]+\.[0-9]+)') {
Write-Output "未找到版本号" Write-Output "未找到版本号"
} }
$deployPath = Join-Path -Path $buildPath -ChildPath "掌静脉工具v$version" $deployPath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools_v$version"
$zipFilePath = Join-Path -Path $buildPath -ChildPath "SmartLockerTools_v$version.zip"
$changelogPath = Join-Path -Path $buildPath -ChildPath "CHANGELOG.txt" $changelogPath = Join-Path -Path $buildPath -ChildPath "CHANGELOG.txt"
function Build() { function Build() {
@ -61,9 +61,6 @@ function Deploy() {
& $qtHome\bin\windeployqt.exe $deployPath\$oldName --qmldir=$qtHome\qml & $qtHome\bin\windeployqt.exe $deployPath\$oldName --qmldir=$qtHome\qml
Rename-Item -Path $deployPath\$oldName -NewName $deployPath\$newName 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) {
@ -86,14 +83,12 @@ function Deploy() {
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", "avformat-61", "avutil-59", "postproc-58", "swresample-5", "swscale-8" # avfilter-10 $ffmpegs = "avcodec-61", "avdevice-61", "avfilter-10", "avformat-61", "avutil-59", "postproc-58", "swresample-5", "swscale-8"
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
} }
$zipFilePath = Join-Path -Path $buildPath -ChildPath "掌静脉工具v$version.7z" Compress-Archive -Path $deployPath -DestinationPath $zipFilePath -Force
# Compress-Archive -Path $deployPath -DestinationPath $zipFilePath -Force
& resources\7za.exe a -t7z -mx=9 $zipFilePath $deployPath
} }
function Clean() { function Clean() {

View File

@ -1,288 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 98 KiB