From f59645700a915900401dfd84e65d106a5648228e Mon Sep 17 00:00:00 2001 From: luocai Date: Fri, 16 Aug 2024 16:24:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E4=BF=AE=E6=94=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application.cpp | 109 +++++++++++++++++---------- Application.h | 36 ++++++--- CMakeLists.txt | 10 ++- DataStructure.h | 22 ++++++ DeviceConnection.cpp | 78 +++++++++++++++++-- DeviceConnection.h | 19 +++-- DeviceListModel.cpp | 161 ++++++++++++++++++++++++++++++++++++++++ DeviceListModel.h | 61 +++++++++++++++ DeviceView.qml | 115 +++++++++++++++++++++------- Main.qml | 83 +++++++++++++++++---- NetworkSettingPopup.qml | 117 +++++++++++++++++++++++++++++ 11 files changed, 703 insertions(+), 108 deletions(-) create mode 100644 DataStructure.h create mode 100644 DeviceListModel.cpp create mode 100644 DeviceListModel.h create mode 100644 NetworkSettingPopup.qml diff --git a/Application.cpp b/Application.cpp index fbb9cbe..dfa776f 100644 --- a/Application.cpp +++ b/Application.cpp @@ -1,7 +1,6 @@ #include "Application.h" #include "BoostLog.h" #include "Configuration.h" -#include "DeviceConnection.h" #include "H264Palyer.h" #include "VideoFrameProvider.h" #include @@ -10,7 +9,7 @@ Application::Application(int &argc, char **argv) : m_app(std::make_shared(argc, argv)), m_videoFrameProvider(new VideoFrameProvider()), - m_player(std::make_shared()) { + m_player(std::make_shared()), m_devices(new DeviceListModel(this)) { QFont font; font.setPointSize(16); m_app->setFont(font); @@ -19,19 +18,29 @@ Application::Application(int &argc, char **argv) m_player->open(); } -bool Application::currentOpenDoorAreaEnabled() const { - return m_currentOpenDoorAreaEnabled; +DeviceConnection::AreaWay Application::currentOpenDoorAreaWay() const { + return m_currentOpenDoorAreaWay; } -void Application::setCurrentOpenDoorAreaEnabled(bool enabled) { - if (m_currentOpenDoorAreaEnabled != enabled) { - m_currentOpenDoorAreaEnabled = enabled; - emit currentOpenDoorAreaEnabledChanged(); - if (m_device != nullptr) { - m_device->updateOpenDoorAreaPoints(m_currentOpenDoorAreaEnabled, m_currentOpenDoorAreaPoints); +void Application::setCurrentOpenDoorAreaWay(DeviceConnection::AreaWay way) { + if (m_currentOpenDoorAreaWay == way) return; + m_currentOpenDoorAreaWay = way; + if (m_currentOpenDoorAreaWay == DeviceConnection::Quadrangle) { + if ((m_currentOpenDoorAreaPoints.size() < 4) || (m_currentOpenDoorAreaPoints == FullArea)) { + m_currentOpenDoorAreaPoints.clear(); + m_currentOpenDoorAreaPoints << QPointF(68, 6) << QPointF(570, 6) << QPointF(570, 354) << QPointF(68, 354); + emit currentOpenDoorAreaPointsChanged(); } + } else if (m_currentOpenDoorAreaWay == DeviceConnection::FullArea) { + m_currentOpenDoorAreaPoints.clear(); + m_currentOpenDoorAreaPoints = FullArea; + emit currentOpenDoorAreaPointsChanged(); + } + + emit currentOpenDoorAreaWayChanged(); + if (m_device != nullptr) { + m_device->updateOpenDoorAreaPoints(m_currentOpenDoorAreaWay, m_currentOpenDoorAreaPoints); } - LOG(info) << "setCurrentOpenDoorAreaEnabled " << enabled; } QList Application::currentOpenDoorAreaPoints() const { @@ -45,6 +54,10 @@ void Application::setCurrentOpenDoorAreaPoints(const QList &points) { } } +NetworkInfomation Application::currentNetworkInfomation() const { + return m_currentNetworkInfomation; +} + bool Application::currentShieldedAreaEnabled() const { return m_currentShieldedAreaEnabled; } @@ -53,6 +66,13 @@ void Application::setCurrentShieldedAreaEnabled(bool enabled) { if (m_currentShieldedAreaEnabled != enabled) { m_currentShieldedAreaEnabled = enabled; emit currentShieldedAreaEnabledChanged(); + + if (m_currentShieldedAreaPoints.size() < 4) { + m_currentShieldedAreaPoints.clear(); + m_currentShieldedAreaPoints << QPointF(6, 6) << QPointF(40, 60) << QPointF(590, 6) << QPointF(630, 60); + emit currentShieldedAreaPointsChanged(); + } + if (m_device != nullptr) { m_device->updateShieldedAreaPoints(m_currentShieldedAreaEnabled, m_currentShieldedAreaPoints); } @@ -96,8 +116,9 @@ void Application::setCurrentAntiClipAreaPoints(const QList &points) { } void Application::updateOpenDoorAreaPoints(const QList &points) { - LOG(info) << "updateOpenDoorAreaPoints: " << points.size(); - m_device->updateOpenDoorAreaPoints(m_currentOpenDoorAreaEnabled, points); + if (m_device != nullptr) { + m_device->updateOpenDoorAreaPoints(m_currentOpenDoorAreaWay, points); + } } void Application::updateAntiClipAreaPoints(const QList &points) { @@ -108,10 +129,39 @@ void Application::updateShieldedAreaPoints(const QList &points) { m_device->updateShieldedAreaPoints(m_currentShieldedAreaEnabled, points); } -void Application::onDeviceOpenDoorArea(bool enabled, const QList &points) { - setCurrentOpenDoorAreaEnabled(enabled); +void Application::updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, + const QString &gateway) { + LOG(info) << dhcp; + LOG(info) << ip.toStdString(); + if (m_device != nullptr) { + m_device->updateNetworkInfomation(dhcp, ip, netmask, gateway); + } +} + +void Application::connectToDevice(const QString &address) { + if (m_device != nullptr) { + m_device->deleteLater(); + } + + m_device = new DeviceConnection(); + connect(m_device, &DeviceConnection::currentOpenDoorAreaChanged, this, &Application::onDeviceOpenDoorArea); + connect(m_device, &DeviceConnection::currentShieldedAreaChanged, this, &Application::onDeviceShieldedArea); + connect(m_device, &DeviceConnection::currentAntiClipAreaChanged, this, &Application::onDeviceAntiClipArea); + connect(m_device, &DeviceConnection::currentNetworkInfomationChanged, this, + &Application::onDeviceNetworkInfomation); + m_device->connect(address); + m_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(); + } + }); +} + +void Application::onDeviceOpenDoorArea(DeviceConnection::AreaWay way, const QList &points) { + setCurrentOpenDoorAreaWay(way); setCurrentOpenDoorAreaPoints(points); - LOG(info) << "onDeviceOpenDoorAreaPoints: " << points.size(); } void Application::onDeviceShieldedArea(bool enabled, const QList &points) { @@ -125,6 +175,11 @@ void Application::onDeviceAntiClipArea(bool enabled, const QList &point setCurrentAntiClipAreaPoints(points); } +void Application::onDeviceNetworkInfomation(const NetworkInfomation &info) { + m_currentNetworkInfomation = info; + emit currentNetworkInfomationChanged(); +} + int Application::exec() { QQmlApplicationEngine engine; engine.addImageProvider("videoframe", m_videoFrameProvider); @@ -135,28 +190,6 @@ int Application::exec() { return m_app->exec(); } -void Application::open() { - LOG(info) << "Application::start"; - m_device = new DeviceConnection(); - connect(m_device, &DeviceConnection::currentOpenDoorAreaChanged, this, &Application::onDeviceOpenDoorArea); - connect(m_device, &DeviceConnection::currentShieldedAreaChanged, this, &Application::onDeviceShieldedArea); - connect(m_device, &DeviceConnection::currentAntiClipAreaChanged, this, &Application::onDeviceAntiClipArea); - m_device->connect(); - m_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(); - } - }); -} - -void Application::start() { - if (m_device != nullptr) { - m_device->start(); - } -} - Application *Application::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) { Application *ret = nullptr; auto app = Amass::Singleton::instance(); diff --git a/Application.h b/Application.h index 2ff3a04..0069ab6 100644 --- a/Application.h +++ b/Application.h @@ -1,13 +1,15 @@ #ifndef APPLICATION_H #define APPLICATION_H +#include "DataStructure.h" +#include "DeviceConnection.h" +#include "DeviceListModel.h" #include "Singleton.h" #include #include #include class QGuiApplication; -class DeviceConnection; class VideoFrameProvider; class H264Palyer; @@ -16,8 +18,10 @@ class Application : public QObject { QML_NAMED_ELEMENT(App) QML_SINGLETON Q_PROPERTY(int DeviceWidth MEMBER DeviceWidth CONSTANT FINAL) - Q_PROPERTY(bool currentOpenDoorAreaEnabled READ currentOpenDoorAreaEnabled WRITE setCurrentOpenDoorAreaEnabled - NOTIFY currentOpenDoorAreaEnabledChanged) + Q_PROPERTY(int DeviceHeight MEMBER DeviceHeight CONSTANT FINAL) + Q_PROPERTY(DeviceListModel *devices MEMBER m_devices CONSTANT FINAL); + Q_PROPERTY(DeviceConnection::AreaWay currentOpenDoorAreaWay READ currentOpenDoorAreaWay WRITE + setCurrentOpenDoorAreaWay NOTIFY currentOpenDoorAreaWayChanged) Q_PROPERTY(QList currentOpenDoorAreaPoints READ currentOpenDoorAreaPoints WRITE setCurrentOpenDoorAreaPoints NOTIFY currentOpenDoorAreaPointsChanged) Q_PROPERTY(bool currentShieldedAreaEnabled READ currentShieldedAreaEnabled WRITE setCurrentShieldedAreaEnabled @@ -28,15 +32,22 @@ class Application : public QObject { NOTIFY currentAntiClipAreaEnabledChanged) Q_PROPERTY(QList currentAntiClipAreaPoints READ currentAntiClipAreaPoints WRITE setCurrentAntiClipAreaPoints NOTIFY currentAntiClipAreaPointsChanged) + Q_PROPERTY( + NetworkInfomation currentNetworkInfomation READ currentNetworkInfomation NOTIFY currentNetworkInfomationChanged) friend class Amass::Singleton; public: constexpr static int DeviceWidth = 640; - bool currentOpenDoorAreaEnabled() const; - void setCurrentOpenDoorAreaEnabled(bool enabled); + constexpr static int DeviceHeight = 360; + inline static const QList FullArea = {QPointF(0, 0), QPointF(DeviceWidth, 0), + QPointF(DeviceWidth, DeviceHeight), QPointF(0, DeviceHeight)}; + DeviceConnection::AreaWay currentOpenDoorAreaWay() const; + void setCurrentOpenDoorAreaWay(DeviceConnection::AreaWay way); QList currentOpenDoorAreaPoints() const; void setCurrentOpenDoorAreaPoints(const QList &points); + NetworkInfomation currentNetworkInfomation() const; + bool currentShieldedAreaEnabled() const; void setCurrentShieldedAreaEnabled(bool enabled); QList currentShieldedAreaPoints() const; @@ -50,10 +61,11 @@ public: Q_INVOKABLE void updateOpenDoorAreaPoints(const QList &points); Q_INVOKABLE void updateAntiClipAreaPoints(const QList &points); Q_INVOKABLE void updateShieldedAreaPoints(const QList &points); + Q_INVOKABLE void updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, + const QString &gateway); + Q_INVOKABLE void connectToDevice(const QString &address); int exec(); - Q_INVOKABLE void open(); - Q_INVOKABLE void start(); static Application *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); signals: @@ -61,28 +73,32 @@ signals: void currentOpenDoorAreaPointsChanged(); void currentShieldedAreaPointsChanged(); void currentAntiClipAreaPointsChanged(); - void currentOpenDoorAreaEnabledChanged(); + void currentOpenDoorAreaWayChanged(); void currentShieldedAreaEnabledChanged(); void currentAntiClipAreaEnabledChanged(); + void currentNetworkInfomationChanged(); protected: Application(int &argc, char **argv); - void onDeviceOpenDoorArea(bool enabled, const QList &points); + void onDeviceOpenDoorArea(DeviceConnection::AreaWay way, const QList &points); void onDeviceShieldedArea(bool enabled, const QList &points); void onDeviceAntiClipArea(bool enabled, const QList &points); + void onDeviceNetworkInfomation(const NetworkInfomation &info); private: std::shared_ptr m_app; VideoFrameProvider *m_videoFrameProvider = nullptr; std::shared_ptr m_player; DeviceConnection *m_device = nullptr; + DeviceListModel *m_devices = nullptr; - bool m_currentOpenDoorAreaEnabled = false; + DeviceConnection::AreaWay m_currentOpenDoorAreaWay = DeviceConnection::Diabled; QList m_currentOpenDoorAreaPoints; bool m_currentShieldedAreaEnabled = false; QList m_currentShieldedAreaPoints; bool m_currentAntiClipAreaEnabled = false; QList m_currentAntiClipAreaPoints; + NetworkInfomation m_currentNetworkInfomation; }; #endif // APPLICATION_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d07238..83eb819 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(Projects_ROOT E:/Projects) set(Libraries_ROOT ${Projects_ROOT}/Libraries) -set(BOOST_ROOT ${Libraries_ROOT}/boost_1_85_0_msvc2022_64bit) -set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include/boost-1_85) +set(BOOST_ROOT ${Libraries_ROOT}/boost_1_86_0_msvc2022_64bit) +set(Boost_INCLUDE_DIR ${BOOST_ROOT}/include/boost-1_86) option(Boost_USE_STATIC_LIBS OFF) add_compile_definitions( BOOST_USE_WINAPI_VERSION=BOOST_WINAPI_VERSION_WIN10 @@ -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) +find_package(Qt6 REQUIRED COMPONENTS Qml Quick Network) qt_standard_project_setup(REQUIRES 6.5) @@ -36,7 +36,9 @@ qt_add_executable(AntiClipSettings AntiClipSettings.rc main.cpp Application.h Application.cpp + DataStructure.h DeviceConnection.h DeviceConnection.cpp + DeviceListModel.h DeviceListModel.cpp H264Palyer.h H264Palyer.cpp VideoFrameProvider.h VideoFrameProvider.cpp ) @@ -46,6 +48,7 @@ qt_add_qml_module(AntiClipSettings QML_FILES Main.qml DeviceView.qml + NetworkSettingPopup.qml ) # Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. @@ -73,6 +76,7 @@ target_link_directories(AntiClipSettings target_link_libraries(AntiClipSettings PRIVATE Qt6::Qml PRIVATE Qt6::Quick + PRIVATE Qt6::Network PRIVATE Boost::json PRIVATE avcodec PRIVATE swscale diff --git a/DataStructure.h b/DataStructure.h new file mode 100644 index 0000000..2547e3f --- /dev/null +++ b/DataStructure.h @@ -0,0 +1,22 @@ +#ifndef __DATASTRUCTURE_H__ +#define __DATASTRUCTURE_H__ + +#include +#include + +struct NetworkInfomation { + Q_GADGET + QML_NAMED_ELEMENT(networkInfomation) + Q_PROPERTY(bool dhcp MEMBER dhcp) + Q_PROPERTY(QString ip MEMBER ip) + Q_PROPERTY(QString netmask MEMBER netmask) + Q_PROPERTY(QString gateway MEMBER gateway) +public: + bool dhcp; + QString ip = "127.0..1"; + QString netmask; + QString gateway; +}; +Q_DECLARE_METATYPE(NetworkInfomation) + +#endif // __DATASTRUCTURE_H__ diff --git a/DeviceConnection.cpp b/DeviceConnection.cpp index 33b6a8c..ab3ef44 100644 --- a/DeviceConnection.cpp +++ b/DeviceConnection.cpp @@ -1,7 +1,6 @@ #include "DeviceConnection.h" #include "BoostLog.h" #include -#include #include #include #include @@ -10,7 +9,7 @@ DeviceConnection::DeviceConnection(QObject *parent) : QObject{parent} { } -void DeviceConnection::connect() { +void DeviceConnection::connect(const QString &address) { m_commandSocket = new QTcpSocket(this); m_h264Socket = new QTcpSocket(this); @@ -20,8 +19,8 @@ void DeviceConnection::connect() { QObject::connect(m_h264Socket, &QTcpSocket::readyRead, this, &DeviceConnection::onH264ReadyRead); QObject::connect(m_commandSocket, &QTcpSocket::readyRead, this, &DeviceConnection::onCommandReadyRead); - m_commandSocket->connectToHost("192.168.10.2", 8000); - m_h264Socket->connectToHost("192.168.10.2", 8000); + m_commandSocket->connectToHost(address, 8000); + m_h264Socket->connectToHost(address, 8000); } void DeviceConnection::start() { @@ -53,13 +52,19 @@ void DeviceConnection::requestOpenDoorArea() { m_requests.push(task); } -void DeviceConnection::updateOpenDoorAreaPoints(bool enabled, const QList &points) { - auto task = [this, enabled, points]() { +void DeviceConnection::updateOpenDoorAreaPoints(AreaWay way, const QList &points) { + auto task = [this, way, points]() { boost::json::object request; request["func"] = "a03opendoor1_setdata"; request["deviceid"] = "0"; boost::json::object data; - data["value"] = enabled ? "1" : "0"; + const char *value = "0"; + if (way == FullArea) { + value = "1"; + } else if (way == Quadrangle) { + value = "2"; + } + data["value"] = value; boost::json::array pointArray; for (auto &p : points) { boost::json::object point; @@ -186,6 +191,46 @@ void DeviceConnection::requestResolution(Resolution resolution) { m_requests.push(task); } +void DeviceConnection::requestNetworkInfomation() { + auto task = [this]() { + boost::json::object request; + request["func"] = "netconfig_getdata"; + request["deviceid"] = "0"; + boost::json::object data; + request["data"] = std::move(data); + auto text = boost::json::serialize(request); + m_commandSocket->write(text.data(), text.size()); + LOG(info) << "requestNetworkInfomation"; + }; + if (m_requests.empty()) { + task(); + } + m_requests.push(task); +} + +void DeviceConnection::updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, + const QString &gateway) { + auto task = [this, dhcp, ip, netmask, gateway]() { + boost::json::object request; + request["func"] = "netconfig_setdata"; + request["deviceid"] = "0"; + boost::json::object data; + data["type"] = dhcp ? "dhcp" : "static"; + data["ip"] = ip.toStdString(); + data["netmask"] = netmask.toStdString(); + data["gateway"] = gateway.toStdString(); + + request["data"] = std::move(data); + auto text = boost::json::serialize(request); + m_commandSocket->write(text.data(), text.size()); + LOG(info) << "requestUpdateNetworkInfomation"; + }; + if (m_requests.empty()) { + task(); + } + m_requests.push(task); +} + void DeviceConnection::handleCommand(const std::string_view &replyText) { boost::system::error_code error; auto replyValue = boost::json::parse(replyText, error); @@ -207,7 +252,13 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) { point.setY(obj.at("y").as_double()); points.push_back(point); } - emit currentOpenDoorAreaChanged(value == "1", points); + AreaWay way = Diabled; + if (value == "1") { + way = FullArea; + } else if (value == "2") { + way = Quadrangle; + } + emit currentOpenDoorAreaChanged(way, points); } else if (function == "a03opendoor4_getdata") { auto &data = reply.at("data").as_object(); auto &value = data.at("value").as_string(); @@ -234,6 +285,14 @@ void DeviceConnection::handleCommand(const std::string_view &replyText) { points.push_back(point); } emit currentAntiClipAreaChanged(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); } } @@ -244,6 +303,9 @@ void DeviceConnection::onConnected() { requestOpenDoorArea(); requestShieldedArea(); requestAntiClipArea(); + requestNetworkInfomation(); + } else if (socket == m_h264Socket) { + start(); } } diff --git a/DeviceConnection.h b/DeviceConnection.h index 8161407..c985953 100644 --- a/DeviceConnection.h +++ b/DeviceConnection.h @@ -1,16 +1,19 @@ #ifndef DEVICECONNECTION_H #define DEVICECONNECTION_H +#include "DataStructure.h" #include #include +#include #include #include -class QTcpSocket; + +class NetworkInfomation; class DeviceConnection : public QObject { Q_OBJECT QML_ELEMENT - QML_UNCREATABLE("Only used in C++...") + QML_UNCREATABLE("Only created in C++...") public: enum Resolution { @@ -19,33 +22,37 @@ public: }; enum AreaWay { Diabled = 0, - FullScreen, + FullArea, Quadrangle, // 四边形 }; Q_ENUM(AreaWay) using H264FrameCallback = std::function; explicit DeviceConnection(QObject *parent = nullptr); void setH264FrameCallback(H264FrameCallback &&callback); - void connect(); + void connect(const QString &address); void start(); void requestOpenDoorArea(); - void updateOpenDoorAreaPoints(bool enabled, const QList &points); + void updateOpenDoorAreaPoints(AreaWay way, const QList &points); void requestShieldedArea(); void updateShieldedAreaPoints(bool enabled, const QList &points); void requestAntiClipArea(); void updateAntiClipAreaPoints(bool enabled, const QList &points); void requestResolution(Resolution resolution); + void requestNetworkInfomation(); + void updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask, const QString &gateway); signals: - void currentOpenDoorAreaChanged(bool enabled, const QList &points); + 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); protected: void onConnected(); void onH264ReadyRead(); void onCommandReadyRead(); void handleCommand(const std::string_view &replyText); + void onErrorOccurred(QAbstractSocket::SocketError socketError); private: QTcpSocket *m_commandSocket = nullptr; diff --git a/DeviceListModel.cpp b/DeviceListModel.cpp new file mode 100644 index 0000000..d6392db --- /dev/null +++ b/DeviceListModel.cpp @@ -0,0 +1,161 @@ +#include "DeviceListModel.h" +#include "BoostLog.h" +#include +#include +#include +#include +#include + +DeviceListModel::DeviceListModel(QObject *parent) : QAbstractListModel{parent} { + m_broadcastSocket = new QUdpSocket(this); +} + +int DeviceListModel::rowCount(const QModelIndex &parent) const { + return m_devices.size(); +} + +QVariant DeviceListModel::data(const QModelIndex &index, int role) const { + QVariant ret; + auto row = index.row(); + if (role == DeviceIdRole) { + ret = m_devices.at(row).deviceId; + } else if (role == FirmwareVersionRole) { + ret = m_devices.at(row).firmwareVersion; + } else if (role == SoftwareVersionRole) { + ret = m_devices.at(row).softwareVersion; + } else if (role == IpRole) { + ret = m_devices.at(row).ip; + } + return ret; +} + +QHash DeviceListModel::roleNames() const { + QHash roleNames; + roleNames.insert(DeviceIdRole, "deviceId"); + roleNames.insert(FirmwareVersionRole, "firmwareVersion"); + roleNames.insert(SoftwareVersionRole, "softwareVersion"); + roleNames.insert(IpRole, "ip"); + return roleNames; +} + +QVariantMap DeviceListModel::get(int index) const { + QVariantMap map; + if (index >= 0 && index < m_devices.size()) { + map["firmwareVersion"] = m_devices[index].firmwareVersion; + map["softwareVersion"] = m_devices[index].softwareVersion; + map["deviceId"] = m_devices[index].deviceId; + map["ip"] = m_devices[index].ip; + } + return map; +} + +void DeviceListModel::startSearchDevice() { + if (m_timerId >= 0) { + LOG(error) << "app is searching device."; + return; + } + beginResetModel(); + m_devices.clear(); + endResetModel(); + auto interfaces = QNetworkInterface::allInterfaces(); + for (auto &interface : interfaces) { + if (interface.flags() & QNetworkInterface::IsLoopBack) continue; + if (interface.flags() & QNetworkInterface::IsUp && interface.flags() & QNetworkInterface::IsRunning) { + const QList entries = interface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) continue; + QUdpSocket *socket = new QUdpSocket(this); + if (socket->bind(entry.ip(), ListenPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { + connect(socket, &QUdpSocket::readyRead, this, &DeviceListModel::onDeviceReplyReadyRead); + m_udpSockets.push_back(socket); + LOG(info) << "Listening on " << entry.ip().toString().toStdString() << ":" << ListenPort; + } else { + LOG(error) << "Failed to bind UDP socket on" << entry.ip().toString().toStdString() << ":" + << ListenPort; + delete socket; + } + } + } + } + if (!m_udpSockets.empty()) { + m_retries = 0; + emit searchProgressChanged(); + m_timerId = startTimer(1000); + emit isSearchingChanged(); + } +} + +bool DeviceListModel::isSearching() const { + return m_timerId >= 0; +} + +float DeviceListModel::searchProgress() const { + return static_cast(m_retries) * 100 / RetryCount; +} + +void DeviceListModel::onDeviceReplyReadyRead() { + auto udp = dynamic_cast(sender()); + while (udp->hasPendingDatagrams()) { + QNetworkDatagram datagram = udp->receiveDatagram(); + auto replyVale = boost::json::parse(datagram.data().toStdString()); + auto &reply = replyVale.as_object(); + DeviceInfomation device; + device.deviceId = QString::fromStdString(std::string(reply.at("devid").as_string())); + device.firmwareVersion = QString::fromStdString(std::string(reply.at("fw_ver").as_string())); + device.softwareVersion = QString::fromStdString(std::string(reply.at("sw_ver").as_string())); + device.ip = datagram.senderAddress().toString(); + + auto iterator = std::find_if(m_devices.cbegin(), m_devices.cend(), [&device](const DeviceInfomation &item) { + return item.deviceId == device.deviceId; + }); + if (iterator == m_devices.cend()) { + beginInsertRows(QModelIndex(), m_devices.size(), m_devices.size()); + m_devices.push_back(device); + endInsertRows(); + } + LOG(info) << "Received datagram from " << datagram.senderAddress().toString().toStdString() << ":" + << datagram.senderPort() << ", Data: " << datagram.data().toStdString(); + } +} + +void DeviceListModel::broadcast() { + if (m_udpSockets.empty()) return; + QByteArray datagram = "FACEPASS_V2"; + auto interfaces = QNetworkInterface::allInterfaces(); + for (auto &interface : interfaces) { + if (interface.flags() & QNetworkInterface::IsLoopBack) continue; + if (interface.flags() & QNetworkInterface::IsUp && interface.flags() & QNetworkInterface::IsRunning) { + const QList entries = interface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + if (entry.broadcast().toIPv4Address()) { + m_broadcastSocket->writeDatagram(datagram, entry.broadcast(), BroadcastPort); + LOG(info) << "Broadcasted datagram: " << datagram.toStdString() << " to " + << entry.broadcast().toString().toStdString() << ":" << BroadcastPort; + } + } + } + } +} + +void DeviceListModel::stopSearchDevice() { + if (m_timerId >= 0) { + killTimer(m_timerId); + m_timerId = -1; + } + if (!m_udpSockets.empty()) { + for (auto &socket : m_udpSockets) { + socket->deleteLater(); + } + m_udpSockets.clear(); + } + emit isSearchingChanged(); +} + +void DeviceListModel::timerEvent(QTimerEvent *event) { + broadcast(); + m_retries++; + emit searchProgressChanged(); + if (m_retries >= RetryCount) { + QTimer::singleShot(0, this, &DeviceListModel::stopSearchDevice); + } +} diff --git a/DeviceListModel.h b/DeviceListModel.h new file mode 100644 index 0000000..fcb9e94 --- /dev/null +++ b/DeviceListModel.h @@ -0,0 +1,61 @@ +#ifndef DEVICELISTMODEL_H +#define DEVICELISTMODEL_H + +#include +#include + +class QUdpSocket; + +class DeviceInfomation { +public: + QString deviceId; + QString softwareVersion; + QString firmwareVersion; + QString ip; +}; + +class DeviceListModel : public QAbstractListModel { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Only used in C++...") + Q_PROPERTY(bool isSearching READ isSearching NOTIFY isSearchingChanged) + Q_PROPERTY(float searchProgress READ searchProgress NOTIFY searchProgressChanged) + +public: + static constexpr uint16_t BroadcastPort = 40000; + static constexpr uint16_t ListenPort = 40001; + static constexpr int RetryCount = 10; + enum InfoRoles { + DeviceIdRole = Qt::UserRole + 1, + FirmwareVersionRole, + SoftwareVersionRole, + IpRole, + }; + DeviceListModel(QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const final; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final; + QHash roleNames() const final; + Q_INVOKABLE QVariantMap get(int index) const; + Q_INVOKABLE void startSearchDevice(); + bool isSearching() const; + float searchProgress() const; + +signals: + void isSearchingChanged(); + void searchProgressChanged(); + +protected: + void onDeviceReplyReadyRead(); + void broadcast(); + void stopSearchDevice(); + void timerEvent(QTimerEvent *event); + +private: + std::vector m_devices; + std::list m_udpSockets; + QUdpSocket *m_broadcastSocket = nullptr; + int m_timerId = -1; + int m_retries = 0; +}; + +#endif // DEVICELISTMODEL_H diff --git a/DeviceView.qml b/DeviceView.qml index ab1df1f..0e5e6fe 100644 --- a/DeviceView.qml +++ b/DeviceView.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts import AntiClipSettings Item { @@ -7,14 +8,13 @@ Item { property int dargWidth: 12 property color openDoorAreaColor: "green" property var openDoorAreaPoints: [] - property bool openDoorAreaEnabled: false + property int openDoorAreaWay: 0 + property color shieldedAreaColor: "yellow" property var shieldedAreaPoints: [] property bool shieldedAreaEnabled: false property color antiClipAreaColor: "blue" property var antiClipAreaPoints: [] property bool antiClipAreaEnabled: false - property var defaultShieldedAreaPoints: [Qt.point(6, 6), Qt.point(40, 60), - Qt.point(598, 6), Qt.point(633, 60)] Item { anchors.left: parent.left anchors.right: parent.right @@ -36,7 +36,13 @@ Item { onPaint: { var ctx = canvas.getContext("2d") ctx.clearRect(0, 0, canvas.width, canvas.height) - if (openDoorAreaEnabled &&(openDoorAreaPoints.length > 0)) { + if(openDoorAreaWay == DeviceConnection.FullArea){ + ctx.strokeStyle = openDoorAreaColor + ctx.lineWidth = 8 + ctx.strokeRect(0,0, canvas.width, + canvas.height) + + } else if ((openDoorAreaWay>=DeviceConnection.Quadrangle) &&(openDoorAreaPoints.length > 0)) { ctx.strokeStyle = openDoorAreaColor ctx.lineWidth = 2 @@ -69,7 +75,8 @@ Item { ctx.stroke() } if (shieldedAreaEnabled &&(shieldedAreaPoints.length > 0)) { - ctx.strokeStyle = "green" + ctx.lineWidth = 2 + ctx.strokeStyle = shieldedAreaColor let point0 = scaledPoint(shieldedAreaPoints[0], width, height) let point1 = scaledPoint(shieldedAreaPoints[1], @@ -88,12 +95,12 @@ Item { Repeater { id: repeater - visible: openDoorAreaEnabled + visible: openDoorAreaWay>=DeviceConnection.Quadrangle model: openDoorAreaPoints delegate: Rectangle { width: dargWidth height: dargWidth - visible: openDoorAreaEnabled + visible: openDoorAreaWay>=DeviceConnection.Quadrangle color: openDoorAreaColor x: scaledPoint(modelData, canvas.width, canvas.height).x - width / 2 @@ -110,7 +117,7 @@ Item { width: dargWidth height: dargWidth visible: shieldedAreaEnabled - color: "green" + color: shieldedAreaColor x: scaledPoint(modelData, canvas.width, canvas.height).x - width / 2 y: scaledPoint(modelData, canvas.width, @@ -140,7 +147,7 @@ Item { property int draggedShieldedAreaPointIndex: -1 property int draggedAntiAreaPointIndex: -1 onPressed: mouse => { - if(openDoorAreaEnabled){ + if(openDoorAreaWay == DeviceConnection.Quadrangle){ for (var i = 0; i < openDoorAreaPoints.length; i++) { let point = scaledPoint( openDoorAreaPoints[i], canvas.width, @@ -226,38 +233,91 @@ Item { } } } - Row { + Grid { id: controlBar anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - Switch { - text: "开门区域" - checked: openDoorAreaEnabled - onToggled: App.currentOpenDoorAreaEnabled = checked + columns: 2 + spacing: 10 + Text {text: qsTr("开门区域: ")} + Row { + RadioButton { + text: "关闭" + checked: App.currentOpenDoorAreaWay ==DeviceConnection.Diabled + onToggled:{ + App.currentOpenDoorAreaWay =DeviceConnection.Diabled + } + } + RadioButton { + text: "全区域" + checked: App.currentOpenDoorAreaWay ==DeviceConnection.FullArea + onToggled:{ + App.currentOpenDoorAreaWay =DeviceConnection.FullArea + } + } + RadioButton { + text: "四边形" + checked: App.currentOpenDoorAreaWay ==DeviceConnection.Quadrangle + onToggled:{ + App.currentOpenDoorAreaWay =DeviceConnection.Quadrangle + } + } } - Switch { - text: "防夹区域" - checked: antiClipAreaEnabled - onToggled: App.currentAntiClipAreaEnabled = checked + + + Text {text: qsTr("防夹区域: ")} + Row { + RadioButton { + text: "关闭" + checked: !App.currentAntiClipAreaEnabled + onToggled: { + App.currentAntiClipAreaEnabled=false + } + + } + RadioButton { + text: "四边形" + checked: App.currentAntiClipAreaEnabled + onToggled: { + App.currentAntiClipAreaEnabled=true + } + } + } - Switch { - text: "屏蔽区域" - checked: shieldedAreaEnabled - onToggled: App.currentShieldedAreaEnabled = checked + + Text {text: qsTr("屏蔽区域: ")} + Row { + id: shieldedRow + RadioButton { + checked: !shieldedAreaEnabled + text: "关闭" + onToggled: { + App.currentShieldedAreaEnabled = false + } + } + RadioButton { + checked: shieldedAreaEnabled + text: "开启" + onToggled: { + App.currentShieldedAreaEnabled = true + } + } } } + + // 转换为显示画点 function scaledPoint(point, width, height) { - let x = point.x * width / 640 - let y = point.y * height / 360 + let x = point.x * width / App.DeviceWidth + let y = point.y * height / App.DeviceHeight return Qt.point(x, y) } // 转换为设备画点(640x360) function standardPoint(point, width, height) { - let x = point.x * 640 / width - let y = point.y * 360 / height + let x = point.x * App.DeviceWidth / width + let y = point.y * App.DeviceHeight / height return Qt.point(x, y) } @@ -269,6 +329,7 @@ Item { } function onCurrentOpenDoorAreaPointsChanged() { canvas.requestPaint() + repeater.model = openDoorAreaPoints } function onCurrentShieldedAreaPointsChanged() { canvas.requestPaint() @@ -276,7 +337,7 @@ Item { function onCurrentAntiClipAreaPointsChanged() { canvas.requestPaint() } - function onCurrentOpenDoorAreaEnabledChanged(){ + function onCurrentOpenDoorAreaWayChanged(){ canvas.requestPaint() } function onCurrentShieldedAreaEnabledChanged(){ diff --git a/Main.qml b/Main.qml index f43bc5d..f6ff258 100644 --- a/Main.qml +++ b/Main.qml @@ -4,8 +4,8 @@ import QtQuick.Layouts import AntiClipSettings ApplicationWindow { - width: 680 - height: 480 + width: 1000 + height: 640 visible: true title: qsTr("Hello World") @@ -14,29 +14,73 @@ ApplicationWindow { anchors.fill: parent Button { text: "搜索设备" + onClicked: { + deviceList.currentIndex = -1 + App.devices.startSearchDevice() + } } - Button { - text: "连接" - onClicked: App.open() - } - Button { - text: "开始" - onClicked: App.start() - } - Text { + Row { Layout.alignment: Qt.AlignRight - text: qsTr("当前设备版本号: RD_T009_V02R001B001") + Text { + text: qsTr("当前设备版本号: ") + } + Text { + id: deviceVersion + text: qsTr("RD_T009_V02R001B001") + } } + } } - Rectangle { + ListView { id: deviceList anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: parent.left - width: 250 - color: "red" + width: 420 + clip: true + model: App.devices + delegate: Rectangle { + width: deviceList.width + height: 40 + color: ListView.isCurrentItem ? "#aaddff" : (index % 2 == 0 ? "#ffffff" : "#eeeeee") + Row { + anchors.fill: parent + spacing: 10 + Text { + text: deviceId + } + Item {} + Text { + text: ip + } + } + MouseArea { + anchors.fill: parent + onClicked: { + deviceList.currentIndex = index + + } + onDoubleClicked: { + networkPopup.open() + } + } + } + onCurrentIndexChanged: { + deviceVersion.text = App.devices.get(deviceList.currentIndex).softwareVersion; + App.connectToDevice(App.devices.get(deviceList.currentIndex).ip) + } + ProgressBar { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + visible: App.devices.isSearching + from: 0 + to: 100 + value: App.devices.searchProgress + height: 25 + } } DeviceView { @@ -44,7 +88,7 @@ ApplicationWindow { anchors.bottom: parent.bottom anchors.left: deviceList.right anchors.right: parent.right - openDoorAreaEnabled: App.currentOpenDoorAreaEnabled + openDoorAreaWay: App.currentOpenDoorAreaWay openDoorAreaPoints: App.currentOpenDoorAreaPoints shieldedAreaEnabled: App.currentShieldedAreaEnabled shieldedAreaPoints: App.currentShieldedAreaPoints @@ -52,6 +96,13 @@ ApplicationWindow { antiClipAreaPoints: App.currentAntiClipAreaPoints } + NetworkSettingPopup { + id: networkPopup + visible: false + width: 500 + height: 240 + } + footer: RowLayout { width: parent.width Item {} diff --git a/NetworkSettingPopup.qml b/NetworkSettingPopup.qml new file mode 100644 index 0000000..8d8903b --- /dev/null +++ b/NetworkSettingPopup.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Controls +import AntiClipSettings + +Popup { + id: root + parent: Overlay.overlay + anchors.centerIn: Overlay.overlay + + Rectangle { + width: parent.width + height: parent.height + color: "white" + border.color: "lightgray" + radius: 10 + + Column { + anchors.centerIn: parent + spacing: 10 + padding: 20 + + Row { + spacing: 10 + + Text { + text: "模式" + font.pointSize: 14 + width: 100 + } + + RadioButton { + id: dhcpMode + text: "DHCP" + checked: App.currentNetworkInfomation.dhcp + } + + RadioButton { + id: staticMode + text: "静态IP" + checked: !App.currentNetworkInfomation.dhcp + } + } + + Row { + spacing: 5 + + Text { + text: "设备IP" + font.pointSize: 14 + width: 100 + } + + TextField { + id: ipInput + width: 350 + height: 30 + text: App.currentNetworkInfomation.ip + } + } + + Row { + spacing: 5 + Text { + width: 100 + text: "子网掩码" + font.pointSize: 14 + } + + TextField { + id: netmaskInput + width: 350 + height: 30 + text: App.currentNetworkInfomation.netmask + } + } + + Row { + spacing: 5 + + Row { + spacing: 5 + + Text { + text: "设备网关" + font.pointSize: 14 + width: 100 + } + } + + TextField { + id: gatewayInput + width: 350 + height: 30 + text: App.currentNetworkInfomation.gateway + } + } + + Row { + spacing: 20 + anchors.horizontalCenter: parent.horizontalCenter + + Button { + text: "保存" + onClicked: { + App.updateNetworkInfomation(dhcpMode.checked,ipInput.text,netmaskInput.text,gatewayInput.text); + networkPopup.close() + } + } + + Button { + text: "取消" + onClicked: root.close() + } + } + } + } +}