2024-08-16 16:24:15 +08:00
|
|
|
#include "DeviceListModel.h"
|
|
|
|
#include "BoostLog.h"
|
2024-08-20 09:29:49 +08:00
|
|
|
#include "DeviceConnection.h"
|
2024-08-16 16:24:15 +08:00
|
|
|
#include <QNetworkDatagram>
|
|
|
|
#include <QNetworkInterface>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QUdpSocket>
|
|
|
|
#include <boost/json/parse.hpp>
|
|
|
|
|
2024-08-22 18:12:11 +08:00
|
|
|
static QHostAddress calculateNetworkAddress(const QHostAddress &ip, const QHostAddress &netmask) {
|
|
|
|
quint32 ipInt = ip.toIPv4Address();
|
|
|
|
quint32 netmaskInt = netmask.toIPv4Address();
|
|
|
|
quint32 networkInt = ipInt & netmaskInt;
|
|
|
|
return QHostAddress(networkInt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isSameNetwork(const QHostAddress &ip1, const QHostAddress &netmask1, const QHostAddress &ip2) {
|
|
|
|
QHostAddress network1 = calculateNetworkAddress(ip1, netmask1);
|
|
|
|
QHostAddress network2 = calculateNetworkAddress(ip2, netmask1); // Using the same netmask for the target IP
|
|
|
|
return network1 == network2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isTargetIPInSameNetwork(const QHostAddress &targetIP) {
|
|
|
|
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
|
|
|
for (const QNetworkInterface &iface : interfaces) {
|
|
|
|
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
|
|
|
for (const QNetworkAddressEntry &entry : entries) {
|
|
|
|
QHostAddress ip = entry.ip();
|
|
|
|
QHostAddress netmask = entry.netmask();
|
|
|
|
|
|
|
|
if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
|
|
|
|
bool sameNetwork = isSameNetwork(ip, netmask, targetIP);
|
|
|
|
if (sameNetwork) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-08-16 16:24:15 +08:00
|
|
|
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();
|
2024-08-20 09:29:49 +08:00
|
|
|
if (row >= m_devices.size()) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
auto info = m_devices.at(row)->infomation();
|
2024-08-16 16:24:15 +08:00
|
|
|
if (role == DeviceIdRole) {
|
2024-08-20 09:29:49 +08:00
|
|
|
ret = info.deviceId;
|
2024-08-16 16:24:15 +08:00
|
|
|
} else if (role == FirmwareVersionRole) {
|
2024-08-20 09:29:49 +08:00
|
|
|
ret = info.firmwareVersion;
|
2024-08-16 16:24:15 +08:00
|
|
|
} else if (role == SoftwareVersionRole) {
|
2024-08-20 09:29:49 +08:00
|
|
|
ret = info.softwareVersion;
|
2024-08-16 16:24:15 +08:00
|
|
|
} else if (role == IpRole) {
|
2024-08-20 09:29:49 +08:00
|
|
|
ret = info.ip;
|
|
|
|
} else if (role == OnlineStatusRole) {
|
|
|
|
ret = m_devices.at(row)->isConnected();
|
2024-08-16 16:24:15 +08:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
QHash<int, QByteArray> DeviceListModel::roleNames() const {
|
|
|
|
QHash<int, QByteArray> roleNames;
|
|
|
|
roleNames.insert(DeviceIdRole, "deviceId");
|
|
|
|
roleNames.insert(FirmwareVersionRole, "firmwareVersion");
|
|
|
|
roleNames.insert(SoftwareVersionRole, "softwareVersion");
|
|
|
|
roleNames.insert(IpRole, "ip");
|
2024-08-20 09:29:49 +08:00
|
|
|
roleNames.insert(OnlineStatusRole, "onlineStatus");
|
2024-08-16 16:24:15 +08:00
|
|
|
return roleNames;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantMap DeviceListModel::get(int index) const {
|
|
|
|
QVariantMap map;
|
|
|
|
if (index >= 0 && index < m_devices.size()) {
|
2024-08-20 09:29:49 +08:00
|
|
|
auto info = m_devices.at(index)->infomation();
|
|
|
|
map["firmwareVersion"] = info.firmwareVersion;
|
|
|
|
map["softwareVersion"] = info.softwareVersion;
|
|
|
|
map["deviceId"] = info.deviceId;
|
|
|
|
map["ip"] = info.ip;
|
|
|
|
map["onlineStatus"] = m_devices.at(index)->isConnected();
|
2024-08-16 16:24:15 +08:00
|
|
|
}
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2024-08-20 09:29:49 +08:00
|
|
|
std::shared_ptr<DeviceConnection> DeviceListModel::device(int index) {
|
|
|
|
std::shared_ptr<DeviceConnection> ret;
|
|
|
|
if (index < m_devices.size()) {
|
|
|
|
ret = m_devices.at(index);
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-08-16 16:24:15 +08:00
|
|
|
void DeviceListModel::startSearchDevice() {
|
|
|
|
if (m_timerId >= 0) {
|
|
|
|
LOG(error) << "app is searching device.";
|
|
|
|
return;
|
|
|
|
}
|
2024-08-22 18:12:11 +08:00
|
|
|
if (!m_udpSockets.empty()) {
|
|
|
|
for (auto &socket : m_udpSockets) {
|
|
|
|
socket->deleteLater();
|
|
|
|
}
|
|
|
|
m_udpSockets.clear();
|
|
|
|
}
|
2024-08-16 16:24:15 +08:00
|
|
|
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<QNetworkAddressEntry> 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<float>(m_retries) * 100 / RetryCount;
|
|
|
|
}
|
|
|
|
|
2024-08-20 09:29:49 +08:00
|
|
|
void DeviceListModel::onDeviceConnected() {
|
|
|
|
auto device = dynamic_cast<DeviceConnection *>(sender());
|
|
|
|
auto iterator =
|
|
|
|
std::find_if(m_devices.cbegin(), m_devices.cend(),
|
|
|
|
[device](const std::shared_ptr<DeviceConnection> &item) { return item.get() == device; });
|
|
|
|
if (iterator != m_devices.cend()) {
|
|
|
|
QList<int> roles;
|
|
|
|
roles << OnlineStatusRole;
|
|
|
|
int row = std::distance(m_devices.cbegin(), iterator);
|
|
|
|
emit dataChanged(index(row), index(row), roles);
|
|
|
|
LOG(info) << "device " << row << " connected.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DeviceListModel::onDeviceDisconnected() {
|
|
|
|
auto device = dynamic_cast<DeviceConnection *>(sender());
|
|
|
|
auto iterator =
|
|
|
|
std::find_if(m_devices.cbegin(), m_devices.cend(),
|
|
|
|
[device](const std::shared_ptr<DeviceConnection> &item) { return item.get() == device; });
|
|
|
|
if (iterator != m_devices.cend()) {
|
|
|
|
int row = std::distance(m_devices.cbegin(), iterator);
|
|
|
|
QList<int> roles;
|
|
|
|
roles << OnlineStatusRole;
|
|
|
|
emit dataChanged(index(row), index(row), roles);
|
|
|
|
LOG(info) << "device " << row << " disconnected.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-16 16:24:15 +08:00
|
|
|
void DeviceListModel::onDeviceReplyReadyRead() {
|
|
|
|
auto udp = dynamic_cast<QUdpSocket *>(sender());
|
|
|
|
while (udp->hasPendingDatagrams()) {
|
|
|
|
QNetworkDatagram datagram = udp->receiveDatagram();
|
2024-08-22 18:12:11 +08:00
|
|
|
if (isTargetIPInSameNetwork(datagram.senderAddress())) { // 设备开机时可能会广播一个 192.168.2.10 的地址
|
|
|
|
auto replyVale = boost::json::parse(datagram.data().toStdString());
|
|
|
|
auto &reply = replyVale.as_object();
|
|
|
|
DeviceConnection::Infomation device;
|
|
|
|
if (reply.contains("devid")) {
|
|
|
|
device.deviceId = QString::fromStdString(std::string(reply.at("devid").as_string()));
|
|
|
|
}
|
|
|
|
if (reply.contains("fw_ver")) {
|
|
|
|
device.firmwareVersion = QString::fromStdString(std::string(reply.at("fw_ver").as_string()));
|
|
|
|
}
|
|
|
|
if (reply.contains("sw_ver")) {
|
|
|
|
device.softwareVersion = QString::fromStdString(std::string(reply.at("sw_ver").as_string()));
|
|
|
|
}
|
|
|
|
if (!device.softwareVersion.startsWith("RD_T009")) continue; // 其它设备不予显示
|
|
|
|
device.ip = datagram.senderAddress().toString();
|
|
|
|
|
|
|
|
auto iterator = std::find_if(m_devices.cbegin(), m_devices.cend(),
|
|
|
|
[&device](const std::shared_ptr<DeviceConnection> &item) {
|
|
|
|
return item->infomation().deviceId == device.deviceId;
|
|
|
|
});
|
|
|
|
if (iterator == m_devices.cend()) {
|
|
|
|
if (m_timerId >= 0) { // 只有在搜索设备的过程中,才加入新设备
|
|
|
|
auto connection = std::shared_ptr<DeviceConnection>(
|
|
|
|
new DeviceConnection(), [](DeviceConnection *self) { self->deleteLater(); });
|
|
|
|
connect(connection.get(), &DeviceConnection::connected, this, &DeviceListModel::onDeviceConnected);
|
|
|
|
connect(connection.get(), &DeviceConnection::disconnected, this,
|
|
|
|
&DeviceListModel::onDeviceDisconnected);
|
|
|
|
connection->connect(device);
|
|
|
|
beginInsertRows(QModelIndex(), m_devices.size(), m_devices.size());
|
|
|
|
m_devices.push_back(connection);
|
|
|
|
endInsertRows();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
auto info = (*iterator)->infomation();
|
|
|
|
if ((m_timerId < 0) ||
|
|
|
|
((info.ip == DeviceConnection::WirelessAddress) && (device.ip != DeviceConnection::WirelessAddress)) ||
|
|
|
|
((device.ip != DeviceConnection::WirelessAddress) && (device.ip != info.ip))) {
|
|
|
|
QList<int> roles;
|
|
|
|
roles << OnlineStatusRole << IpRole;
|
|
|
|
int row = std::distance(m_devices.cbegin(), iterator);
|
|
|
|
emit dataChanged(index(row), index(row), roles);
|
|
|
|
(*iterator)->connect(device);
|
|
|
|
LOG(info) << "device: " << device.deviceId.toStdString() << " back online";
|
|
|
|
}
|
|
|
|
}
|
2024-08-16 16:24:15 +08:00
|
|
|
}
|
2024-08-22 18:12:11 +08:00
|
|
|
LOG(info) << datagram.destinationAddress().toString().toStdString() << ":" << datagram.destinationPort()
|
|
|
|
<< " received datagram from " << datagram.senderAddress().toString().toStdString() << ":"
|
|
|
|
<< datagram.senderPort() << ", Data: " << datagram.data().toStdString()
|
|
|
|
<< ", same network: " << isTargetIPInSameNetwork(datagram.senderAddress());
|
2024-08-16 16:24:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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<QNetworkAddressEntry> entries = interface.addressEntries();
|
|
|
|
for (const QNetworkAddressEntry &entry : entries) {
|
|
|
|
if (entry.broadcast().toIPv4Address()) {
|
|
|
|
m_broadcastSocket->writeDatagram(datagram, entry.broadcast(), BroadcastPort);
|
2024-08-20 09:29:49 +08:00
|
|
|
// LOG(info) << "Broadcasted datagram: " << datagram.toStdString() << " to "
|
|
|
|
// << entry.broadcast().toString().toStdString() << ":" << BroadcastPort;
|
2024-08-16 16:24:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DeviceListModel::stopSearchDevice() {
|
|
|
|
if (m_timerId >= 0) {
|
|
|
|
killTimer(m_timerId);
|
|
|
|
m_timerId = -1;
|
|
|
|
}
|
|
|
|
emit isSearchingChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DeviceListModel::timerEvent(QTimerEvent *event) {
|
|
|
|
broadcast();
|
|
|
|
m_retries++;
|
|
|
|
emit searchProgressChanged();
|
|
|
|
if (m_retries >= RetryCount) {
|
|
|
|
QTimer::singleShot(0, this, &DeviceListModel::stopSearchDevice);
|
|
|
|
}
|
|
|
|
}
|