Работа с WIFI
This commit is contained in:
parent
da72c4eda6
commit
21e7c210c1
@ -11,6 +11,8 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QThreadPool>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "dump/TaskDumpRecorder.h"
|
#include "dump/TaskDumpRecorder.h"
|
||||||
|
|
||||||
@ -206,6 +208,104 @@ QList<QPair<QString, QString> > AdbUtils::getListDevices() {
|
|||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::adbPair(const QString &ip, int port, const QString &code) {
|
||||||
|
const QString endpoint = ip + ":" + QString::number(port);
|
||||||
|
QString out, err;
|
||||||
|
startProcess(adbPath(), {"pair", endpoint, code}, &out, &err);
|
||||||
|
const QString combined = out + err;
|
||||||
|
const bool ok = combined.contains("Successfully paired", Qt::CaseInsensitive);
|
||||||
|
if (ok) {
|
||||||
|
qInfo().noquote() << "[AdbUtils] adb pair" << endpoint << "→ ok";
|
||||||
|
} else {
|
||||||
|
qWarning().noquote() << "[AdbUtils] adb pair" << endpoint << "failed:" << combined.trimmed();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AdbUtils::adbConnect(const QString &ip, int port) {
|
||||||
|
const QString endpoint = ip + ":" + QString::number(port);
|
||||||
|
QString out, err;
|
||||||
|
startProcess(adbPath(), {"connect", endpoint}, &out, &err);
|
||||||
|
const QString combined = out + err;
|
||||||
|
// adb connect возвращает exit 0 и при неудаче ("failed to connect"), поэтому
|
||||||
|
// ориентируемся на текст вывода.
|
||||||
|
if (combined.contains("connected to", Qt::CaseInsensitive)
|
||||||
|
|| combined.contains("already connected", Qt::CaseInsensitive)) {
|
||||||
|
qInfo().noquote() << "[AdbUtils] adb connect" << endpoint << "→ ok";
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
qWarning().noquote() << "[AdbUtils] adb connect" << endpoint << "failed:" << combined.trimmed();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::adbDisconnect(const QString &endpoint) {
|
||||||
|
QString out, err;
|
||||||
|
startProcess(adbPath(), {"disconnect", endpoint}, &out, &err);
|
||||||
|
return true; // best-effort
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AdbUtils::mdnsFindConnect(const QString &ip) {
|
||||||
|
QString out;
|
||||||
|
startProcess(adbPath(), {"mdns", "services"}, &out);
|
||||||
|
// Строки вида: adb-XXXX-YY._adb-tls-connect._tcp. 192.168.1.50:41555
|
||||||
|
static const QRegularExpression reEndpoint(R"((\d+\.\d+\.\d+\.\d+):(\d+))");
|
||||||
|
const QStringList lines = out.split('\n');
|
||||||
|
for (const QString &line : lines) {
|
||||||
|
if (!line.contains("_adb-tls-connect")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto m = reEndpoint.match(line); m.hasMatch() && m.captured(1) == ip) {
|
||||||
|
return m.captured(1) + ":" + m.captured(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AdbUtils::scanFindConnect(const QString &ip, int portStart, int portEnd) {
|
||||||
|
if (portStart < 1) portStart = 1;
|
||||||
|
if (portEnd > 65535) portEnd = 65535;
|
||||||
|
if (portEnd < portStart) return {};
|
||||||
|
|
||||||
|
// 1. Параллельно ищем открытые TCP-порты на IP телефона (сырой сокет, без adb).
|
||||||
|
QList<int> ports;
|
||||||
|
ports.reserve(portEnd - portStart + 1);
|
||||||
|
for (int p = portStart; p <= portEnd; ++p) ports.append(p);
|
||||||
|
|
||||||
|
QThreadPool pool;
|
||||||
|
pool.setMaxThreadCount(256);
|
||||||
|
const QList<int> openPorts = QtConcurrent::blockingFiltered(
|
||||||
|
&pool, ports, [&ip](int p) {
|
||||||
|
QTcpSocket s;
|
||||||
|
s.connectToHost(ip, static_cast<quint16>(p));
|
||||||
|
return s.waitForConnected(250);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (openPorts.isEmpty()) {
|
||||||
|
qWarning().noquote() << "[AdbUtils] scanFindConnect: no open ports on" << ip;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
qInfo().noquote() << "[AdbUtils] scanFindConnect: open ports on" << ip << ":" << openPorts;
|
||||||
|
|
||||||
|
// 2. На каждый открытый порт пробуем adb connect (порт спаривания откажет, connect-
|
||||||
|
// порт подключится — pairing уже есть, код не нужен).
|
||||||
|
for (const int p : openPorts) {
|
||||||
|
if (const QString ep = adbConnect(ip, p); !ep.isEmpty()) {
|
||||||
|
return ep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString AdbUtils::getAndroidId(const QString &deviceId) {
|
||||||
|
QString out;
|
||||||
|
if (!startProcess(adbPath(),
|
||||||
|
{"-s", deviceId, "shell", "settings", "get", "secure", "android_id"}, &out)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const QString id = out.trimmed();
|
||||||
|
return (id == "null" || id.isEmpty()) ? QString() : id;
|
||||||
|
}
|
||||||
|
|
||||||
QString AdbUtils::tryToGetRunningAppPid(const QString &deviceId, const QString &packageName) {
|
QString AdbUtils::tryToGetRunningAppPid(const QString &deviceId, const QString &packageName) {
|
||||||
QProcess process;
|
QProcess process;
|
||||||
process.setWorkingDirectory(adbDir());
|
process.setWorkingDirectory(adbDir());
|
||||||
|
|||||||
@ -13,6 +13,28 @@ public:
|
|||||||
|
|
||||||
static QList<QPair<QString, QString> > getListDevices();
|
static QList<QPair<QString, QString> > getListDevices();
|
||||||
|
|
||||||
|
// --- Wi-Fi подключение (wireless debugging) ---
|
||||||
|
// Спаривание по коду: `adb pair IP:port code`. true при "Successfully paired".
|
||||||
|
static bool adbPair(const QString &ip, int port, const QString &code);
|
||||||
|
// `adb connect IP:port`. Возвращает рабочий "IP:port" при успехе, иначе пусто
|
||||||
|
// (adb может вернуть exit 0 и при неудаче — судим по тексту вывода).
|
||||||
|
static QString adbConnect(const QString &ip, int port);
|
||||||
|
// `adb disconnect <endpoint>` (best-effort).
|
||||||
|
static bool adbDisconnect(const QString &endpoint);
|
||||||
|
// Поиск connect-эндпоинта устройства по IP через `adb mdns services`
|
||||||
|
// (_adb-tls-connect._tcp). Возвращает "IP:port" или пусто.
|
||||||
|
static QString mdnsFindConnect(const QString &ip);
|
||||||
|
|
||||||
|
// android_id устройства (`settings get secure android_id`) — стабильная личность.
|
||||||
|
// Пусто при ошибке/таймауте.
|
||||||
|
static QString getAndroidId(const QString &deviceId);
|
||||||
|
|
||||||
|
// Fallback-поиск connect-порта, когда он сменился (тоггл/ребут wireless debugging),
|
||||||
|
// а mDNS в сети не работает: параллельно сканирует IP телефона на открытые порты и
|
||||||
|
// пробует `adb connect` (pairing сохранён → код не нужен). Возвращает рабочий
|
||||||
|
// "IP:port" или пусто. Тяжёлая операция — звать только как fallback.
|
||||||
|
static QString scanFindConnect(const QString &ip, int portStart = 30000, int portEnd = 50000);
|
||||||
|
|
||||||
static QString tryToGetRunningAppPid(const QString &deviceId, const QString &packageName);
|
static QString tryToGetRunningAppPid(const QString &deviceId, const QString &packageName);
|
||||||
|
|
||||||
static bool tryToStartApplication(const QString &deviceId, const QString &packageName);
|
static bool tryToStartApplication(const QString &deviceId, const QString &packageName);
|
||||||
|
|||||||
@ -260,16 +260,17 @@ void DatabaseManager::initSchema() {
|
|||||||
))");
|
))");
|
||||||
|
|
||||||
// Wi-Fi реквизиты подключения per-device (связь по android_id = devices.id).
|
// Wi-Fi реквизиты подключения per-device (связь по android_id = devices.id).
|
||||||
// Задел под авто-реконнект по Wi-Fi: ip+port для adb connect, code — одноразовый
|
// Авто-реконнект по Wi-Fi: ip+port для adb connect. Pairing-код НЕ храним — он
|
||||||
// pairing-код (нужен только для первого adb pair; реконнект идёт по ip:port).
|
// одноразовый и для реконнекта не нужен (pairing сохраняется на устройстве).
|
||||||
q.exec(R"(CREATE TABLE IF NOT EXISTS wifi_connections (
|
q.exec(R"(CREATE TABLE IF NOT EXISTS wifi_connections (
|
||||||
android_id TEXT PRIMARY KEY,
|
android_id TEXT PRIMARY KEY,
|
||||||
ip TEXT,
|
ip TEXT,
|
||||||
port INTEGER,
|
port INTEGER,
|
||||||
code TEXT,
|
|
||||||
updated_at DATETIME,
|
updated_at DATETIME,
|
||||||
FOREIGN KEY (android_id) REFERENCES devices (id) ON DELETE CASCADE
|
FOREIGN KEY (android_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||||
))");
|
))");
|
||||||
|
// Миграция: убираем колонку code из старых БД (ошибка = колонки уже нет — норм).
|
||||||
|
q.exec("ALTER TABLE wifi_connections DROP COLUMN code");
|
||||||
|
|
||||||
db.commit();
|
db.commit();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,28 +13,24 @@ WifiConnection parseRow(const QSqlQuery &query) {
|
|||||||
wc.androidId = query.value("android_id").toString();
|
wc.androidId = query.value("android_id").toString();
|
||||||
wc.ip = query.value("ip").toString();
|
wc.ip = query.value("ip").toString();
|
||||||
wc.port = query.value("port").toInt();
|
wc.port = query.value("port").toInt();
|
||||||
wc.code = query.value("code").toString();
|
|
||||||
wc.updatedAt = query.value("updated_at").toString();
|
wc.updatedAt = query.value("updated_at").toString();
|
||||||
return wc;
|
return wc;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool WifiConnectionDAO::upsert(const QString &androidId, const QString &ip, int port,
|
bool WifiConnectionDAO::upsert(const QString &androidId, const QString &ip, int port) {
|
||||||
const QString &code) {
|
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
INSERT INTO wifi_connections (android_id, ip, port, code, updated_at)
|
INSERT INTO wifi_connections (android_id, ip, port, updated_at)
|
||||||
VALUES (:android_id, :ip, :port, :code, :updated_at)
|
VALUES (:android_id, :ip, :port, :updated_at)
|
||||||
ON CONFLICT(android_id) DO UPDATE SET
|
ON CONFLICT(android_id) DO UPDATE SET
|
||||||
ip = excluded.ip,
|
ip = excluded.ip,
|
||||||
port = excluded.port,
|
port = excluded.port,
|
||||||
code = excluded.code,
|
|
||||||
updated_at = excluded.updated_at
|
updated_at = excluded.updated_at
|
||||||
)");
|
)");
|
||||||
query.bindValue(":android_id", androidId);
|
query.bindValue(":android_id", androidId);
|
||||||
query.bindValue(":ip", ip);
|
query.bindValue(":ip", ip);
|
||||||
query.bindValue(":port", port);
|
query.bindValue(":port", port);
|
||||||
query.bindValue(":code", code);
|
|
||||||
query.bindValue(":updated_at", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
query.bindValue(":updated_at", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "WifiConnectionDAO::upsert error:" << query.lastError().text();
|
qWarning() << "WifiConnectionDAO::upsert error:" << query.lastError().text();
|
||||||
@ -43,6 +39,22 @@ bool WifiConnectionDAO::upsert(const QString &androidId, const QString &ip, int
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WifiConnectionDAO::updateIp(const QString &androidId, const QString &ip) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
query.prepare(R"(
|
||||||
|
UPDATE wifi_connections SET ip = :ip, updated_at = :updated_at
|
||||||
|
WHERE android_id = :android_id
|
||||||
|
)");
|
||||||
|
query.bindValue(":ip", ip);
|
||||||
|
query.bindValue(":updated_at", QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||||
|
query.bindValue(":android_id", androidId);
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "WifiConnectionDAO::updateIp error:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
WifiConnection WifiConnectionDAO::getByAndroidId(const QString &androidId) {
|
WifiConnection WifiConnectionDAO::getByAndroidId(const QString &androidId) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
query.prepare("SELECT * FROM wifi_connections WHERE android_id = :android_id LIMIT 1");
|
query.prepare("SELECT * FROM wifi_connections WHERE android_id = :android_id LIMIT 1");
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
struct WifiConnection {
|
struct WifiConnection {
|
||||||
QString androidId; // = devices.id
|
QString androidId; // = devices.id
|
||||||
QString ip; // последний известный IP телефона в Wi-Fi
|
QString ip; // последний известный IP телефона в Wi-Fi
|
||||||
int port = 0; // порт для adb connect (5555 для legacy tcpip)
|
int port = 0; // порт для adb connect
|
||||||
QString code; // одноразовый pairing-код (пусто для legacy tcpip)
|
|
||||||
QString updatedAt; // ISO-время последнего обновления
|
QString updatedAt; // ISO-время последнего обновления
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -18,9 +17,13 @@ struct WifiConnection {
|
|||||||
// поиска. В текущей итерации — только хранилище (воркер реконнекта отложен).
|
// поиска. В текущей итерации — только хранилище (воркер реконнекта отложен).
|
||||||
class WifiConnectionDAO {
|
class WifiConnectionDAO {
|
||||||
public:
|
public:
|
||||||
// Вставка/обновление по android_id (PRIMARY KEY).
|
// Вставка/обновление по android_id (PRIMARY KEY). Pairing-код НЕ храним: он
|
||||||
static bool upsert(const QString &androidId, const QString &ip, int port,
|
// одноразовый и для реконнекта не нужен (pairing сохраняется на устройстве).
|
||||||
const QString &code = {});
|
static bool upsert(const QString &androidId, const QString &ip, int port);
|
||||||
|
|
||||||
|
// Обновить ТОЛЬКО ip (и updated_at) существующей записи, не трогая port/code.
|
||||||
|
// Если записи нет — ничего не делает (порт/код задаёт онбординг, не фоновый IP-апдейт).
|
||||||
|
static bool updateIp(const QString &androidId, const QString &ip);
|
||||||
|
|
||||||
// Запись по android_id (пустой androidId в результате → не найдено).
|
// Запись по android_id (пустой androidId в результате → не найдено).
|
||||||
[[nodiscard]] static WifiConnection getByAndroidId(const QString &androidId);
|
[[nodiscard]] static WifiConnection getByAndroidId(const QString &androidId);
|
||||||
|
|||||||
BIN
images/usb.png
Normal file
BIN
images/usb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
images/wifi.png
Normal file
BIN
images/wifi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
18
main.cpp
18
main.cpp
@ -56,6 +56,7 @@ static LONG WINAPI crashHandler(EXCEPTION_POINTERS *ep) {
|
|||||||
#include "dao/GeneralLogDAO.h"
|
#include "dao/GeneralLogDAO.h"
|
||||||
#include "dao/SettingsDAO.h"
|
#include "dao/SettingsDAO.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
|
#include "wifi/WifiAdbConnector.h"
|
||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "update/ApplyUpdate.h"
|
#include "update/ApplyUpdate.h"
|
||||||
#include "black/PayByCardScript.h"
|
#include "black/PayByCardScript.h"
|
||||||
@ -126,6 +127,20 @@ void startEventHandler(const QCoreApplication &app) {
|
|||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startWifiAdbConnector(const QCoreApplication &app) {
|
||||||
|
auto *thread = new QThread;
|
||||||
|
auto *connector = new WifiAdbConnector;
|
||||||
|
connector->moveToThread(thread);
|
||||||
|
QObject::connect(thread, &QThread::started, connector, &WifiAdbConnector::start);
|
||||||
|
QObject::connect(connector, &WifiAdbConnector::finished, thread, &QThread::quit);
|
||||||
|
QObject::connect(connector, &WifiAdbConnector::finished, connector, &QObject::deleteLater);
|
||||||
|
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||||
|
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
||||||
|
connector->stop();
|
||||||
|
});
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
void startPayByPhoneTest() {
|
void startPayByPhoneTest() {
|
||||||
// Берём первый девайс с ozon аккаунтом
|
// Берём первый девайс с ozon аккаунтом
|
||||||
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("ozon");
|
QList<MaterialInfo> accounts = MaterialDAO::getAccountsByAppCode("ozon");
|
||||||
@ -439,6 +454,7 @@ int main(int argc, char *argv[]) {
|
|||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
startDeviceScreener(app, net);
|
startDeviceScreener(app, net);
|
||||||
startEventHandler(app);
|
startEventHandler(app);
|
||||||
|
startWifiAdbConnector(app);
|
||||||
// startBlackPayByCardTest();
|
// startBlackPayByCardTest();
|
||||||
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
||||||
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
||||||
@ -476,6 +492,7 @@ int main(int argc, char *argv[]) {
|
|||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
startDeviceScreener(app, net);
|
startDeviceScreener(app, net);
|
||||||
startEventHandler(app);
|
startEventHandler(app);
|
||||||
|
startWifiAdbConnector(app);
|
||||||
// startBlackPayByCardTest();
|
// startBlackPayByCardTest();
|
||||||
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
||||||
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
||||||
@ -503,6 +520,7 @@ int main(int argc, char *argv[]) {
|
|||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
startDeviceScreener(app, net);
|
startDeviceScreener(app, net);
|
||||||
startEventHandler(app);
|
startEventHandler(app);
|
||||||
|
startWifiAdbConnector(app);
|
||||||
// startBlackPayByCardTest();
|
// startBlackPayByCardTest();
|
||||||
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
|
||||||
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
|
||||||
|
|||||||
@ -147,13 +147,12 @@ CREATE TABLE IF NOT EXISTS settings
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- Wi-Fi реквизиты подключения per-device (связь по android_id = devices.id).
|
-- Wi-Fi реквизиты подключения per-device (связь по android_id = devices.id).
|
||||||
-- ip+port — для adb connect; code — одноразовый pairing-код (только для первого adb pair).
|
-- ip+port — для adb connect. Pairing-код не храним (одноразовый, для реконнекта не нужен).
|
||||||
CREATE TABLE IF NOT EXISTS wifi_connections
|
CREATE TABLE IF NOT EXISTS wifi_connections
|
||||||
(
|
(
|
||||||
android_id TEXT PRIMARY KEY,
|
android_id TEXT PRIMARY KEY,
|
||||||
ip TEXT,
|
ip TEXT,
|
||||||
port INTEGER,
|
port INTEGER,
|
||||||
code TEXT,
|
|
||||||
updated_at DATETIME,
|
updated_at DATETIME,
|
||||||
FOREIGN KEY (android_id) REFERENCES devices (id) ON DELETE CASCADE
|
FOREIGN KEY (android_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|||||||
@ -447,14 +447,13 @@ void DeviceScreener::start() {
|
|||||||
qDebug() << "Cant update device";
|
qDebug() << "Cant update device";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем Wi-Fi IP телефона в wifi_connections (по android_id) — задел под
|
// Обновляем Wi-Fi IP телефона в wifi_connections (по android_id). Только IP —
|
||||||
// авто-реконнект. Резолвим напрямую (независимо от http-скриншота, чтобы IP
|
// НЕ трогаем port/code, которые задаёт онбординг по коду (иначе затрём их).
|
||||||
// был и для exec-out-устройств); resolvePhoneIp кэширует с TTL ~5 мин, так что
|
// updateIp обновляет лишь существующую запись; новые реквизиты создаёт онбординг.
|
||||||
// adb дёргается редко. Пишем только при изменении. upsert после upsertDevice —
|
// resolvePhoneIp кэширует с TTL ~5 мин, пишем только при изменении IP.
|
||||||
// FK wifi_connections → devices(id).
|
|
||||||
if (const QString ip = AdbUtils::resolvePhoneIp(scan.adbSerial);
|
if (const QString ip = AdbUtils::resolvePhoneIp(scan.adbSerial);
|
||||||
!ip.isEmpty() && m_lastPersistedIp.value(device.id) != ip) {
|
!ip.isEmpty() && m_lastPersistedIp.value(device.id) != ip) {
|
||||||
if (WifiConnectionDAO::upsert(device.id, ip, 5555)) {
|
if (WifiConnectionDAO::updateIp(device.id, ip)) {
|
||||||
m_lastPersistedIp[device.id] = ip;
|
m_lastPersistedIp[device.id] = ip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -566,6 +565,13 @@ void DeviceScreener::start() {
|
|||||||
qDebug() << "[DeviceScreener] device went OFFLINE (debounced):" << id;
|
qDebug() << "[DeviceScreener] device went OFFLINE (debounced):" << id;
|
||||||
// Сообщаем серверу что устройство офлайн (отдельно от bank_profile-синка)
|
// Сообщаем серверу что устройство офлайн (отдельно от bank_profile-синка)
|
||||||
const DeviceInfo offlineDevice = DeviceDAO::getDeviceById(id);
|
const DeviceInfo offlineDevice = DeviceDAO::getDeviceById(id);
|
||||||
|
// Wi-Fi-устройство (IP:port): рвём мёртвый TCP, чтобы adb убрал его из
|
||||||
|
// `adb devices` и мы перестали его пинговать; сторож переподключит при
|
||||||
|
// возврате связи (по записи в wifi_connections).
|
||||||
|
static const QRegularExpression reWifiSerial(R"(^\d+\.\d+\.\d+\.\d+:\d+$)");
|
||||||
|
if (reWifiSerial.match(offlineDevice.adbSerial).hasMatch()) {
|
||||||
|
AdbUtils::adbDisconnect(offlineDevice.adbSerial);
|
||||||
|
}
|
||||||
if (!offlineDevice.apiId.isEmpty()) {
|
if (!offlineDevice.apiId.isEmpty()) {
|
||||||
DeviceInfo offlineCopy = offlineDevice;
|
DeviceInfo offlineCopy = offlineDevice;
|
||||||
offlineCopy.status = "offline";
|
offlineCopy.status = "offline";
|
||||||
@ -674,11 +680,12 @@ DeviceScreener::ScanResult DeviceScreener::scanDeviceIo(const QString &adbSerial
|
|||||||
// ok=false означает, что процесс не завершился за таймаут (зависшая команда):
|
// ok=false означает, что процесс не завершился за таймаут (зависшая команда):
|
||||||
// результат считаем «нет данных», а не валидной пустой строкой, чтобы вызывающий
|
// результат считаем «нет данных», а не валидной пустой строкой, чтобы вызывающий
|
||||||
// код не затирал известные значения.
|
// код не затирал известные значения.
|
||||||
auto runAdbCommand = [&](const QStringList &args, bool *ok = nullptr) -> QString {
|
auto runAdbCommand = [&](const QStringList &args, bool *ok = nullptr,
|
||||||
|
int timeoutMs = kAdbCommandTimeoutMs) -> QString {
|
||||||
QProcess process;
|
QProcess process;
|
||||||
process.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
process.setWorkingDirectory(QFileInfo(AdbUtils::adbPath()).absolutePath());
|
||||||
process.start(AdbUtils::adbPath(), QStringList{"-s", adbSerial} + args);
|
process.start(AdbUtils::adbPath(), QStringList{"-s", adbSerial} + args);
|
||||||
if (!process.waitForFinished(kAdbCommandTimeoutMs)) {
|
if (!process.waitForFinished(timeoutMs)) {
|
||||||
qWarning() << "[DeviceScreener] adb command timeout for" << adbSerial << args;
|
qWarning() << "[DeviceScreener] adb command timeout for" << adbSerial << args;
|
||||||
process.kill();
|
process.kill();
|
||||||
process.waitForFinished(1000);
|
process.waitForFinished(1000);
|
||||||
@ -692,6 +699,22 @@ DeviceScreener::ScanResult DeviceScreener::scanDeviceIo(const QString &adbSerial
|
|||||||
// Статика (id/name/android/размер экрана) не меняется за сессию. Если передан
|
// Статика (id/name/android/размер экрана) не меняется за сессию. Если передан
|
||||||
// непустой кэш — переиспользуем его и не дёргаем adb. Иначе читаем с нуля.
|
// непустой кэш — переиспользуем его и не дёргаем adb. Иначе читаем с нуля.
|
||||||
DeviceInfo info = cachedStatic;
|
DeviceInfo info = cachedStatic;
|
||||||
|
if (!info.id.isEmpty()) {
|
||||||
|
// Устройство уже идентифицировано — но проверим, что оно ещё ОТВЕЧАЕТ.
|
||||||
|
// Иначе при падении Wi-Fi мёртвый TCP долго висит в `adb devices` как "device",
|
||||||
|
// а мы держали бы устройство «онлайн». Быстрый пинг с коротким таймаутом (3с):
|
||||||
|
// живое отвечает мгновенно, отвалившееся — таймаут → не считаем живым в этом
|
||||||
|
// цикле → уйдёт в offline по дебаунсу.
|
||||||
|
bool aliveOk = false;
|
||||||
|
runAdbCommand({"shell", "true"}, &aliveOk, 3000);
|
||||||
|
if (!aliveOk) {
|
||||||
|
qWarning() << "[DeviceScreener] device not responding (link down?):" << adbSerial;
|
||||||
|
info.adbSerial = adbSerial;
|
||||||
|
result.info = info;
|
||||||
|
result.identified = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (info.id.isEmpty()) {
|
if (info.id.isEmpty()) {
|
||||||
bool idOk = false;
|
bool idOk = false;
|
||||||
bool modelOk = false;
|
bool modelOk = false;
|
||||||
|
|||||||
87
services/wifi/WifiAdbConnector.cpp
Normal file
87
services/wifi/WifiAdbConnector.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include "WifiAdbConnector.h"
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "adb/AdbUtils.h"
|
||||||
|
#include "dao/WifiConnectionDAO.h"
|
||||||
|
|
||||||
|
WifiAdbConnector::WifiAdbConnector(QObject *parent) : QObject(parent) {
|
||||||
|
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||||
|
m_intervalSecs = settings.value("wifi_adb/reconnectIntervalSecs", 30).toInt();
|
||||||
|
if (m_intervalSecs < 5) m_intervalSecs = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiAdbConnector::start() {
|
||||||
|
qDebug() << "[WifiAdbConnector] started, interval" << m_intervalSecs << "s";
|
||||||
|
while (m_running) {
|
||||||
|
reconnectCycle();
|
||||||
|
// Спим интервал посекундно, чтобы stop() срабатывал быстро.
|
||||||
|
for (int i = 0; i < m_intervalSecs && m_running; ++i) {
|
||||||
|
QThread::msleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiAdbConnector::stop() {
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiAdbConnector::reconnectCycle() {
|
||||||
|
const QList<WifiConnection> records = WifiConnectionDAO::getAll();
|
||||||
|
if (records.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Текущее состояние adb: endpoint → status.
|
||||||
|
QHash<QString, QString> status;
|
||||||
|
for (const auto &[serial, st] : AdbUtils::getListDevices()) {
|
||||||
|
status.insert(serial, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const WifiConnection &wc : records) {
|
||||||
|
if (!m_running) break;
|
||||||
|
if (wc.ip.isEmpty() || wc.port <= 0) continue;
|
||||||
|
|
||||||
|
const QString endpoint = wc.ip + ":" + QString::number(wc.port);
|
||||||
|
const QString st = status.value(endpoint);
|
||||||
|
if (st == "device") {
|
||||||
|
continue; // живо — не трогаем (не рвём связь работающего устройства)
|
||||||
|
}
|
||||||
|
if (st == "offline") {
|
||||||
|
// Частый случай при слабом Wi-Fi — пересоздаём соединение.
|
||||||
|
AdbUtils::adbDisconnect(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пытаемся подключиться по сохранённому порту.
|
||||||
|
QString ep = AdbUtils::adbConnect(wc.ip, wc.port);
|
||||||
|
if (ep.isEmpty()) {
|
||||||
|
// Порт мог смениться (тоггл/ребут wireless debugging). Сначала mDNS…
|
||||||
|
if (const QString mep = AdbUtils::mdnsFindConnect(wc.ip); !mep.isEmpty()) {
|
||||||
|
const int c = mep.lastIndexOf(':');
|
||||||
|
ep = AdbUtils::adbConnect(mep.left(c), mep.mid(c + 1).toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ep.isEmpty()) {
|
||||||
|
// …mDNS пуст (сеть режет мультикаст) — скан-fallback: ищем новый порт
|
||||||
|
// перебором (pairing сохранён, код не нужен). Тяжело, но только для
|
||||||
|
// реально отвалившихся устройств.
|
||||||
|
ep = AdbUtils::scanFindConnect(wc.ip);
|
||||||
|
}
|
||||||
|
if (ep.isEmpty()) {
|
||||||
|
continue; // не нашли — попробуем в следующем цикле
|
||||||
|
}
|
||||||
|
// Подключились. Если эндпоинт сменился — обновляем реестр (порт), сохраняя код.
|
||||||
|
const int c = ep.lastIndexOf(':');
|
||||||
|
const QString newIp = ep.left(c);
|
||||||
|
const int newPort = ep.mid(c + 1).toInt();
|
||||||
|
if (newIp != wc.ip || newPort != wc.port) {
|
||||||
|
WifiConnectionDAO::upsert(wc.androidId, newIp, newPort);
|
||||||
|
qDebug() << "[WifiAdbConnector] endpoint updated for" << wc.androidId
|
||||||
|
<< "→" << ep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
services/wifi/WifiAdbConnector.h
Normal file
28
services/wifi/WifiAdbConnector.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
// Сторож Wi-Fi-подключений: фоновый воркер (QThread), который держит устройства из
|
||||||
|
// реестра wifi_connections на связи. Раз в ~intervalSecs проходит по записям и
|
||||||
|
// переподключает отвалившихся (offline → disconnect+connect, absent → connect, при
|
||||||
|
// смене порта — поиск через mDNS). Повторный pair не нужен — хост уже доверенный.
|
||||||
|
// Само устройство подхватывает DeviceScreener (общий adb-сервер).
|
||||||
|
class WifiAdbConnector final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WifiAdbConnector(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reconnectCycle();
|
||||||
|
|
||||||
|
bool m_running = true;
|
||||||
|
int m_intervalSecs = 30;
|
||||||
|
};
|
||||||
@ -21,6 +21,7 @@
|
|||||||
#include "net/NetworkService.h"
|
#include "net/NetworkService.h"
|
||||||
#include "db/DeviceInfo.h"
|
#include "db/DeviceInfo.h"
|
||||||
#include "device/DeviceWidget.h"
|
#include "device/DeviceWidget.h"
|
||||||
|
#include "device/WifiConnectDialog.h"
|
||||||
#include "dao/BankProfileDAO.h"
|
#include "dao/BankProfileDAO.h"
|
||||||
#include "DatabaseManager.h"
|
#include "DatabaseManager.h"
|
||||||
#include "dao/DeviceDAO.h"
|
#include "dao/DeviceDAO.h"
|
||||||
@ -70,6 +71,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
|||||||
auto *tutorialPageAction = new QAction("Инструкция", this);
|
auto *tutorialPageAction = new QAction("Инструкция", this);
|
||||||
auto *eventAction = new QAction("События", this);
|
auto *eventAction = new QAction("События", this);
|
||||||
auto *fileLogAction = new QAction("Логи", this);
|
auto *fileLogAction = new QAction("Логи", this);
|
||||||
|
auto *wifiConnectAction = new QAction("Подключить по Wi-Fi", this);
|
||||||
auto *updateAction = new QAction("Проверить обновления", this);
|
auto *updateAction = new QAction("Проверить обновления", this);
|
||||||
m_updateAction = updateAction;
|
m_updateAction = updateAction;
|
||||||
auto *exitAction = new QAction("Выйти", this);
|
auto *exitAction = new QAction("Выйти", this);
|
||||||
@ -78,6 +80,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
|||||||
menu->addAction(eventAction);
|
menu->addAction(eventAction);
|
||||||
menu->addAction(tutorialPageAction);
|
menu->addAction(tutorialPageAction);
|
||||||
menu->addAction(fileLogAction);
|
menu->addAction(fileLogAction);
|
||||||
|
menu->addAction(wifiConnectAction);
|
||||||
menu->addAction(updateAction);
|
menu->addAction(updateAction);
|
||||||
menu->addAction(exitAction);
|
menu->addAction(exitAction);
|
||||||
|
|
||||||
@ -126,6 +129,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(wifiConnectAction, &QAction::triggered, this, [this]() {
|
||||||
|
auto *dlg = new WifiConnectDialog(this);
|
||||||
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
dlg->show();
|
||||||
|
});
|
||||||
|
|
||||||
connect(updateAction, &QAction::triggered, this, &MainWindow::checkForUpdates);
|
connect(updateAction, &QAction::triggered, this, &MainWindow::checkForUpdates);
|
||||||
|
|
||||||
connect(exitAction, &QAction::triggered, this, [this]() {
|
connect(exitAction, &QAction::triggered, this, [this]() {
|
||||||
|
|||||||
@ -359,7 +359,31 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
|||||||
"color: white;"
|
"color: white;"
|
||||||
"font-size: 10px;"
|
"font-size: 10px;"
|
||||||
);
|
);
|
||||||
layout->addWidget(statusLabel, 0, 0, Qt::AlignTop | Qt::AlignLeft);
|
|
||||||
|
// Имя устройства + иконка типа подключения (USB/Wi-Fi) под ним. Тип — по формату
|
||||||
|
// adb_serial: IP:port → Wi-Fi, иначе USB.
|
||||||
|
auto *topLeft = new QWidget(this);
|
||||||
|
topLeft->setStyleSheet("background: transparent;");
|
||||||
|
auto *topLeftLayout = new QVBoxLayout(topLeft);
|
||||||
|
topLeftLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
topLeftLayout->setSpacing(2);
|
||||||
|
topLeftLayout->addWidget(statusLabel);
|
||||||
|
|
||||||
|
if (device.status == "device" && !device.adbSerial.isEmpty()) {
|
||||||
|
// Wi-Fi-серийник: IP:port (ручной adb connect) ИЛИ mDNS-имя wireless debugging
|
||||||
|
// (adb-XXXX._adb-tls-connect._tcp). USB-серийник — обычный hex, без ':' и '_tcp'.
|
||||||
|
static const QRegularExpression reIpPort(R"(^\d+\.\d+\.\d+\.\d+:\d+$)");
|
||||||
|
const bool isWifi = reIpPort.match(device.adbSerial).hasMatch()
|
||||||
|
|| device.adbSerial.contains("_tcp", Qt::CaseInsensitive);
|
||||||
|
auto *connIcon = new QLabel(topLeft);
|
||||||
|
const QPixmap p(QString("%1/images/%2.png")
|
||||||
|
.arg(QCoreApplication::applicationDirPath(), isWifi ? "wifi" : "usb"));
|
||||||
|
connIcon->setPixmap(p.scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||||
|
connIcon->setStyleSheet("background: transparent;");
|
||||||
|
connIcon->setToolTip(isWifi ? "Wi-Fi" : "USB");
|
||||||
|
topLeftLayout->addWidget(connIcon, 0, Qt::AlignLeft);
|
||||||
|
}
|
||||||
|
layout->addWidget(topLeft, 0, 0, Qt::AlignTop | Qt::AlignLeft);
|
||||||
|
|
||||||
// Кнопки настроек и удаления (справа сверху)
|
// Кнопки настроек и удаления (справа сверху)
|
||||||
auto *topRightWidget = new QWidget(this);
|
auto *topRightWidget = new QWidget(this);
|
||||||
|
|||||||
136
views/device/WifiConnectDialog.cpp
Normal file
136
views/device/WifiConnectDialog.cpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#include "WifiConnectDialog.h"
|
||||||
|
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QIntValidator>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QProgressDialog>
|
||||||
|
|
||||||
|
#include "adb/AdbUtils.h"
|
||||||
|
#include "dao/WifiConnectionDAO.h"
|
||||||
|
|
||||||
|
WifiConnectDialog::WifiConnectDialog(QWidget *parent) : QDialog(parent) {
|
||||||
|
setWindowTitle("Подключить по Wi-Fi");
|
||||||
|
setModal(true);
|
||||||
|
|
||||||
|
auto *form = new QFormLayout;
|
||||||
|
m_ip = new QLineEdit(this);
|
||||||
|
m_ip->setPlaceholderText("192.168.1.50");
|
||||||
|
m_port = new QLineEdit(this);
|
||||||
|
m_port->setPlaceholderText("37123");
|
||||||
|
m_port->setValidator(new QIntValidator(1, 65535, this));
|
||||||
|
m_code = new QLineEdit(this);
|
||||||
|
m_code->setPlaceholderText("6-значный код");
|
||||||
|
|
||||||
|
form->addRow("IP", m_ip);
|
||||||
|
form->addRow("Порт", m_port);
|
||||||
|
form->addRow("Код", m_code);
|
||||||
|
|
||||||
|
m_btn = new QPushButton("Подключить", this);
|
||||||
|
connect(m_btn, &QPushButton::clicked, this, &WifiConnectDialog::onConnect);
|
||||||
|
|
||||||
|
m_status = new QLabel(this);
|
||||||
|
m_status->setWordWrap(true);
|
||||||
|
m_status->setStyleSheet("color: #555; font-size: 11px;");
|
||||||
|
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
layout->addLayout(form);
|
||||||
|
layout->addWidget(m_btn);
|
||||||
|
layout->addWidget(m_status);
|
||||||
|
|
||||||
|
// Подсказка оператору: где взять реквизиты.
|
||||||
|
auto *hint = new QLabel(
|
||||||
|
"Телефон → Для разработчиков → Беспроводная отладка →\n"
|
||||||
|
"«Подключение с помощью кода»: введите показанные IP:порт и код.", this);
|
||||||
|
hint->setWordWrap(true);
|
||||||
|
hint->setStyleSheet("color: #888; font-size: 10px;");
|
||||||
|
layout->addWidget(hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiConnectDialog::setStatus(const QString &text) {
|
||||||
|
if (m_status) m_status->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiConnectDialog::onConnect() {
|
||||||
|
const QString ip = m_ip->text().trimmed();
|
||||||
|
const int port = m_port->text().trimmed().toInt();
|
||||||
|
const QString code = m_code->text().trimmed();
|
||||||
|
if (ip.isEmpty() || port <= 0 || code.isEmpty()) {
|
||||||
|
setStatus("Заполните IP, порт и код.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_btn->setEnabled(false);
|
||||||
|
setStatus("Спаривание и подключение…");
|
||||||
|
|
||||||
|
// Лоадер на время онбординга (бесконечный индикатор, без кнопки отмены).
|
||||||
|
auto *progress = new QProgressDialog("Спаривание и подключение…", QString(), 0, 0, this);
|
||||||
|
progress->setWindowTitle("Подключение по Wi-Fi");
|
||||||
|
progress->setWindowModality(Qt::WindowModal);
|
||||||
|
progress->setCancelButton(nullptr);
|
||||||
|
progress->setMinimumDuration(0);
|
||||||
|
progress->setAutoClose(false);
|
||||||
|
progress->setAutoReset(false);
|
||||||
|
progress->setWindowFlags(progress->windowFlags()
|
||||||
|
& ~Qt::WindowCloseButtonHint & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
progress->show();
|
||||||
|
|
||||||
|
// Результат онбординга, заполняется в фоновом потоке.
|
||||||
|
struct Res { bool ok = false; QString msg; };
|
||||||
|
auto *res = new Res;
|
||||||
|
|
||||||
|
auto *worker = QThread::create([res, ip, port, code]() {
|
||||||
|
if (!AdbUtils::adbPair(ip, port, code)) {
|
||||||
|
res->msg = "Не удалось спарить — проверьте порт и код (код одноразовый, "
|
||||||
|
"переоткройте экран спаривания на телефоне).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Connect: сначала тем же портом, при неудаче — поиск connect-порта через mDNS.
|
||||||
|
QString endpoint = AdbUtils::adbConnect(ip, port);
|
||||||
|
if (endpoint.isEmpty()) {
|
||||||
|
if (const QString mep = AdbUtils::mdnsFindConnect(ip); !mep.isEmpty()) {
|
||||||
|
const int c = mep.lastIndexOf(':');
|
||||||
|
endpoint = AdbUtils::adbConnect(mep.left(c), mep.mid(c + 1).toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (endpoint.isEmpty()) {
|
||||||
|
res->msg = "Спарено, но не удалось подключиться. Проверьте порт подключения "
|
||||||
|
"(в «Беспроводной отладке» он отличается от порта спаривания).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QString androidId = AdbUtils::getAndroidId(endpoint);
|
||||||
|
if (androidId.isEmpty()) {
|
||||||
|
res->msg = "Подключено (" + endpoint + "), но не удалось прочитать android_id.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int connectPort = endpoint.mid(endpoint.lastIndexOf(':') + 1).toInt();
|
||||||
|
// Код НЕ сохраняем — он одноразовый, для реконнекта не нужен.
|
||||||
|
WifiConnectionDAO::upsert(androidId, ip, connectPort);
|
||||||
|
res->ok = true;
|
||||||
|
res->msg = "Подключено: " + endpoint + ". Устройство появится в списке.";
|
||||||
|
});
|
||||||
|
|
||||||
|
QPointer<WifiConnectDialog> guard(this);
|
||||||
|
QPointer<QProgressDialog> progressGuard(progress);
|
||||||
|
connect(worker, &QThread::finished, qApp, [worker, res, guard, progressGuard]() {
|
||||||
|
if (progressGuard) {
|
||||||
|
progressGuard->reset();
|
||||||
|
progressGuard->deleteLater();
|
||||||
|
}
|
||||||
|
if (guard) {
|
||||||
|
guard->setStatus(res->msg);
|
||||||
|
guard->m_btn->setEnabled(true);
|
||||||
|
if (res->ok) {
|
||||||
|
guard->m_code->clear(); // код одноразовый — очистим для следующего ввода
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete res;
|
||||||
|
worker->deleteLater();
|
||||||
|
});
|
||||||
|
worker->start();
|
||||||
|
}
|
||||||
27
views/device/WifiConnectDialog.h
Normal file
27
views/device/WifiConnectDialog.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
class QLineEdit;
|
||||||
|
class QPushButton;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
// Диалог «Подключить по Wi-Fi»: оператор вводит IP, порт и pairing-код.
|
||||||
|
// По кнопке — онбординг в фоновом потоке: adb pair → adb connect → читаем android_id →
|
||||||
|
// запись в wifi_connections. Дальше устройство подхватывает DeviceScreener.
|
||||||
|
class WifiConnectDialog final : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WifiConnectDialog(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onConnect();
|
||||||
|
void setStatus(const QString &text);
|
||||||
|
|
||||||
|
QLineEdit *m_ip = nullptr;
|
||||||
|
QLineEdit *m_port = nullptr;
|
||||||
|
QLineEdit *m_code = nullptr;
|
||||||
|
QPushButton *m_btn = nullptr;
|
||||||
|
QLabel *m_status = nullptr;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user