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)
: 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;
font.setPointSize(16);
m_app->setFont(font);
@ -149,49 +150,57 @@ void Application::updateNetworkInfomation(bool dhcp, const QString &ip, const QS
void Application::connectToDevice(int index) {
if (!m_device.expired()) {
auto device = m_device.lock();
disconnect(device.get(), &DeviceConnection::currentOpenDoorAreaChanged, this,
&Application::onDeviceOpenDoorArea);
disconnect(device.get(), &DeviceConnection::currentShieldedAreaChanged, this,
&Application::onDeviceShieldedArea);
disconnect(device.get(), &DeviceConnection::currentAntiClipAreaChanged, this,
&Application::onDeviceAntiClipArea);
disconnect(device.get(), &DeviceConnection::currentNetworkInfomationChanged, this,
disconnect(device.get(), &DeviceConnection::openDoorAreaChanged, this, &Application::onDeviceOpenDoorArea);
disconnect(device.get(), &DeviceConnection::shieldedAreaChanged, this, &Application::onDeviceShieldedArea);
disconnect(device.get(), &DeviceConnection::antiClipAreaChanged, this, &Application::onDeviceAntiClipArea);
disconnect(device.get(), &DeviceConnection::networkInfomationChanged, this,
&Application::onDeviceNetworkInfomation);
disconnect(device.get(), &DeviceConnection::otaProgressChanged, this,
&Application::currentDeviceOtaProgressChanged);
device->setH264FrameCallback(DeviceConnection::H264FrameCallback());
device->setLiveStreamEnabled(false);
}
auto device = m_devices->device(index);
m_device = device;
if (index >= 0) {
auto device = m_devices->device(index);
m_device = device;
connect(device.get(), &DeviceConnection::currentOpenDoorAreaChanged, this, &Application::onDeviceOpenDoorArea);
connect(device.get(), &DeviceConnection::currentShieldedAreaChanged, this, &Application::onDeviceShieldedArea);
connect(device.get(), &DeviceConnection::currentAntiClipAreaChanged, this, &Application::onDeviceAntiClipArea);
connect(device.get(), &DeviceConnection::currentNetworkInfomationChanged, this,
&Application::onDeviceNetworkInfomation);
device->setH264FrameCallback([this](const char *data, uint32_t size) {
auto image = m_player->decode((const uint8_t *)data, size);
if (image) {
m_videoFrameProvider->setImage(*image);
emit newVideoFrame();
}
});
device->setLiveStreamEnabled(true);
auto area = device->area();
connect(device.get(), &DeviceConnection::openDoorAreaChanged, this, &Application::onDeviceOpenDoorArea);
connect(device.get(), &DeviceConnection::shieldedAreaChanged, this, &Application::onDeviceShieldedArea);
connect(device.get(), &DeviceConnection::antiClipAreaChanged, this, &Application::onDeviceAntiClipArea);
connect(device.get(), &DeviceConnection::networkInfomationChanged, this,
&Application::onDeviceNetworkInfomation);
connect(device.get(), &DeviceConnection::otaProgressChanged, this,
&Application::currentDeviceOtaProgressChanged);
device->setH264FrameCallback([this](const char *data, uint32_t size) {
auto image = m_player->decode((const uint8_t *)data, size);
if (image) {
m_videoFrameProvider->setImage(*image);
emit newVideoFrame();
}
});
device->setLiveStreamEnabled(true);
auto area = device->area();
m_currentOpenDoorAreaWay = area.openDoorAreaWay;
m_currentOpenDoorAreaPoints = area.openDoorArea;
m_currentShieldedAreaEnabled = area.shieldedAreaEnabled;
m_currentShieldedAreaPoints = area.shieldedArea;
m_currentAntiClipAreaEnabled = area.antiClipAreaEnabled;
m_currentAntiClipAreaPoints = area.antiClipArea;
m_currentNetworkInfomation = device->networkInfomation();
emit currentOpenDoorAreaPointsChanged();
emit currentShieldedAreaPointsChanged();
emit currentAntiClipAreaPointsChanged();
emit currentOpenDoorAreaWayChanged();
emit currentShieldedAreaEnabledChanged();
emit currentAntiClipAreaEnabledChanged();
emit currentNetworkInfomationChanged();
m_currentOpenDoorAreaWay = area.openDoorAreaWay;
m_currentOpenDoorAreaPoints = area.openDoorArea;
m_currentShieldedAreaEnabled = area.shieldedAreaEnabled;
m_currentShieldedAreaPoints = area.shieldedArea;
m_currentAntiClipAreaEnabled = area.antiClipAreaEnabled;
m_currentAntiClipAreaPoints = area.antiClipArea;
m_currentNetworkInfomation = device->networkInfomation();
emit currentOpenDoorAreaPointsChanged();
emit currentShieldedAreaPointsChanged();
emit currentAntiClipAreaPointsChanged();
emit currentOpenDoorAreaWayChanged();
emit currentShieldedAreaEnabledChanged();
emit currentAntiClipAreaEnabledChanged();
emit currentNetworkInfomationChanged();
}
}
void Application::startSearchDevice() {
connectToDevice(-1);
m_devices->startSearchDevice();
}
void Application::upgradeDevice(const QString &file) {

View File

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

View File

@ -21,7 +21,7 @@ set(FFmpeg_INCLUDE_DIR ${FFmpeg_ROOT}/include)
set(FFmpeg_LIB_DIR ${FFmpeg_ROOT}/lib)
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)
@ -37,6 +37,7 @@ qt_add_executable(AntiClipSettings
main.cpp
Application.h Application.cpp
DataStructure.h
DataCollection.h DataCollection.cpp
DeviceConnection.h DeviceConnection.cpp
DeviceListModel.h DeviceListModel.cpp
H264Palyer.h H264Palyer.cpp
@ -46,10 +47,19 @@ qt_add_executable(AntiClipSettings
qt_add_qml_module(AntiClipSettings
URI AntiClipSettings
QML_FILES
Main.qml
DeviceView.qml
NetworkSettingPopup.qml
OtaPopup.qml
qml/Main.qml
qml/DeviceView.qml
qml/IconButton.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.
@ -78,6 +88,7 @@ target_link_directories(AntiClipSettings
target_link_libraries(AntiClipSettings
PRIVATE Qt6::Qml
PRIVATE Qt6::Quick
PRIVATE Qt6::QuickControls2
PRIVATE Qt6::Network
PRIVATE Boost::json
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 "StringUtility.h"
#include <QPointF>
#include <QTimer>
#include <WinSock2.h>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
@ -13,8 +14,25 @@
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);
QObject::connect(m_commandSocket, &QTcpSocket::disconnected, this, &DeviceConnection::disconnected);
QObject::connect(m_commandSocket, &QTcpSocket::errorOccurred, this, &DeviceConnection::onErrorOccurred);
m_h264Socket = new QTcpSocket(this);
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_commandSocket, &QTcpSocket::readyRead, this, &DeviceConnection::onCommandReadyRead);
m_commandSocket->connectToHost(address, 8000);
m_h264Socket->connectToHost(address, 8000);
LOG(info) << "connect to " << infomation.ip.toStdString();
m_commandSocket->connectToHost(infomation.ip, 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;
request["func"] = "openlivestream_setdata";
request["func"] = enabled ? "openlivestream_setdata" : "closelivestream_setdata";
request["deviceid"] = "0";
boost::json::object data;
@ -236,11 +262,13 @@ void DeviceConnection::updateNetworkInfomation(bool dhcp, const QString &ip, con
}
void DeviceConnection::requestOta(const QString &file) {
LOG(info) << file.toStdString();
m_otaProgress = 0;
emit otaProgressChanged(true, m_otaProgress, "正在向设备发起OTA请求......");
auto task = [this, file]() {
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_sendedSize = 0;
unsigned char md5[16];
mbedtls_md5_context context;
@ -259,13 +287,13 @@ void DeviceConnection::requestOta(const QString &file) {
request["func"] = "a22devicefirmware_setdata";
request["deviceid"] = "0";
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["md5"] = oss.str();
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestOta: " << text;
LOG(info) << "requestOta: " << text << ": " << text.size();
};
if (m_requests.empty()) {
task();
@ -273,6 +301,48 @@ void DeviceConnection::requestOta(const QString &file) {
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) {
boost::system::error_code error;
auto replyValue = boost::json::parse(replyText, error);
@ -300,7 +370,9 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) {
} else if (value == "2") {
way = Quadrangle;
}
emit currentOpenDoorAreaChanged(way, points);
m_area.openDoorAreaWay = way;
m_area.openDoorArea = points;
emit openDoorAreaChanged(way, points);
} else if (function == "a03opendoor4_getdata") {
auto &data = reply.at("data").as_object();
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());
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") {
auto &data = reply.at("data").as_object();
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());
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") {
auto &data = reply.at("data").as_object();
NetworkInfomation info;
info.dhcp = data.at("type").as_string() == "dhcp";
info.ip = data.at("ip").as_string().c_str();
info.gateway = data.at("gateway").as_string().c_str();
info.netmask = data.at("netmask").as_string().c_str();
emit currentNetworkInfomationChanged(info);
m_networkInfomation.dhcp = data.at("type").as_string() == "dhcp";
m_networkInfomation.ip = data.at("ip").as_string().c_str();
m_networkInfomation.gateway = data.at("gateway").as_string().c_str();
m_networkInfomation.netmask = data.at("netmask").as_string().c_str();
emit networkInfomationChanged(m_networkInfomation);
} 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();
requestAntiClipArea();
requestNetworkInfomation();
emit connected();
// m_timerId = startTimer(2500);
if (m_otaProgress > 0) {
m_otaProgress = 100;
emit otaProgressChanged(true, m_otaProgress, "设备升级成功!");
}
} else if (socket == m_h264Socket) {
start();
if (m_needReconnect) {
setLiveStreamEnabled(true);
}
}
}
@ -383,12 +497,31 @@ void DeviceConnection::onCommandReadyRead() {
while (!m_commandBuffer.isEmpty()) {
auto packageSize = ntohl(*reinterpret_cast<uint32_t *>(m_commandBuffer.data()));
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));
m_commandBuffer.remove(0, packageSize + sizeof(uint32_t));
m_requests.pop();
if (!m_requests.empty()) {
m_requests.front()();
m_requests.pop();
if (!m_requests.empty()) {
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,
Quadrangle, // 四边形
};
class Infomation {
public:
QString deviceId;
QString softwareVersion;
QString firmwareVersion;
QString ip;
};
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)>;
explicit DeviceConnection(QObject *parent = nullptr);
Infomation infomation() const;
bool isConnected() const;
void setH264FrameCallback(H264FrameCallback &&callback);
void connect(const QString &address);
void start();
void connect(const Infomation &infomation);
void setLiveStreamEnabled(bool enabled);
NetworkInfomation networkInfomation() const;
Area area() const;
void requestOpenDoorArea();
void updateOpenDoorAreaPoints(AreaWay way, const QList<QPointF> &points);
void requestShieldedArea();
@ -41,13 +68,25 @@ public:
void requestNetworkInfomation();
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);
signals:
void currentOpenDoorAreaChanged(AreaWay way, const QList<QPointF> &points);
void currentShieldedAreaChanged(bool enabled, const QList<QPointF> &points);
void currentAntiClipAreaChanged(bool enabled, const QList<QPointF> &points);
void currentNetworkInfomationChanged(const NetworkInfomation &info);
void connected();
void disconnected();
void openDoorAreaChanged(AreaWay way, const QList<QPointF> &points);
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:
void onConnected();
@ -55,8 +94,11 @@ protected:
void onCommandReadyRead();
void handleCommand(const std::string_view &replyText);
void onErrorOccurred(QAbstractSocket::SocketError socketError);
void timerEvent(QTimerEvent *event) final;
void transferBinContent();
private:
Infomation m_infomation;
QTcpSocket *m_commandSocket = nullptr;
QTcpSocket *m_h264Socket = nullptr;
@ -64,9 +106,17 @@ private:
QByteArray m_commandBuffer;
QByteArray m_h264Buffer;
int m_otaProgress = 0;
bool m_needReconnect = false;
std::vector<uint8_t> m_uploadBuffer;
int m_sendedSize = 0;
H264FrameCallback m_frameCallback;
std::queue<std::function<void()>> m_requests;
int m_timerId = -1;
Area m_area;
NetworkInfomation m_networkInfomation;
};
#endif // DEVICECONNECTION_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import AntiClipSettings
Item {
id: root
property alias enabled: shieldedRow.enabled
property int dargWidth: 12
property color openDoorAreaColor: "green"
property var openDoorAreaPoints: []
@ -29,13 +30,16 @@ Item {
property real aspectRatio: 16 / 9
width: Math.min(parent.width, parent.height * aspectRatio)
height: width / aspectRatio
enabled: root.enabled
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, canvas.width, canvas.height)
if(!root.enabled)return
if(openDoorAreaWay == DeviceConnection.FullArea){
ctx.strokeStyle = openDoorAreaColor
ctx.lineWidth = 8
@ -100,7 +104,7 @@ Item {
delegate: Rectangle {
width: dargWidth
height: dargWidth
visible: openDoorAreaWay>=DeviceConnection.Quadrangle
visible: root.enabled &&(openDoorAreaWay>=DeviceConnection.Quadrangle)
color: openDoorAreaColor
x: scaledPoint(modelData, canvas.width,
canvas.height).x - width / 2
@ -116,7 +120,7 @@ Item {
delegate: Rectangle {
width: dargWidth
height: dargWidth
visible: shieldedAreaEnabled
visible: root.enabled && shieldedAreaEnabled
color: shieldedAreaColor
x: scaledPoint(modelData, canvas.width,
canvas.height).x - width / 2
@ -130,7 +134,7 @@ Item {
visible: antiClipAreaEnabled
model: antiClipAreaPoints
delegate: Rectangle {
visible: antiClipAreaEnabled
visible: root.enabled && antiClipAreaEnabled
width: dargWidth
height: dargWidth
color: antiClipAreaColor
@ -143,6 +147,7 @@ Item {
MouseArea {
anchors.fill: parent
enabled: root.enabled
property int draggedOpenDoorAreaPointIndex: -1
property int draggedShieldedAreaPointIndex: -1
property int draggedAntiAreaPointIndex: -1
@ -242,6 +247,7 @@ Item {
spacing: 10
Text {text: qsTr("开门区域: ")}
Row {
enabled: root.enabled
RadioButton {
text: "关闭"
checked: App.currentOpenDoorAreaWay ==DeviceConnection.Diabled
@ -268,6 +274,7 @@ Item {
Text {text: qsTr("防夹区域: ")}
Row {
enabled: root.enabled
RadioButton {
text: "关闭"
checked: !App.currentAntiClipAreaEnabled
@ -321,6 +328,13 @@ Item {
return Qt.point(x, y)
}
onEnabledChanged: {
canvas.requestPaint()
if(!enabled){
image.source = "image://videoframe/black"
}
}
Connections {
target: App
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.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import AntiClipSettings
ApplicationWindow {
id: window
width: 1000
height: 640
visible: true
@ -16,23 +18,19 @@ ApplicationWindow {
text: "搜索设备"
onClicked: {
deviceList.currentIndex = -1
App.devices.startSearchDevice()
App.startSearchDevice()
}
}
Text {
text: `: ${deviceList.count}`
}
Row {
Text {
Layout.alignment: Qt.AlignRight
Text {
text: qsTr("当前设备版本号: ")
}
Text {
id: deviceVersion
text: qsTr("RD_T009_V02R001B001")
}
Layout.preferredWidth: 400
text: deviceList.currentIndex
>= 0 ? `: ${App.devices.get(
deviceList.currentIndex).softwareVersion}` : ""
}
}
}
@ -62,7 +60,7 @@ ApplicationWindow {
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
color: onlineStatus ? "green":"black"
color: onlineStatus ? "green" : "black"
width: 10
height: 10
radius: 5
@ -72,7 +70,6 @@ ApplicationWindow {
anchors.fill: parent
onClicked: {
deviceList.currentIndex = index
}
onDoubleClicked: {
networkPopup.open()
@ -80,7 +77,7 @@ ApplicationWindow {
}
}
onCurrentIndexChanged: {
deviceVersion.text = App.devices.get(deviceList.currentIndex).softwareVersion;
// deviceVersion.text = App.devices.get(deviceList.currentIndex).softwareVersion;
App.connectToDevice(deviceList.currentIndex)
}
ProgressBar {
@ -100,6 +97,7 @@ ApplicationWindow {
anchors.bottom: parent.bottom
anchors.left: deviceList.right
anchors.right: parent.right
enabled: deviceList.currentIndex>=0
openDoorAreaWay: App.currentOpenDoorAreaWay
openDoorAreaPoints: App.currentOpenDoorAreaPoints
shieldedAreaEnabled: App.currentShieldedAreaEnabled
@ -119,18 +117,61 @@ ApplicationWindow {
id: otaPopup
}
FolderDialog {
id: folderDialog
onAccepted: {
App.collector.path = folderDialog.selectedFolder
App.collector.start(App.devices.get(deviceList.currentIndex).ip)
}
}
footer: RowLayout {
width: parent.width
Item {}
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 {}
Button {
text: "升级"
onClicked: otaPopup.open()
onClicked: {
if (deviceList.currentIndex < 0) {
showMessageDialog(2, "OTA升级", "请选选择设备")
return
}
otaPopup.open()
}
}
Item {}
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
parent: Overlay.overlay
anchors.centerIn: Overlay.overlay
width: 500
height: 200
width: 540
height: 260
modal: true
focus: true
closePolicy: Popup.CloseOnEscape
@ -96,18 +96,18 @@ Popup {
}
}
// Connections {
// target: App
// function onUpdateFinished() {
// otaMessage.text = "OTA"
// otaMessage.color = "green"
// }
// function onOtaMessage(message) {
// otaMessage.text = message
// }
// function onOtaProgressChanged(progress) {
// progressBar.value = progress
// progressText.text = `${progress}%`
// }
// }
Connections {
target: App
function onCurrentDeviceOtaProgressChanged (status, progress, message) {
progressBar.value = progress
progressText.text = `${progress}%`
if(progress>=100){
otaMessage.text = "OTA升级完成"
otaMessage.color = "green"
}else {
otaMessage.text = message
}
}
}
}

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