add data collect.

This commit is contained in:
luocai 2024-08-21 09:26:06 +08:00
parent 5adb0d783d
commit 6289b66ac5
23 changed files with 746 additions and 109 deletions

View File

@ -9,7 +9,8 @@
Application::Application(int &argc, char **argv) Application::Application(int &argc, char **argv)
: m_app(std::make_shared<QGuiApplication>(argc, argv)), m_videoFrameProvider(new VideoFrameProvider()), : m_app(std::make_shared<QGuiApplication>(argc, argv)), m_videoFrameProvider(new VideoFrameProvider()),
m_player(std::make_shared<H264Palyer>()), m_devices(new DeviceListModel(this)) { m_player(std::make_shared<H264Palyer>()), m_devices(new DeviceListModel(this)),
m_collector(new DataCollection(this)) {
QFont font; QFont font;
font.setPointSize(16); font.setPointSize(16);
m_app->setFont(font); m_app->setFont(font);
@ -149,25 +150,27 @@ void Application::updateNetworkInfomation(bool dhcp, const QString &ip, const QS
void Application::connectToDevice(int index) { void Application::connectToDevice(int index) {
if (!m_device.expired()) { if (!m_device.expired()) {
auto device = m_device.lock(); auto device = m_device.lock();
disconnect(device.get(), &DeviceConnection::currentOpenDoorAreaChanged, this, disconnect(device.get(), &DeviceConnection::openDoorAreaChanged, this, &Application::onDeviceOpenDoorArea);
&Application::onDeviceOpenDoorArea); disconnect(device.get(), &DeviceConnection::shieldedAreaChanged, this, &Application::onDeviceShieldedArea);
disconnect(device.get(), &DeviceConnection::currentShieldedAreaChanged, this, disconnect(device.get(), &DeviceConnection::antiClipAreaChanged, this, &Application::onDeviceAntiClipArea);
&Application::onDeviceShieldedArea); disconnect(device.get(), &DeviceConnection::networkInfomationChanged, this,
disconnect(device.get(), &DeviceConnection::currentAntiClipAreaChanged, this,
&Application::onDeviceAntiClipArea);
disconnect(device.get(), &DeviceConnection::currentNetworkInfomationChanged, this,
&Application::onDeviceNetworkInfomation); &Application::onDeviceNetworkInfomation);
disconnect(device.get(), &DeviceConnection::otaProgressChanged, this,
&Application::currentDeviceOtaProgressChanged);
device->setH264FrameCallback(DeviceConnection::H264FrameCallback()); device->setH264FrameCallback(DeviceConnection::H264FrameCallback());
device->setLiveStreamEnabled(false); device->setLiveStreamEnabled(false);
} }
if (index >= 0) {
auto device = m_devices->device(index); auto device = m_devices->device(index);
m_device = device; m_device = device;
connect(device.get(), &DeviceConnection::currentOpenDoorAreaChanged, this, &Application::onDeviceOpenDoorArea); connect(device.get(), &DeviceConnection::openDoorAreaChanged, this, &Application::onDeviceOpenDoorArea);
connect(device.get(), &DeviceConnection::currentShieldedAreaChanged, this, &Application::onDeviceShieldedArea); connect(device.get(), &DeviceConnection::shieldedAreaChanged, this, &Application::onDeviceShieldedArea);
connect(device.get(), &DeviceConnection::currentAntiClipAreaChanged, this, &Application::onDeviceAntiClipArea); connect(device.get(), &DeviceConnection::antiClipAreaChanged, this, &Application::onDeviceAntiClipArea);
connect(device.get(), &DeviceConnection::currentNetworkInfomationChanged, this, connect(device.get(), &DeviceConnection::networkInfomationChanged, this,
&Application::onDeviceNetworkInfomation); &Application::onDeviceNetworkInfomation);
connect(device.get(), &DeviceConnection::otaProgressChanged, this,
&Application::currentDeviceOtaProgressChanged);
device->setH264FrameCallback([this](const char *data, uint32_t size) { device->setH264FrameCallback([this](const char *data, uint32_t size) {
auto image = m_player->decode((const uint8_t *)data, size); auto image = m_player->decode((const uint8_t *)data, size);
if (image) { if (image) {
@ -193,6 +196,12 @@ void Application::connectToDevice(int index) {
emit currentAntiClipAreaEnabledChanged(); emit currentAntiClipAreaEnabledChanged();
emit currentNetworkInfomationChanged(); emit currentNetworkInfomationChanged();
} }
}
void Application::startSearchDevice() {
connectToDevice(-1);
m_devices->startSearchDevice();
}
void Application::upgradeDevice(const QString &file) { void Application::upgradeDevice(const QString &file) {
if (!m_device.expired()) { if (!m_device.expired()) {

View File

@ -1,6 +1,7 @@
#ifndef APPLICATION_H #ifndef APPLICATION_H
#define APPLICATION_H #define APPLICATION_H
#include "DataCollection.h"
#include "DataStructure.h" #include "DataStructure.h"
#include "DeviceConnection.h" #include "DeviceConnection.h"
#include "DeviceListModel.h" #include "DeviceListModel.h"
@ -20,6 +21,8 @@ class Application : public QObject {
Q_PROPERTY(int DeviceWidth MEMBER DeviceWidth CONSTANT FINAL) Q_PROPERTY(int DeviceWidth MEMBER DeviceWidth CONSTANT FINAL)
Q_PROPERTY(int DeviceHeight MEMBER DeviceHeight CONSTANT FINAL) Q_PROPERTY(int DeviceHeight MEMBER DeviceHeight CONSTANT FINAL)
Q_PROPERTY(DeviceListModel *devices MEMBER m_devices CONSTANT FINAL); Q_PROPERTY(DeviceListModel *devices MEMBER m_devices CONSTANT FINAL);
Q_PROPERTY(DataCollection *collector MEMBER m_collector CONSTANT FINAL);
Q_PROPERTY(DeviceConnection::AreaWay currentOpenDoorAreaWay READ currentOpenDoorAreaWay WRITE Q_PROPERTY(DeviceConnection::AreaWay currentOpenDoorAreaWay READ currentOpenDoorAreaWay WRITE
setCurrentOpenDoorAreaWay NOTIFY currentOpenDoorAreaWayChanged) setCurrentOpenDoorAreaWay NOTIFY currentOpenDoorAreaWayChanged)
Q_PROPERTY(QList<QPointF> currentOpenDoorAreaPoints READ currentOpenDoorAreaPoints WRITE Q_PROPERTY(QList<QPointF> currentOpenDoorAreaPoints READ currentOpenDoorAreaPoints WRITE
@ -65,6 +68,7 @@ public:
const QString &gateway); const QString &gateway);
Q_INVOKABLE void connectToDevice(int index); Q_INVOKABLE void connectToDevice(int index);
Q_INVOKABLE void upgradeDevice(const QString &file); Q_INVOKABLE void upgradeDevice(const QString &file);
Q_INVOKABLE void startSearchDevice();
int exec(); int exec();
static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine);
@ -78,6 +82,7 @@ signals:
void currentShieldedAreaEnabledChanged(); void currentShieldedAreaEnabledChanged();
void currentAntiClipAreaEnabledChanged(); void currentAntiClipAreaEnabledChanged();
void currentNetworkInfomationChanged(); void currentNetworkInfomationChanged();
void currentDeviceOtaProgressChanged(bool status, int progress, const QString &message);
protected: protected:
Application(int &argc, char **argv); Application(int &argc, char **argv);
@ -92,6 +97,7 @@ private:
std::shared_ptr<H264Palyer> m_player; std::shared_ptr<H264Palyer> m_player;
std::weak_ptr<DeviceConnection> m_device; std::weak_ptr<DeviceConnection> m_device;
DeviceListModel *m_devices = nullptr; DeviceListModel *m_devices = nullptr;
DataCollection *m_collector = nullptr;
DeviceConnection::AreaWay m_currentOpenDoorAreaWay = DeviceConnection::Diabled; DeviceConnection::AreaWay m_currentOpenDoorAreaWay = DeviceConnection::Diabled;
QList<QPointF> m_currentOpenDoorAreaPoints; QList<QPointF> m_currentOpenDoorAreaPoints;

View File

@ -21,7 +21,7 @@ set(FFmpeg_INCLUDE_DIR ${FFmpeg_ROOT}/include)
set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib) set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib)
find_package(Boost REQUIRED COMPONENTS json) find_package(Boost REQUIRED COMPONENTS json)
find_package(Qt6 REQUIRED COMPONENTS Qml Quick Network) find_package(Qt6 REQUIRED COMPONENTS Qml Quick Network QuickControls2)
qt_standard_project_setup(REQUIRES 6.5) qt_standard_project_setup(REQUIRES 6.5)
@ -37,6 +37,7 @@ qt_add_executable(AntiClipSettings
main.cpp main.cpp
Application.h Application.cpp Application.h Application.cpp
DataStructure.h DataStructure.h
DataCollection.h DataCollection.cpp
DeviceConnection.h DeviceConnection.cpp DeviceConnection.h DeviceConnection.cpp
DeviceListModel.h DeviceListModel.cpp DeviceListModel.h DeviceListModel.cpp
H264Palyer.h H264Palyer.cpp H264Palyer.h H264Palyer.cpp
@ -46,10 +47,19 @@ qt_add_executable(AntiClipSettings
qt_add_qml_module(AntiClipSettings qt_add_qml_module(AntiClipSettings
URI AntiClipSettings URI AntiClipSettings
QML_FILES QML_FILES
Main.qml qml/Main.qml
DeviceView.qml qml/DeviceView.qml
NetworkSettingPopup.qml qml/IconButton.qml
OtaPopup.qml qml/MessageDialog.qml
qml/NetworkSettingPopup.qml
qml/OtaPopup.qml
qml/StatusTip.qml
qml/DataCollectionPopup.qml
RESOURCES
resources/popup_close.svg
resources/prompt_delete.svg
resources/successfull.svg
resources/warning.svg
) )
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
@ -78,6 +88,7 @@ target_link_directories(AntiClipSettings
target_link_libraries(AntiClipSettings target_link_libraries(AntiClipSettings
PRIVATE Qt6::Qml PRIVATE Qt6::Qml
PRIVATE Qt6::Quick PRIVATE Qt6::Quick
PRIVATE Qt6::QuickControls2
PRIVATE Qt6::Network PRIVATE Qt6::Network
PRIVATE Boost::json PRIVATE Boost::json
PRIVATE avcodec PRIVATE avcodec

90
DataCollection.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "DataCollection.h"
#include "BoostLog.h"
#include <QFile>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
DataCollection::DataCollection(QObject *parent) : QObject{parent} {
m_manager = new QNetworkAccessManager(this);
}
void DataCollection::start(const QString &address) {
m_address = address;
m_enabled = true;
emit enabledChanged();
start();
}
void DataCollection::stop() {
m_enabled = false;
emit enabledChanged();
}
QString DataCollection::path() const {
return m_path;
}
void DataCollection::setPath(const QString &path) {
// file:///E:/Downloads/logs
auto p = path;
if (p.startsWith("file:///")) {
p.remove(0, 8);
}
if (m_path != p) {
m_path = p;
emit pathChanged();
LOG(info) << "set data path: " << m_path.toStdWString();
}
}
bool DataCollection::enabled() const {
return m_enabled;
}
void DataCollection::start() {
auto url = QString("http://%1:8080/capture.do").arg(m_address);
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(url);
boost::json::object data;
data["devid"] = 1;
auto content = boost::json::serialize(data);
QNetworkReply *reply = m_manager->post(request, QString::fromStdString(content).toLocal8Bit());
connect(reply, &QNetworkReply::finished, this, &DataCollection::onCaptureFinished);
}
void DataCollection::onCaptureFinished() {
auto reply = dynamic_cast<QNetworkReply *>(sender());
reply->deleteLater();
auto text = reply->readAll().toStdString();
auto replyVale = boost::json::parse(text);
auto root = replyVale.as_object();
m_filename = QString::fromStdString(std::string(root.at("path").as_string()));
auto url = QString("http://%1:8080/%2").arg(m_address, m_filename);
QNetworkRequest request;
request.setUrl(url);
QNetworkReply *dataReply = m_manager->get(request);
connect(dataReply, &QNetworkReply::finished, this, &DataCollection::onDataGetFinished);
}
void DataCollection::onDataGetFinished() {
auto reply = dynamic_cast<QNetworkReply *>(sender());
auto data = reply->readAll();
LOG(info) << "error: " << reply->error() << ", capture data size: " << data.size();
QFile file(m_path + "/" + m_filename);
file.open(QIODevice::WriteOnly);
file.write(data);
file.close();
if (m_enabled) {
QTimer::singleShot(0, this, [this]() { start(); });
}
}

43
DataCollection.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef DATACOLLECTION_H
#define DATACOLLECTION_H
#include <QObject>
#include <QQmlEngine>
class QNetworkAccessManager;
class DataCollection : public QObject {
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("Only created in C++...")
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
public:
explicit DataCollection(QObject *parent = nullptr);
Q_INVOKABLE void start(const QString &address);
Q_INVOKABLE void stop();
QString path() const;
void setPath(const QString &path);
bool enabled() const;
signals:
void pathChanged();
void enabledChanged();
protected:
void start();
void onCaptureFinished();
void onDataGetFinished();
private:
QNetworkAccessManager *m_manager = nullptr;
bool m_enabled = false;
QString m_address;
QString m_path;
QString m_filename;
};
#endif // DATACOLLECTION_H

View File

@ -2,6 +2,7 @@
#include "BoostLog.h" #include "BoostLog.h"
#include "StringUtility.h" #include "StringUtility.h"
#include <QPointF> #include <QPointF>
#include <QTimer>
#include <WinSock2.h> #include <WinSock2.h>
#include <boost/json/object.hpp> #include <boost/json/object.hpp>
#include <boost/json/parse.hpp> #include <boost/json/parse.hpp>
@ -13,8 +14,25 @@
DeviceConnection::DeviceConnection(QObject *parent) : QObject{parent} { DeviceConnection::DeviceConnection(QObject *parent) : QObject{parent} {
} }
void DeviceConnection::connect(const QString &address) { DeviceConnection::Infomation DeviceConnection::infomation() const {
return m_infomation;
}
bool DeviceConnection::isConnected() const {
bool ret = false;
if (m_commandSocket != nullptr) {
// LOG(info) << "DeviceConnection::isConnected " << m_commandSocket->isValid();
ret = m_commandSocket->isValid() && (m_commandSocket->state() == QTcpSocket::ConnectedState);
}
return ret;
}
void DeviceConnection::connect(const Infomation &infomation) {
m_infomation = infomation;
m_commandSocket = new QTcpSocket(this); m_commandSocket = new QTcpSocket(this);
QObject::connect(m_commandSocket, &QTcpSocket::disconnected, this, &DeviceConnection::disconnected);
QObject::connect(m_commandSocket, &QTcpSocket::errorOccurred, this, &DeviceConnection::onErrorOccurred);
m_h264Socket = new QTcpSocket(this); m_h264Socket = new QTcpSocket(this);
QObject::connect(m_commandSocket, &QTcpSocket::connected, this, &DeviceConnection::onConnected); QObject::connect(m_commandSocket, &QTcpSocket::connected, this, &DeviceConnection::onConnected);
@ -22,14 +40,22 @@ void DeviceConnection::connect(const QString &address) {
QObject::connect(m_h264Socket, &QTcpSocket::readyRead, this, &DeviceConnection::onH264ReadyRead); QObject::connect(m_h264Socket, &QTcpSocket::readyRead, this, &DeviceConnection::onH264ReadyRead);
QObject::connect(m_commandSocket, &QTcpSocket::readyRead, this, &DeviceConnection::onCommandReadyRead); QObject::connect(m_commandSocket, &QTcpSocket::readyRead, this, &DeviceConnection::onCommandReadyRead);
LOG(info) << "connect to " << infomation.ip.toStdString();
m_commandSocket->connectToHost(address, 8000); m_commandSocket->connectToHost(infomation.ip, 8000);
m_h264Socket->connectToHost(address, 8000); m_h264Socket->connectToHost(infomation.ip, 8000);
} }
void DeviceConnection::start() { DeviceConnection::Area DeviceConnection::area() const {
return m_area;
}
NetworkInfomation DeviceConnection::networkInfomation() const {
return m_networkInfomation;
}
void DeviceConnection::setLiveStreamEnabled(bool enabled) {
boost::json::object request; boost::json::object request;
request["func"] = "openlivestream_setdata"; request["func"] = enabled ? "openlivestream_setdata" : "closelivestream_setdata";
request["deviceid"] = "0"; request["deviceid"] = "0";
boost::json::object data; boost::json::object data;
@ -236,11 +262,13 @@ void DeviceConnection::updateNetworkInfomation(bool dhcp, const QString &ip, con
} }
void DeviceConnection::requestOta(const QString &file) { void DeviceConnection::requestOta(const QString &file) {
LOG(info) << file.toStdString(); m_otaProgress = 0;
emit otaProgressChanged(true, m_otaProgress, "正在向设备发起OTA请求......");
auto task = [this, file]() { auto task = [this, file]() {
std::ifstream ifs(Amass::StringUtility::UTF8ToGBK(file.toStdString()), std::ifstream::binary); std::ifstream ifs(Amass::StringUtility::UTF8ToGBK(file.toStdString()), std::ifstream::binary);
m_uploadBuffer = std::vector<uint8_t>((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); m_uploadBuffer = std::vector<uint8_t>((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
m_sendedSize = 0;
unsigned char md5[16]; unsigned char md5[16];
mbedtls_md5_context context; mbedtls_md5_context context;
@ -259,13 +287,13 @@ void DeviceConnection::requestOta(const QString &file) {
request["func"] = "a22devicefirmware_setdata"; request["func"] = "a22devicefirmware_setdata";
request["deviceid"] = "0"; request["deviceid"] = "0";
boost::json::object data; boost::json::object data;
data["target_linux04_firmware"] = "RD_T009_V21R003B001"; data["target_linux04_firmware"] = "RD_T009_V21R003B002";
data["datasize"] = std::filesystem::file_size(file.toStdString()); data["datasize"] = std::filesystem::file_size(file.toStdString());
data["md5"] = oss.str(); data["md5"] = oss.str();
request["data"] = std::move(data); request["data"] = std::move(data);
auto text = boost::json::serialize(request); auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size()); m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestOta: " << text; LOG(info) << "requestOta: " << text << ": " << text.size();
}; };
if (m_requests.empty()) { if (m_requests.empty()) {
task(); task();
@ -273,6 +301,48 @@ void DeviceConnection::requestOta(const QString &file) {
m_requests.push(task); m_requests.push(task);
} }
void DeviceConnection::transferBinContent() {
constexpr int SliceSize = 1024;
constexpr int WaitMd5CheckTime = 3000; // ms
if (m_sendedSize >= m_uploadBuffer.size()) return;
char buffer[1 + sizeof(int32_t) + 1024];
int sendSize = SliceSize;
if ((m_sendedSize + SliceSize) > m_uploadBuffer.size()) {
sendSize = m_uploadBuffer.size() - m_sendedSize;
}
memcpy(buffer + 1 + sizeof(int32_t), m_uploadBuffer.data() + m_sendedSize, sendSize);
buffer[0] = ':';
auto contentSize = reinterpret_cast<int32_t *>(&buffer[1]);
*contentSize = htonl(sendSize);
m_commandSocket->write(buffer, sendSize + 1 + sizeof(uint32_t));
m_sendedSize += sendSize;
auto fileProgress = static_cast<float>(m_sendedSize) / m_uploadBuffer.size();
m_otaProgress = 2 + 95 * fileProgress;
emit otaProgressChanged(true, m_otaProgress, "向设备发送OTA升级文件......");
if (m_sendedSize < m_uploadBuffer.size()) {
QTimer::singleShot(0, this, &DeviceConnection::transferBinContent);
} else if (m_sendedSize >= m_uploadBuffer.size()) {
LOG(info) << "transfer ota file finished, wait " << WaitMd5CheckTime
<< " ms for send check, total sended size: " << m_sendedSize;
QTimer::singleShot(WaitMd5CheckTime, this, [this]() {
boost::json::object request;
request["func"] = "a22devicefirmware_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["target_linux04_firmware"] = "RD_T009_V21R003B001";
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "request md5 check result: " << text;
});
}
}
void DeviceConnection::handleCommand(const std::string_view &replyText) { void DeviceConnection::handleCommand(const std::string_view &replyText) {
boost::system::error_code error; boost::system::error_code error;
auto replyValue = boost::json::parse(replyText, error); auto replyValue = boost::json::parse(replyText, error);
@ -300,7 +370,9 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) {
} else if (value == "2") { } else if (value == "2") {
way = Quadrangle; way = Quadrangle;
} }
emit currentOpenDoorAreaChanged(way, points); m_area.openDoorAreaWay = way;
m_area.openDoorArea = points;
emit openDoorAreaChanged(way, points);
} else if (function == "a03opendoor4_getdata") { } else if (function == "a03opendoor4_getdata") {
auto &data = reply.at("data").as_object(); auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string(); auto &value = data.at("value").as_string();
@ -313,7 +385,9 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) {
point.setY(obj.at("y").as_double()); point.setY(obj.at("y").as_double());
points.push_back(point); points.push_back(point);
} }
emit currentShieldedAreaChanged(value == "1", points); m_area.shieldedAreaEnabled = value == "1";
m_area.shieldedArea = points;
emit shieldedAreaChanged(value == "1", points);
} else if (function == "a03opendoor5_getdata") { } else if (function == "a03opendoor5_getdata") {
auto &data = reply.at("data").as_object(); auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string(); auto &value = data.at("value").as_string();
@ -326,15 +400,47 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) {
point.setY(obj.at("y").as_double()); point.setY(obj.at("y").as_double());
points.push_back(point); points.push_back(point);
} }
emit currentAntiClipAreaChanged(value == "1", points); m_area.antiClipAreaEnabled = value == "1";
m_area.antiClipArea = points;
emit antiClipAreaChanged(value == "1", points);
} else if (function == "netconfig_getdata") { } else if (function == "netconfig_getdata") {
auto &data = reply.at("data").as_object(); auto &data = reply.at("data").as_object();
NetworkInfomation info; m_networkInfomation.dhcp = data.at("type").as_string() == "dhcp";
info.dhcp = data.at("type").as_string() == "dhcp"; m_networkInfomation.ip = data.at("ip").as_string().c_str();
info.ip = data.at("ip").as_string().c_str(); m_networkInfomation.gateway = data.at("gateway").as_string().c_str();
info.gateway = data.at("gateway").as_string().c_str(); m_networkInfomation.netmask = data.at("netmask").as_string().c_str();
info.netmask = data.at("netmask").as_string().c_str(); emit networkInfomationChanged(m_networkInfomation);
emit currentNetworkInfomationChanged(info); } else if (function == "a03opendoor5_setdata") {
requestAntiClipArea();
} else if (function == "a03opendoor4_setdata") {
requestShieldedArea();
} else if (function == "a03opendoor1_setdata") {
requestOpenDoorArea();
} else if (function == "a22devicefirmware_setdata") {
LOG(warning) << "ota reply: " << replyText;
auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string();
if (value == "1") {
m_otaProgress = 1;
emit otaProgressChanged(true, m_otaProgress, "设备已进入OTA升级状态......");
QTimer::singleShot(0, this, [this]() { transferBinContent(); });
} else if (value == "2") {
LOG(info) << "md5 check finished";
m_otaProgress = 98;
emit otaProgressChanged(true, m_otaProgress, "设备正在升级中,请稍后......");
QTimer::singleShot(0, this, [this]() {
m_commandSocket->close();
m_h264Socket->close();
});
QTimer::singleShot(25000, this, [this]() {
LOG(info) << "try connect after ota.";
m_commandSocket->connectToHost(m_infomation.ip, 8000);
m_h264Socket->connectToHost(m_infomation.ip, 8000);
m_needReconnect = true;
});
}
} else {
LOG(warning) << "unknown reply: " << replyText;
} }
} }
@ -346,8 +452,16 @@ void DeviceConnection::onConnected() {
requestShieldedArea(); requestShieldedArea();
requestAntiClipArea(); requestAntiClipArea();
requestNetworkInfomation(); requestNetworkInfomation();
emit connected();
// m_timerId = startTimer(2500);
if (m_otaProgress > 0) {
m_otaProgress = 100;
emit otaProgressChanged(true, m_otaProgress, "设备升级成功!");
}
} else if (socket == m_h264Socket) { } else if (socket == m_h264Socket) {
start(); if (m_needReconnect) {
setLiveStreamEnabled(true);
}
} }
} }
@ -383,12 +497,31 @@ void DeviceConnection::onCommandReadyRead() {
while (!m_commandBuffer.isEmpty()) { while (!m_commandBuffer.isEmpty()) {
auto packageSize = ntohl(*reinterpret_cast<uint32_t *>(m_commandBuffer.data())); auto packageSize = ntohl(*reinterpret_cast<uint32_t *>(m_commandBuffer.data()));
if (m_commandBuffer.size() < (packageSize + sizeof(uint32_t))) break; if (m_commandBuffer.size() < (packageSize + sizeof(uint32_t))) break;
LOG(info) << "h264 reply: " << m_commandBuffer.data() + sizeof(uint32_t);
handleCommand(std::string_view(m_commandBuffer.data() + sizeof(uint32_t), packageSize)); handleCommand(std::string_view(m_commandBuffer.data() + sizeof(uint32_t), packageSize));
m_commandBuffer.remove(0, packageSize + sizeof(uint32_t)); m_commandBuffer.remove(0, packageSize + sizeof(uint32_t));
if (!m_requests.empty()) {
m_requests.pop(); m_requests.pop();
if (!m_requests.empty()) { if (!m_requests.empty()) {
m_requests.front()(); m_requests.front()();
} }
} }
} }
}
void DeviceConnection::onErrorOccurred(QAbstractSocket::SocketError socketError) {
auto socket = dynamic_cast<QTcpSocket *>(sender());
qDebug() << "DeviceConnection::onErrorOccurred" << socketError;
if (m_needReconnect) {
if (socket->state() == QTcpSocket::UnconnectedState) {
LOG(info) << "try reconnect after ota.";
socket->connectToHost(m_infomation.ip, 8000);
}
}
}
void DeviceConnection::timerEvent(QTimerEvent *event) {
if (isConnected()) {
requestOpenDoorArea();
}
}

View File

@ -25,12 +25,39 @@ public:
FullArea, FullArea,
Quadrangle, // 四边形 Quadrangle, // 四边形
}; };
class Infomation {
public:
QString deviceId;
QString softwareVersion;
QString firmwareVersion;
QString ip;
};
Q_ENUM(AreaWay) Q_ENUM(AreaWay)
class Area {
public:
QList<QPointF> openDoorArea;
AreaWay openDoorAreaWay;
QList<QPointF> shieldedArea;
bool shieldedAreaEnabled;
QList<QPointF> antiClipArea;
bool antiClipAreaEnabled;
};
using H264FrameCallback = std::function<void(const char *data, uint32_t size)>; using H264FrameCallback = std::function<void(const char *data, uint32_t size)>;
explicit DeviceConnection(QObject *parent = nullptr); explicit DeviceConnection(QObject *parent = nullptr);
Infomation infomation() const;
bool isConnected() const;
void setH264FrameCallback(H264FrameCallback &&callback); void setH264FrameCallback(H264FrameCallback &&callback);
void connect(const QString &address); void connect(const Infomation &infomation);
void start();
void setLiveStreamEnabled(bool enabled);
NetworkInfomation networkInfomation() const;
Area area() const;
void requestOpenDoorArea(); void requestOpenDoorArea();
void updateOpenDoorAreaPoints(AreaWay way, const QList<QPointF> &points); void updateOpenDoorAreaPoints(AreaWay way, const QList<QPointF> &points);
void requestShieldedArea(); void requestShieldedArea();
@ -41,13 +68,25 @@ public:
void requestNetworkInfomation(); void requestNetworkInfomation();
void updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, const QString &gateway); void updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, const QString &gateway);
/**
* @brief OTA,
* 1. OTA请求 1
* 2. 2-97
* 3. 98
* 4. 99
*
* @param file
*/
void requestOta(const QString &file); void requestOta(const QString &file);
signals: signals:
void currentOpenDoorAreaChanged(AreaWay way, const QList<QPointF> &points); void connected();
void currentShieldedAreaChanged(bool enabled, const QList<QPointF> &points); void disconnected();
void currentAntiClipAreaChanged(bool enabled, const QList<QPointF> &points); void openDoorAreaChanged(AreaWay way, const QList<QPointF> &points);
void currentNetworkInfomationChanged(const NetworkInfomation &info); void shieldedAreaChanged(bool enabled, const QList<QPointF> &points);
void antiClipAreaChanged(bool enabled, const QList<QPointF> &points);
void networkInfomationChanged(const NetworkInfomation &info);
void otaProgressChanged(bool status, int progress, const QString &message);
protected: protected:
void onConnected(); void onConnected();
@ -55,8 +94,11 @@ protected:
void onCommandReadyRead(); void onCommandReadyRead();
void handleCommand(const std::string_view &replyText); void handleCommand(const std::string_view &replyText);
void onErrorOccurred(QAbstractSocket::SocketError socketError); void onErrorOccurred(QAbstractSocket::SocketError socketError);
void timerEvent(QTimerEvent *event) final;
void transferBinContent();
private: private:
Infomation m_infomation;
QTcpSocket *m_commandSocket = nullptr; QTcpSocket *m_commandSocket = nullptr;
QTcpSocket *m_h264Socket = nullptr; QTcpSocket *m_h264Socket = nullptr;
@ -64,9 +106,17 @@ private:
QByteArray m_commandBuffer; QByteArray m_commandBuffer;
QByteArray m_h264Buffer; QByteArray m_h264Buffer;
int m_otaProgress = 0;
bool m_needReconnect = false;
std::vector<uint8_t> m_uploadBuffer; std::vector<uint8_t> m_uploadBuffer;
int m_sendedSize = 0;
H264FrameCallback m_frameCallback; H264FrameCallback m_frameCallback;
std::queue<std::function<void()>> m_requests; std::queue<std::function<void()>> m_requests;
int m_timerId = -1;
Area m_area;
NetworkInfomation m_networkInfomation;
}; };
#endif // DEVICECONNECTION_H #endif // DEVICECONNECTION_H

View File

@ -73,9 +73,6 @@ void DeviceListModel::startSearchDevice() {
return; return;
} }
beginResetModel(); beginResetModel();
for (auto &device : m_devices) {
device->deleteLater();
}
m_devices.clear(); m_devices.clear();
endResetModel(); endResetModel();
auto interfaces = QNetworkInterface::allInterfaces(); auto interfaces = QNetworkInterface::allInterfaces();

View File

@ -31,7 +31,7 @@ public:
QHash<int, QByteArray> roleNames() const final; QHash<int, QByteArray> roleNames() const final;
Q_INVOKABLE QVariantMap get(int index) const; Q_INVOKABLE QVariantMap get(int index) const;
std::shared_ptr<DeviceConnection> device(int index); std::shared_ptr<DeviceConnection> device(int index);
Q_INVOKABLE void startSearchDevice(); void startSearchDevice();
bool isSearching() const; bool isSearching() const;
float searchProgress() const; float searchProgress() const;

View File

@ -7,6 +7,9 @@ VideoFrameProvider::VideoFrameProvider()
QImage VideoFrameProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { QImage VideoFrameProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) {
Q_UNUSED(id); Q_UNUSED(id);
if (id == "black") {
m_image.fill(Qt::black);
}
if (size) *size = m_image.size(); if (size) *size = m_image.size();

View File

@ -3,6 +3,7 @@
#include "Configuration.h" #include "Configuration.h"
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQuickStyle>
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
using namespace Amass; using namespace Amass;
@ -12,5 +13,7 @@ int main(int argc, char *argv[]) {
LOG(info) << "Program version: " << APP_VERSION << std::endl; LOG(info) << "Program version: " << APP_VERSION << std::endl;
auto app = Singleton<Application>::instance<Construct>(argc, argv); auto app = Singleton<Application>::instance<Construct>(argc, argv);
QQuickStyle::setStyle("Basic");
return app->exec(); return app->exec();
} }

View File

@ -0,0 +1,6 @@
import QtQuick
import QtQuick.Controls
Popup {
}

View File

@ -5,6 +5,7 @@ import AntiClipSettings
Item { Item {
id: root id: root
property alias enabled: shieldedRow.enabled
property int dargWidth: 12 property int dargWidth: 12
property color openDoorAreaColor: "green" property color openDoorAreaColor: "green"
property var openDoorAreaPoints: [] property var openDoorAreaPoints: []
@ -29,13 +30,16 @@ Item {
property real aspectRatio: 16 / 9 property real aspectRatio: 16 / 9
width: Math.min(parent.width, parent.height * aspectRatio) width: Math.min(parent.width, parent.height * aspectRatio)
height: width / aspectRatio height: width / aspectRatio
enabled: root.enabled
Canvas { Canvas {
id: canvas id: canvas
anchors.fill: parent anchors.fill: parent
onPaint: { onPaint: {
var ctx = canvas.getContext("2d") var ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.clearRect(0, 0, canvas.width, canvas.height)
if(!root.enabled)return
if(openDoorAreaWay == DeviceConnection.FullArea){ if(openDoorAreaWay == DeviceConnection.FullArea){
ctx.strokeStyle = openDoorAreaColor ctx.strokeStyle = openDoorAreaColor
ctx.lineWidth = 8 ctx.lineWidth = 8
@ -100,7 +104,7 @@ Item {
delegate: Rectangle { delegate: Rectangle {
width: dargWidth width: dargWidth
height: dargWidth height: dargWidth
visible: openDoorAreaWay>=DeviceConnection.Quadrangle visible: root.enabled &&(openDoorAreaWay>=DeviceConnection.Quadrangle)
color: openDoorAreaColor color: openDoorAreaColor
x: scaledPoint(modelData, canvas.width, x: scaledPoint(modelData, canvas.width,
canvas.height).x - width / 2 canvas.height).x - width / 2
@ -116,7 +120,7 @@ Item {
delegate: Rectangle { delegate: Rectangle {
width: dargWidth width: dargWidth
height: dargWidth height: dargWidth
visible: shieldedAreaEnabled visible: root.enabled && shieldedAreaEnabled
color: shieldedAreaColor color: shieldedAreaColor
x: scaledPoint(modelData, canvas.width, x: scaledPoint(modelData, canvas.width,
canvas.height).x - width / 2 canvas.height).x - width / 2
@ -130,7 +134,7 @@ Item {
visible: antiClipAreaEnabled visible: antiClipAreaEnabled
model: antiClipAreaPoints model: antiClipAreaPoints
delegate: Rectangle { delegate: Rectangle {
visible: antiClipAreaEnabled visible: root.enabled && antiClipAreaEnabled
width: dargWidth width: dargWidth
height: dargWidth height: dargWidth
color: antiClipAreaColor color: antiClipAreaColor
@ -143,6 +147,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: root.enabled
property int draggedOpenDoorAreaPointIndex: -1 property int draggedOpenDoorAreaPointIndex: -1
property int draggedShieldedAreaPointIndex: -1 property int draggedShieldedAreaPointIndex: -1
property int draggedAntiAreaPointIndex: -1 property int draggedAntiAreaPointIndex: -1
@ -242,6 +247,7 @@ Item {
spacing: 10 spacing: 10
Text {text: qsTr("开门区域: ")} Text {text: qsTr("开门区域: ")}
Row { Row {
enabled: root.enabled
RadioButton { RadioButton {
text: "关闭" text: "关闭"
checked: App.currentOpenDoorAreaWay ==DeviceConnection.Diabled checked: App.currentOpenDoorAreaWay ==DeviceConnection.Diabled
@ -268,6 +274,7 @@ Item {
Text {text: qsTr("防夹区域: ")} Text {text: qsTr("防夹区域: ")}
Row { Row {
enabled: root.enabled
RadioButton { RadioButton {
text: "关闭" text: "关闭"
checked: !App.currentAntiClipAreaEnabled checked: !App.currentAntiClipAreaEnabled
@ -321,6 +328,13 @@ Item {
return Qt.point(x, y) return Qt.point(x, y)
} }
onEnabledChanged: {
canvas.requestPaint()
if(!enabled){
image.source = "image://videoframe/black"
}
}
Connections { Connections {
target: App target: App
function onNewVideoFrame() { function onNewVideoFrame() {

12
qml/IconButton.qml Normal file
View File

@ -0,0 +1,12 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
Button {
property alias source: image.source
background: Item {
Image {
id:image
anchors.centerIn: parent
}
}
}

View File

@ -1,9 +1,11 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs
import AntiClipSettings import AntiClipSettings
ApplicationWindow { ApplicationWindow {
id: window
width: 1000 width: 1000
height: 640 height: 640
visible: true visible: true
@ -16,23 +18,19 @@ ApplicationWindow {
text: "搜索设备" text: "搜索设备"
onClicked: { onClicked: {
deviceList.currentIndex = -1 deviceList.currentIndex = -1
App.devices.startSearchDevice() App.startSearchDevice()
} }
} }
Text { Text {
text: `: ${deviceList.count}` text: `: ${deviceList.count}`
} }
Row { Text {
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Text { Layout.preferredWidth: 400
text: qsTr("当前设备版本号: ") text: deviceList.currentIndex
>= 0 ? `: ${App.devices.get(
deviceList.currentIndex).softwareVersion}` : ""
} }
Text {
id: deviceVersion
text: qsTr("RD_T009_V02R001B001")
}
}
} }
} }
@ -72,7 +70,6 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
deviceList.currentIndex = index deviceList.currentIndex = index
} }
onDoubleClicked: { onDoubleClicked: {
networkPopup.open() networkPopup.open()
@ -80,7 +77,7 @@ ApplicationWindow {
} }
} }
onCurrentIndexChanged: { onCurrentIndexChanged: {
deviceVersion.text = App.devices.get(deviceList.currentIndex).softwareVersion; // deviceVersion.text = App.devices.get(deviceList.currentIndex).softwareVersion;
App.connectToDevice(deviceList.currentIndex) App.connectToDevice(deviceList.currentIndex)
} }
ProgressBar { ProgressBar {
@ -100,6 +97,7 @@ ApplicationWindow {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: deviceList.right anchors.left: deviceList.right
anchors.right: parent.right anchors.right: parent.right
enabled: deviceList.currentIndex>=0
openDoorAreaWay: App.currentOpenDoorAreaWay openDoorAreaWay: App.currentOpenDoorAreaWay
openDoorAreaPoints: App.currentOpenDoorAreaPoints openDoorAreaPoints: App.currentOpenDoorAreaPoints
shieldedAreaEnabled: App.currentShieldedAreaEnabled shieldedAreaEnabled: App.currentShieldedAreaEnabled
@ -119,18 +117,61 @@ ApplicationWindow {
id: otaPopup id: otaPopup
} }
FolderDialog {
id: folderDialog
onAccepted: {
App.collector.path = folderDialog.selectedFolder
App.collector.start(App.devices.get(deviceList.currentIndex).ip)
}
}
footer: RowLayout { footer: RowLayout {
width: parent.width width: parent.width
Item {} Item {}
Button { Button {
text: "数据采集" text: App.collector.enabled ? "停止采集" : "数据采集"
onClicked: {
if (App.collector.enabled) {
App.collector.stop()
} else {
if (deviceList.currentIndex < 0) {
showMessageDialog(2, "数据采集", "请先选择设备")
return
}
if (App.collector.path.length <= 0) {
folderDialog.open()
} else {
App.collector.start(App.devices.get(
deviceList.currentIndex).ip)
}
}
}
} }
Item {} Item {}
Button { Button {
text: "升级" text: "升级"
onClicked: otaPopup.open() onClicked: {
if (deviceList.currentIndex < 0) {
showMessageDialog(2, "OTA升级", "请选选择设备")
return
}
otaPopup.open()
}
} }
Item {} Item {}
spacing: (parent.width - (2 * 100)) / 3 spacing: (parent.width - (2 * 100)) / 3
} }
function showMessageDialog(type, title, message) {
let component = Qt.createComponent("MessageDialog.qml")
if (component.status === Component.Ready) {
let dialog = component.createObject(window, {
"type": type,
"height": 200,
"titleText": title,
"text": message
})
dialog.open()
}
}
} }

108
qml/MessageDialog.qml Normal file
View File

@ -0,0 +1,108 @@
import QtQuick
import QtQuick.Controls
Dialog {
id: control
width: 320
parent: Overlay.overlay
anchors.centerIn: Overlay.overlay
modal: true
property alias titleText: titleLabel.text
property alias text: textLabel.text
property alias textFontSize: textLabel.font.pixelSize
property int type: MessageDialog.Type.Successful
closePolicy: Popup.CloseOnEscap | Popup.NoAutoClose
background: Rectangle {
radius: 8
}
enum Type {
Ok,
Successful,
Warning,
Failed
}
IconButton {
anchors.right: parent.right
anchors.rightMargin: 7
anchors.top: parent.top
anchors.topMargin: 10
source: "../resources/popup_close.svg"
onClicked: control.close()
}
Image {
id: image
width: 18
height: 18
anchors.left: parent.left
anchors.leftMargin: 30
anchors.top: parent.top
anchors.topMargin: 40
source: "qrc:/qt/qml/AntiClipSettings/resources/successfull.svg"
}
Text {
id: titleLabel
anchors.left: parent.left
anchors.leftMargin: 60
anchors.top: parent.top
anchors.topMargin: 40
font.family: control.font.family
font.pixelSize: 16
color: "#0A1F44"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Text {
id: textLabel
anchors.left: parent.left
anchors.leftMargin: 60
anchors.right: parent.right
anchors.rightMargin: 40
anchors.top: parent.top
anchors.topMargin: 76
font.family: control.font.family
font.pixelSize: 14
color: "#53627C"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
}
Button {
id: cancelButton
width: 72
height: 26
anchors.right: parent.right
anchors.rightMargin: 15
anchors.bottom: parent.bottom
anchors.bottomMargin: 15
font.family: control.font.family
text: "关闭"
font.pixelSize: 14
contentItem: Text {
text: parent.text
font: parent.font
color: parent.down ? "#FFFFFF" : "#53627C"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
border.color: "#E1E4E8"
border.width: 1
color: parent.down ? "#F25959" : "#FFFFFF"
radius: 2
}
onClicked: control.reject()
}
onTypeChanged: {
if (type === MessageDialog.Type.Successful || type === MessageDialog.Type.Ok) {
image.source = "qrc:/qt/qml/AntiClipSettings/resources/successfull.svg"
} else if (type === MessageDialog.Type.Warning) {
image.source = "qrc:/qt/qml/AntiClipSettings/resources/warning.svg"
} else if(type === MessageDialog.Type.Failed){
// image.source = "qrc:/AntiClipSettings/resources/prompt_delete.svg"
}
}
}

View File

@ -9,8 +9,8 @@ Popup {
id: root id: root
parent: Overlay.overlay parent: Overlay.overlay
anchors.centerIn: Overlay.overlay anchors.centerIn: Overlay.overlay
width: 500 width: 540
height: 200 height: 260
modal: true modal: true
focus: true focus: true
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
@ -96,18 +96,18 @@ Popup {
} }
} }
// Connections { Connections {
// target: App target: App
// function onUpdateFinished() { function onCurrentDeviceOtaProgressChanged (status, progress, message) {
// otaMessage.text = "OTA" progressBar.value = progress
// otaMessage.color = "green" progressText.text = `${progress}%`
// } if(progress>=100){
// function onOtaMessage(message) { otaMessage.text = "OTA升级完成"
// otaMessage.text = message otaMessage.color = "green"
// } }else {
// function onOtaProgressChanged(progress) { otaMessage.text = message
// progressBar.value = progress }
// progressText.text = `${progress}%`
// } }
// } }
} }

55
qml/StatusTip.qml Normal file
View File

@ -0,0 +1,55 @@
import QtQuick
import QtQuick.Controls
Popup{
id: control
property alias text: textItem.text
property alias icon: image.source
property alias color: back.color
property string borderColor
x: (parent.width-200)/2
y: 40
width: 200
height: 32
font.pixelSize: 16
contentItem: Row{
leftPadding: 4
spacing: 9.6
Image {
id: image
anchors.verticalCenter: parent.verticalCenter
}
Text {
id: textItem
anchors.verticalCenter: parent.verticalCenter
text: control.text
font: control.font
color: "#666666"
}
}
background: Rectangle {
id:back
anchors.fill: parent
color: "#EBF8ED"
radius: 3.2
border.width: 1
border.color: control.borderColor
layer.enabled: true
}
Timer {
id: timer
repeat: false
onTriggered: control.visible=false
}
function show(text,timeout){
control.text = text
timer.interval = timeout
timer.restart();
control.visible=true
}
}

15
resources/popup_close.svg Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
<title>Mask</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M6.1785113,5 L8.92258898,7.74407768 C9.24802589,8.06951459 9.24802589,8.59715207 8.92258898,8.92258898 C8.59715207,9.24802589 8.06951459,9.24802589 7.74407768,8.92258898 L5,6.1785113 L2.25592232,8.92258898 C1.93048541,9.24802589 1.40284793,9.24802589 1.07741102,8.92258898 C0.751974106,8.59715207 0.751974106,8.06951459 1.07741102,7.74407768 L3.8214887,5 L1.07741102,2.25592232 C0.751974106,1.93048541 0.751974106,1.40284793 1.07741102,1.07741102 C1.40284793,0.751974106 1.93048541,0.751974106 2.25592232,1.07741102 L5,3.8214887 L7.74407768,1.07741102 C8.06951459,0.751974106 8.59715207,0.751974106 8.92258898,1.07741102 C9.24802589,1.40284793 9.24802589,1.93048541 8.92258898,2.25592232 L6.1785113,5 Z" id="path-1"></path>
</defs>
<g id="下载页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#182C4F" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="17px" viewBox="0 0 18 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
<title>Mask</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M10.7557911,1.21895043 L17.3867068,13.3756291 C17.9156322,14.3453257 17.5583166,15.560199 16.5886199,16.0891245 C16.2948427,16.2493666 15.9655536,16.3333333 15.6309156,16.3333333 L2.36908438,16.3333333 C1.26451488,16.3333333 0.369084379,15.4379028 0.369084379,14.3333333 C0.369084379,13.9986954 0.453051125,13.6694063 0.613293233,13.3756291 L7.24420885,1.21895043 C7.77313431,0.24925376 8.98800759,-0.108061909 9.95770426,0.42086355 C10.2948373,0.604754298 10.5719004,0.881817396 10.7557911,1.21895043 Z M9.07936508,11.75 C8.61912779,11.75 8.24603175,12.123096 8.24603175,12.5833333 C8.24603175,13.0435706 8.61912779,13.4166667 9.07936508,13.4166667 C9.53960237,13.4166667 9.91269841,13.0435706 9.91269841,12.5833333 C9.91269841,12.123096 9.53960237,11.75 9.07936508,11.75 Z M9.07936508,5.5 C8.61912779,5.5 8.24603175,5.87309604 8.24603175,6.33333333 L8.24603175,9.66666667 C8.24603175,10.126904 8.61912779,10.5 9.07936508,10.5 C9.53960237,10.5 9.91269841,10.126904 9.91269841,9.66666667 L9.91269841,6.33333333 C9.91269841,5.87309604 9.53960237,5.5 9.07936508,5.5 Z" id="path-1"></path>
</defs>
<g id="下载页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#F25959" xlink:href="#path-1"></use>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
<title>Combined Shape</title>
<desc>Created with Sketch.</desc>
<g id="下载页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M6.1,11.6 C3.0072054,11.6 0.5,9.0927946 0.5,6 C0.5,2.9072054 3.0072054,0.4 6.1,0.4 C9.1927946,0.4 11.7,2.9072054 11.7,6 C11.7,9.0927946 9.1927946,11.6 6.1,11.6 Z M5.74088496,8.2481404 L9.2254819,4.76354346 C9.44990579,4.53911957 9.44990579,4.17579395 9.2254819,3.95194404 C9.00105802,3.72752015 8.63773239,3.72752015 8.41388248,3.95194404 L5.33508525,7.03074127 L4.28528656,5.98094259 C4.06143665,5.75709268 3.69811103,5.75709268 3.47368714,5.98094259 C3.24983723,6.20536647 3.24983723,6.5686921 3.47368714,6.79311598 L4.92928554,8.2481404 C5.15313545,8.47256429 5.51646107,8.47256429 5.74088496,8.2481404 Z" id="Combined-Shape" fill="#37BD4B"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

17
resources/warning.svg Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="17px" viewBox="0 0 18 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
<title>💚 Icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M10.7557911,2.21895043 L17.3867068,14.3756291 C17.9156322,15.3453257 17.5583166,16.560199 16.5886199,17.0891245 C16.2948427,17.2493666 15.9655536,17.3333333 15.6309156,17.3333333 L2.36908438,17.3333333 C1.26451488,17.3333333 0.369084379,16.4379028 0.369084379,15.3333333 C0.369084379,14.9986954 0.453051125,14.6694063 0.613293233,14.3756291 L7.24420885,2.21895043 C7.77313431,1.24925376 8.98800759,0.891938091 9.95770426,1.42086355 C10.2948373,1.6047543 10.5719004,1.8818174 10.7557911,2.21895043 Z M9.07936508,12.75 C8.61912779,12.75 8.24603175,13.123096 8.24603175,13.5833333 C8.24603175,14.0435706 8.61912779,14.4166667 9.07936508,14.4166667 C9.53960237,14.4166667 9.91269841,14.0435706 9.91269841,13.5833333 C9.91269841,13.123096 9.53960237,12.75 9.07936508,12.75 Z M9.07936508,6.5 C8.61912779,6.5 8.24603175,6.87309604 8.24603175,7.33333333 L8.24603175,10.6666667 C8.24603175,11.126904 8.61912779,11.5 9.07936508,11.5 C9.53960237,11.5 9.91269841,11.126904 9.91269841,10.6666667 L9.91269841,7.33333333 C9.91269841,6.87309604 9.53960237,6.5 9.07936508,6.5 Z" id="path-1"></path>
</defs>
<g id="下载页" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="💚-Icon" transform="translate(0.000000, -1.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#F5A623" xlink:href="#path-1"></use>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB