diff --git a/Application.cpp b/Application.cpp index aaec9cd..63201ac 100644 --- a/Application.cpp +++ b/Application.cpp @@ -9,7 +9,8 @@ Application::Application(int &argc, char **argv) : m_app(std::make_shared(argc, argv)), m_videoFrameProvider(new VideoFrameProvider()), - m_player(std::make_shared()), m_devices(new DeviceListModel(this)) { + m_player(std::make_shared()), 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) { diff --git a/Application.h b/Application.h index bbe730d..fbb7e0d 100644 --- a/Application.h +++ b/Application.h @@ -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 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 m_player; std::weak_ptr m_device; DeviceListModel *m_devices = nullptr; + DataCollection *m_collector = nullptr; DeviceConnection::AreaWay m_currentOpenDoorAreaWay = DeviceConnection::Diabled; QList m_currentOpenDoorAreaPoints; diff --git a/CMakeLists.txt b/CMakeLists.txt index a53cf6e..74ce391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/DataCollection.cpp b/DataCollection.cpp new file mode 100644 index 0000000..cac490e --- /dev/null +++ b/DataCollection.cpp @@ -0,0 +1,90 @@ +#include "DataCollection.h" +#include "BoostLog.h" +#include +#include +#include +#include +#include +#include +#include + +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(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(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(); }); + } +} diff --git a/DataCollection.h b/DataCollection.h new file mode 100644 index 0000000..031df27 --- /dev/null +++ b/DataCollection.h @@ -0,0 +1,43 @@ +#ifndef DATACOLLECTION_H +#define DATACOLLECTION_H + +#include +#include + +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 diff --git a/DeviceConnection.cpp b/DeviceConnection.cpp index 4853a54..55461f1 100644 --- a/DeviceConnection.cpp +++ b/DeviceConnection.cpp @@ -2,6 +2,7 @@ #include "BoostLog.h" #include "StringUtility.h" #include +#include #include #include #include @@ -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((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + 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(&buffer[1]); + *contentSize = htonl(sendSize); + + m_commandSocket->write(buffer, sendSize + 1 + sizeof(uint32_t)); + m_sendedSize += sendSize; + + auto fileProgress = static_cast(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(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(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(); + } +} diff --git a/DeviceConnection.h b/DeviceConnection.h index 1cb89ee..afb0d22 100644 --- a/DeviceConnection.h +++ b/DeviceConnection.h @@ -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 openDoorArea; + AreaWay openDoorAreaWay; + + QList shieldedArea; + bool shieldedAreaEnabled; + + QList antiClipArea; + bool antiClipAreaEnabled; + }; + using H264FrameCallback = std::function; 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 &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 &points); - void currentShieldedAreaChanged(bool enabled, const QList &points); - void currentAntiClipAreaChanged(bool enabled, const QList &points); - void currentNetworkInfomationChanged(const NetworkInfomation &info); + void connected(); + void disconnected(); + void openDoorAreaChanged(AreaWay way, const QList &points); + void shieldedAreaChanged(bool enabled, const QList &points); + void antiClipAreaChanged(bool enabled, const QList &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 m_uploadBuffer; + int m_sendedSize = 0; + H264FrameCallback m_frameCallback; std::queue> m_requests; + int m_timerId = -1; + Area m_area; + NetworkInfomation m_networkInfomation; }; #endif // DEVICECONNECTION_H diff --git a/DeviceListModel.cpp b/DeviceListModel.cpp index cb90733..0091d18 100644 --- a/DeviceListModel.cpp +++ b/DeviceListModel.cpp @@ -73,9 +73,6 @@ void DeviceListModel::startSearchDevice() { return; } beginResetModel(); - for (auto &device : m_devices) { - device->deleteLater(); - } m_devices.clear(); endResetModel(); auto interfaces = QNetworkInterface::allInterfaces(); diff --git a/DeviceListModel.h b/DeviceListModel.h index 4da6c66..7bf6cbf 100644 --- a/DeviceListModel.h +++ b/DeviceListModel.h @@ -31,7 +31,7 @@ public: QHash roleNames() const final; Q_INVOKABLE QVariantMap get(int index) const; std::shared_ptr device(int index); - Q_INVOKABLE void startSearchDevice(); + void startSearchDevice(); bool isSearching() const; float searchProgress() const; diff --git a/VideoFrameProvider.cpp b/VideoFrameProvider.cpp index 822ff79..74d116e 100644 --- a/VideoFrameProvider.cpp +++ b/VideoFrameProvider.cpp @@ -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(); diff --git a/main.cpp b/main.cpp index 52e5bf7..5006378 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ #include "Configuration.h" #include #include +#include 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::instance(argc, argv); + QQuickStyle::setStyle("Basic"); + return app->exec(); } diff --git a/qml/DataCollectionPopup.qml b/qml/DataCollectionPopup.qml new file mode 100644 index 0000000..b3ad9da --- /dev/null +++ b/qml/DataCollectionPopup.qml @@ -0,0 +1,6 @@ +import QtQuick +import QtQuick.Controls + +Popup { + +} diff --git a/DeviceView.qml b/qml/DeviceView.qml similarity index 93% rename from DeviceView.qml rename to qml/DeviceView.qml index f6c55f4..7738d38 100644 --- a/DeviceView.qml +++ b/qml/DeviceView.qml @@ -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() { diff --git a/qml/IconButton.qml b/qml/IconButton.qml new file mode 100644 index 0000000..f9d8f2a --- /dev/null +++ b/qml/IconButton.qml @@ -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 + } + } +} diff --git a/Main.qml b/qml/Main.qml similarity index 56% rename from Main.qml rename to qml/Main.qml index b24d700..a29ef5b 100644 --- a/Main.qml +++ b/qml/Main.qml @@ -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() + } + } } diff --git a/qml/MessageDialog.qml b/qml/MessageDialog.qml new file mode 100644 index 0000000..0e4d5ca --- /dev/null +++ b/qml/MessageDialog.qml @@ -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" + } + } +} diff --git a/NetworkSettingPopup.qml b/qml/NetworkSettingPopup.qml similarity index 100% rename from NetworkSettingPopup.qml rename to qml/NetworkSettingPopup.qml diff --git a/OtaPopup.qml b/qml/OtaPopup.qml similarity index 80% rename from OtaPopup.qml rename to qml/OtaPopup.qml index 37486e3..b0eb1ab 100644 --- a/OtaPopup.qml +++ b/qml/OtaPopup.qml @@ -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 + } + + } + } } diff --git a/qml/StatusTip.qml b/qml/StatusTip.qml new file mode 100644 index 0000000..ba392d2 --- /dev/null +++ b/qml/StatusTip.qml @@ -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 + } + +} diff --git a/resources/popup_close.svg b/resources/popup_close.svg new file mode 100644 index 0000000..579f945 --- /dev/null +++ b/resources/popup_close.svg @@ -0,0 +1,15 @@ + + + + Mask + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/resources/prompt_delete.svg b/resources/prompt_delete.svg new file mode 100644 index 0000000..811f27b --- /dev/null +++ b/resources/prompt_delete.svg @@ -0,0 +1,15 @@ + + + + Mask + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/resources/successfull.svg b/resources/successfull.svg new file mode 100644 index 0000000..ce8a61b --- /dev/null +++ b/resources/successfull.svg @@ -0,0 +1,9 @@ + + + + Combined Shape + Created with Sketch. + + + + \ No newline at end of file diff --git a/resources/warning.svg b/resources/warning.svg new file mode 100644 index 0000000..135c164 --- /dev/null +++ b/resources/warning.svg @@ -0,0 +1,17 @@ + + + + 💚 Icon + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file