fix: improve WiFi AP recovery and scan
This commit is contained in:
131
lib/network.js
131
lib/network.js
@@ -180,19 +180,24 @@ function scanWifi() {
|
|||||||
// 等扫描完成
|
// 等扫描完成
|
||||||
sleep(2000);
|
sleep(2000);
|
||||||
|
|
||||||
const out = run('nmcli -t -f SSID,SIGNAL,SECURITY device wifi list');
|
// 指定 ifname,避免 AP/多网卡场景下读取到非目标接口或旧缓存;带回频率便于诊断 2.4G/5G。
|
||||||
|
const out = run(`nmcli -t -f SSID,SIGNAL,SECURITY,FREQ device wifi list ifname ${iface}`);
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const line of out.split('\n')) {
|
for (const line of out.split('\n')) {
|
||||||
if (!line.trim()) continue;
|
if (!line.trim()) continue;
|
||||||
const parts = line.split(':');
|
const parts = _parseNmcliTerseLine(line);
|
||||||
const ssid = parts[0].trim().replace(/\\:/g, ':');
|
const ssid = (parts[0] || '').trim();
|
||||||
if (!ssid || seen.has(ssid)) continue;
|
if (!ssid || seen.has(ssid)) continue;
|
||||||
seen.add(ssid);
|
seen.add(ssid);
|
||||||
|
const freq = (parts[3] || '').trim();
|
||||||
|
const freqMhz = parseInt(freq, 10) || null;
|
||||||
results.push({
|
results.push({
|
||||||
ssid,
|
ssid,
|
||||||
signal: parseInt(parts[1], 10) || 0,
|
signal: parseInt(parts[1], 10) || 0,
|
||||||
security: parts.slice(2).join(':').trim() || 'Open',
|
security: (parts[2] || '').trim() || 'Open',
|
||||||
|
freq,
|
||||||
|
band: freqMhz ? (freqMhz >= 4900 ? '5G' : '2.4G') : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
results.sort((a, b) => b.signal - a.signal);
|
results.sort((a, b) => b.signal - a.signal);
|
||||||
@@ -275,6 +280,7 @@ async function connectWifi(ssid, password) {
|
|||||||
if (password) args.push('password', password);
|
if (password) args.push('password', password);
|
||||||
args.push('ifname', iface);
|
args.push('ifname', iface);
|
||||||
await nmcliAsync(args, 120000);
|
await nmcliAsync(args, 120000);
|
||||||
|
await _ensureActiveWifiAutoconnect();
|
||||||
|
|
||||||
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
|
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
@@ -324,6 +330,9 @@ function startAP(clawId) {
|
|||||||
cmd.push(`password "${AP_PASSWORD}"`);
|
cmd.push(`password "${AP_PASSWORD}"`);
|
||||||
}
|
}
|
||||||
run(cmd.join(' '));
|
run(cmd.join(' '));
|
||||||
|
try {
|
||||||
|
nmcliSync(['connection', 'modify', CON_NAME, 'connection.autoconnect', 'no'], 8000);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
// 等待 AP 启动
|
// 等待 AP 启动
|
||||||
sleep(2000);
|
sleep(2000);
|
||||||
@@ -361,20 +370,120 @@ function sleep(ms) {
|
|||||||
execSync(`sleep ${ms / 1000}`, { timeout: ms + 2000 });
|
execSync(`sleep ${ms / 1000}`, { timeout: ms + 2000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _parseNmcliTerseLine(line) {
|
||||||
|
const fields = [];
|
||||||
|
let cur = '';
|
||||||
|
let escaped = false;
|
||||||
|
for (const ch of line) {
|
||||||
|
if (escaped) {
|
||||||
|
cur += ch;
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === '\\') {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ch === ':') {
|
||||||
|
fields.push(cur);
|
||||||
|
cur = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cur += ch;
|
||||||
|
}
|
||||||
|
fields.push(cur);
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出已保存的 WiFi STA 连接(排除自身热点),按 autoconnect-priority 从高到低排序。
|
||||||
|
*/
|
||||||
|
function listSavedWifiConnections() {
|
||||||
|
const profiles = [];
|
||||||
|
try {
|
||||||
|
const out = run('nmcli -t -f NAME,UUID,TYPE,AUTOCONNECT,AUTOCONNECT-PRIORITY connection show');
|
||||||
|
for (const line of out.split('\n')) {
|
||||||
|
if (!line.trim()) continue;
|
||||||
|
const [name, uuid, type, autoconnect, priority] = _parseNmcliTerseLine(line);
|
||||||
|
if (type !== '802-11-wireless' || name === CON_NAME) continue;
|
||||||
|
profiles.push({
|
||||||
|
name,
|
||||||
|
uuid,
|
||||||
|
autoconnect: autoconnect === 'yes',
|
||||||
|
priority: parseInt(priority, 10) || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
profiles.sort((a, b) => {
|
||||||
|
if (b.priority !== a.priority) return b.priority - a.priority;
|
||||||
|
if (a.autoconnect !== b.autoconnect) return a.autoconnect ? -1 : 1;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测是否有已保存的 WiFi STA 连接(排除自身热点)
|
* 检测是否有已保存的 WiFi STA 连接(排除自身热点)
|
||||||
*/
|
*/
|
||||||
function hasSavedWifiConnection() {
|
function hasSavedWifiConnection() {
|
||||||
|
return listSavedWifiConnections().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWifiActiveConnectionName() {
|
||||||
|
const iface = getWifiIface();
|
||||||
try {
|
try {
|
||||||
const out = run('nmcli -t -f NAME,TYPE connection show');
|
const conn = nmcliSync(['-g', 'GENERAL.CONNECTION', 'device', 'show', iface], 8000).trim();
|
||||||
for (const line of out.split('\n')) {
|
return conn && conn !== '--' ? conn : null;
|
||||||
const [name, type] = line.split(':');
|
} catch (_) {
|
||||||
if (type === '802-11-wireless' && name !== CON_NAME) {
|
return null;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _ensureActiveWifiAutoconnect() {
|
||||||
|
const conn = getWifiActiveConnectionName();
|
||||||
|
if (!conn || conn === CON_NAME) return;
|
||||||
|
try {
|
||||||
|
await nmcliAsync(['connection', 'modify', conn, 'connection.autoconnect', 'yes'], 15000);
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `设置 WiFi 自动连接失败: ${conn}: ${e.message}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动让 NetworkManager 尝试已保存 WiFi。
|
||||||
|
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
|
||||||
|
*/
|
||||||
|
async function connectSavedWifiConnections() {
|
||||||
|
const iface = getWifiIface();
|
||||||
|
const profiles = listSavedWifiConnections();
|
||||||
|
if (profiles.length === 0) {
|
||||||
|
return { success: false, error: '没有已保存的 WiFi 配置' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
return false;
|
|
||||||
|
let lastError = '';
|
||||||
|
for (const profile of profiles) {
|
||||||
|
const label = profile.name || profile.uuid;
|
||||||
|
try {
|
||||||
|
log.info('network', `尝试连接已保存 WiFi: ${label}(ifname=${iface})`);
|
||||||
|
const idArgs = profile.uuid ? ['uuid', profile.uuid] : ['id', profile.name];
|
||||||
|
await nmcliAsync(['connection', 'up', ...idArgs, 'ifname', iface], 90000);
|
||||||
|
if (isWifiStaConnected()) {
|
||||||
|
await _ensureActiveWifiAutoconnect();
|
||||||
|
log.info('network', `已保存 WiFi 连接成功: ${label}`);
|
||||||
|
return { success: true, profile };
|
||||||
|
}
|
||||||
|
lastError = '连接命令完成但网卡未进入 STA connected 状态';
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e.message;
|
||||||
|
log.warn('network', `已保存 WiFi 连接失败: ${label}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error: lastError || '所有已保存 WiFi 均连接失败' };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -427,7 +536,9 @@ module.exports = {
|
|||||||
hasLanCableCarrier,
|
hasLanCableCarrier,
|
||||||
hasWiredInternetProbe,
|
hasWiredInternetProbe,
|
||||||
getWiredIfaceWithCarrier,
|
getWiredIfaceWithCarrier,
|
||||||
|
listSavedWifiConnections,
|
||||||
hasSavedWifiConnection,
|
hasSavedWifiConnection,
|
||||||
|
connectSavedWifiConnections,
|
||||||
isWifiStaConnected,
|
isWifiStaConnected,
|
||||||
getWifiIface,
|
getWifiIface,
|
||||||
scanWifi,
|
scanWifi,
|
||||||
|
|||||||
@@ -2,33 +2,38 @@
|
|||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
const { hasInternet, hasSavedWifiConnection, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network');
|
const { hasInternet, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network');
|
||||||
const { DnsHijack } = require('./dns-hijack');
|
const { DnsHijack } = require('./dns-hijack');
|
||||||
const { CaptiveServer } = require('./captive-server');
|
const { CaptiveServer } = require('./captive-server');
|
||||||
const led = require('./led');
|
const led = require('./led');
|
||||||
|
|
||||||
const MONITOR_INTERVAL_MS = 15_000;
|
const MONITOR_INTERVAL_MS = 15_000;
|
||||||
const BOOT_WAIT_MAX_MS = 20_000; // 等待 NM 自动连接的最大时间
|
const WIFI_RECONNECT_MAX_ROUNDS = 3;
|
||||||
const BOOT_POLL_MS = 2_000; // 轮询间隔
|
const WIFI_RECONNECT_ROUND_DELAY_MS = 5_000;
|
||||||
|
const AP_SAVED_WIFI_RETRY_INTERVAL_MS = 180_000;
|
||||||
|
const AP_MIN_UP_BEFORE_RETRY_MS = 60_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AP 常驻配网管理器。
|
* AP 常驻配网管理器。
|
||||||
*
|
*
|
||||||
* 规则:
|
* 规则:
|
||||||
* - 启动时:有已保存 WiFi 配置 → 等 NM 自动连接(最多 20 秒)
|
* - 启动时:WiFi STA 优先;有已保存 WiFi 时主动让 NM 重连,最多 3 轮
|
||||||
* - wlan0 没有以 STA 模式连接 WiFi → 开 AP + DNS 劫持 + HTTP 配网页
|
* - 有线网络可用时:通知网络就绪,但不自动开启 AP
|
||||||
* - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则重新开 AP
|
* - 自动开 AP 的唯一兜底:无有线/无 WiFi,且无 saved WiFi 或 saved WiFi 3 轮失败
|
||||||
* - 运行中 WiFi 断开 → 自动重新开 AP
|
* - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则按网络状态决定是否重新开 AP
|
||||||
* - WiFi 已连接 → AP 关闭
|
* - AP 状态下:若仍无有线网络,低频释放 wlan0 并尝试 saved WiFi
|
||||||
*/
|
*/
|
||||||
class ProvisionManager extends EventEmitter {
|
class ProvisionManager extends EventEmitter {
|
||||||
constructor(clawId) {
|
constructor(clawId) {
|
||||||
super();
|
super();
|
||||||
this._clawId = clawId || 'Setup';
|
this._clawId = clawId || 'Setup';
|
||||||
this._state = 'idle'; // 'idle' | 'ap' | 'connecting' | 'sta'
|
this._state = 'idle'; // 'idle' | 'ap' | 'connecting' | 'sta' | 'wired'
|
||||||
this._dns = null;
|
this._dns = null;
|
||||||
this._server = null;
|
this._server = null;
|
||||||
this._monitorTimer = null;
|
this._monitorTimer = null;
|
||||||
|
this._monitorBusy = false;
|
||||||
|
this._apStartedAt = 0;
|
||||||
|
this._lastApSavedWifiRetryAt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 是否正处于 AP 模式(WiFi 热点广播中) */
|
/** 是否正处于 AP 模式(WiFi 热点广播中) */
|
||||||
@@ -46,39 +51,35 @@ class ProvisionManager extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 有线有网 → 立即通知 WS 连接,后台异步设置 AP 供 WiFi 配网
|
// 有线网络可用时,网络已就绪;但不自动开启 AP,不抢占 wlan0。
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
log.info('provision', '有线网络就绪,立即启动 WS,AP 后台准备中...');
|
this._state = 'wired';
|
||||||
|
log.info('provision', '有线网络就绪,启动 WS;不自动开启 AP');
|
||||||
|
led.off();
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
setTimeout(() => {
|
|
||||||
this._enterAP();
|
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
}, 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无网:有已保存的 WiFi 配置 → 等 NM 自动连接(重启场景)
|
// 无有线可用时,有 saved WiFi 才主动让 NetworkManager 重连;不要只被动等待 NM autoconnect。
|
||||||
if (hasSavedWifiConnection()) {
|
if (hasSavedWifiConnection()) {
|
||||||
log.info('provision', '发现已保存的 WiFi 配置,等待 NetworkManager 自动连接...');
|
log.info('provision', `发现已保存的 WiFi 配置,主动重连(最多 ${WIFI_RECONNECT_MAX_ROUNDS} 轮)...`);
|
||||||
led.blink(); // WiFi 灯:等待自动重连期间闪烁
|
this._state = 'connecting';
|
||||||
const connected = await this._waitForWifiConnect();
|
led.blink();
|
||||||
|
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
||||||
if (connected) {
|
if (connected) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
log.info('provision', 'WiFi 自动连接成功,AP 不启动');
|
log.info('provision', '已保存 WiFi 重连成功,AP 不启动');
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.warn('provision', 'WiFi 自动连接超时,启动 AP');
|
log.warn('provision', '已保存 WiFi 重连失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有已保存 WiFi 或等待超时 → 开 AP
|
// 无有线、无 WiFi;且无 saved WiFi 或 saved WiFi 3 轮失败 → 开 AP 兜底配网。
|
||||||
this._enterAP();
|
this._enterAP();
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
|
|
||||||
if (hasInternet()) {
|
|
||||||
this._emitNetworkReady();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_emitNetworkReady() {
|
_emitNetworkReady() {
|
||||||
@@ -91,23 +92,17 @@ class ProvisionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async _trySavedWifiReconnectRounds(rounds = WIFI_RECONNECT_MAX_ROUNDS) {
|
||||||
* 轮询等待 NM 自动连接 WiFi,最多等 BOOT_WAIT_MAX_MS
|
for (let i = 1; i <= rounds; i++) {
|
||||||
*/
|
if (isWifiStaConnected()) return true;
|
||||||
_waitForWifiConnect() {
|
log.info('provision', `尝试已保存 WiFi 重连:第 ${i}/${rounds} 轮`);
|
||||||
return new Promise(resolve => {
|
const result = await connectSavedWifiConnections();
|
||||||
let elapsed = 0;
|
if (result.success || isWifiStaConnected()) return true;
|
||||||
const timer = setInterval(() => {
|
if (i < rounds) {
|
||||||
elapsed += BOOT_POLL_MS;
|
await new Promise((r) => setTimeout(r, WIFI_RECONNECT_ROUND_DELAY_MS));
|
||||||
if (isWifiStaConnected()) {
|
|
||||||
clearInterval(timer);
|
|
||||||
resolve(true);
|
|
||||||
} else if (elapsed >= BOOT_WAIT_MAX_MS) {
|
|
||||||
clearInterval(timer);
|
|
||||||
resolve(false);
|
|
||||||
}
|
}
|
||||||
}, BOOT_POLL_MS);
|
}
|
||||||
});
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
@@ -126,10 +121,13 @@ class ProvisionManager extends EventEmitter {
|
|||||||
if (!hasInternet()) led.display.showAP(); // 无网时立即显示 AP,有线时等 WS 连接后再定
|
if (!hasInternet()) led.display.showAP(); // 无网时立即显示 AP,有线时等 WS 连接后再定
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 若上次进程退出前留下 clawd-hotspot,必须先释放 wlan0;否则会在 AP 模式下扫描,列表可能只剩 2.4G/自身热点。
|
||||||
|
stopAP();
|
||||||
|
|
||||||
// AP 模式下无法扫描 WiFi,必须在开 AP 之前扫描并缓存
|
// AP 模式下无法扫描 WiFi,必须在开 AP 之前扫描并缓存
|
||||||
log.info('provision', '扫描周边 WiFi...');
|
log.info('provision', '扫描周边 WiFi...');
|
||||||
this._cachedWifiList = scanWifi();
|
this._cachedWifiList = scanWifi();
|
||||||
log.info('provision', `扫描到 ${this._cachedWifiList.length} 个网络`);
|
log.info('provision', `扫描到 ${this._cachedWifiList.length} 个网络: ${this._cachedWifiList.map(w => `${w.ssid}${w.band ? `(${w.band})` : ''}`).join(', ')}`);
|
||||||
|
|
||||||
// 写 DNS 劫持配置(NM 启动热点时加载);接口名与热点一致,勿写死 wlan0
|
// 写 DNS 劫持配置(NM 启动热点时加载);接口名与热点一致,勿写死 wlan0
|
||||||
this._dns = new DnsHijack();
|
this._dns = new DnsHijack();
|
||||||
@@ -145,6 +143,8 @@ class ProvisionManager extends EventEmitter {
|
|||||||
this._server.startListening();
|
this._server.startListening();
|
||||||
|
|
||||||
this._state = 'ap';
|
this._state = 'ap';
|
||||||
|
this._apStartedAt = Date.now();
|
||||||
|
this._lastApSavedWifiRetryAt = 0;
|
||||||
log.info('provision', `AP 常驻模式已启动: ${ap.ssid}, 密码 12345678`);
|
log.info('provision', `AP 常驻模式已启动: ${ap.ssid}, 密码 12345678`);
|
||||||
log.info('provision', `配网地址: http://10.42.0.1`);
|
log.info('provision', `配网地址: http://10.42.0.1`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -178,16 +178,27 @@ class ProvisionManager extends EventEmitter {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn('provision', `WiFi 连接失败: ${result.error},重新启动 AP`);
|
log.warn('provision', `WiFi 连接失败: ${result.error},按当前网络状态恢复`);
|
||||||
this._safeReenterAP();
|
this._recoverAfterWifiFailure();
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('provision', `配网过程异常: ${e.message}`);
|
log.error('provision', `配网过程异常: ${e.message}`);
|
||||||
this._safeReenterAP();
|
this._recoverAfterWifiFailure();
|
||||||
return { success: false, error: e.message };
|
return { success: false, error: e.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** WiFi 连接失败后:有线可用则保持 wired;否则开 AP 兜底。 */
|
||||||
|
_recoverAfterWifiFailure() {
|
||||||
|
if (hasInternet()) {
|
||||||
|
this._state = 'wired';
|
||||||
|
led.off();
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._safeReenterAP();
|
||||||
|
}
|
||||||
|
|
||||||
/** 重新开 AP;失败时勿把 _state 永久卡在 connecting */
|
/** 重新开 AP;失败时勿把 _state 永久卡在 connecting */
|
||||||
_safeReenterAP() {
|
_safeReenterAP() {
|
||||||
try {
|
try {
|
||||||
@@ -202,34 +213,116 @@ class ProvisionManager extends EventEmitter {
|
|||||||
|
|
||||||
_startMonitor() {
|
_startMonitor() {
|
||||||
this._monitorTimer = setInterval(() => {
|
this._monitorTimer = setInterval(() => {
|
||||||
|
if (this._monitorBusy) return;
|
||||||
|
this._monitorBusy = true;
|
||||||
|
this._monitorTick()
|
||||||
|
.catch((e) => log.error('provision', `WiFi 状态监控异常: ${e.message}`))
|
||||||
|
.finally(() => { this._monitorBusy = false; });
|
||||||
|
}, MONITOR_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _monitorTick() {
|
||||||
if (this._state === 'connecting') return;
|
if (this._state === 'connecting') return;
|
||||||
|
|
||||||
const wifiUp = isWifiStaConnected();
|
const wifiUp = isWifiStaConnected();
|
||||||
|
|
||||||
if (this._state === 'sta' && !wifiUp) {
|
if (wifiUp && this._state !== 'sta') {
|
||||||
log.warn('provision', 'WiFi 连接已断开,重新启动 AP');
|
if (this._state === 'ap') {
|
||||||
this._enterAP(); // 内部调用 led.off()
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._state === 'ap' && wifiUp) {
|
|
||||||
log.info('provision', 'WiFi 已外部连接,关闭 AP');
|
log.info('provision', 'WiFi 已外部连接,关闭 AP');
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
|
}
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
this.emit('network-ready');
|
this.emit('network-ready');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 产品 WiFi 灯(OpenVFD wifi+eth):AP 全程强制熄灭,避免与其它逻辑竞态导致误亮
|
if (this._state === 'sta' && !wifiUp) {
|
||||||
if (this._state === 'ap') {
|
log.warn('provision', 'WiFi 连接已断开,尝试恢复网络');
|
||||||
|
await this._recoverNetworkWithoutWifi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._state === 'wired') {
|
||||||
|
if (!hasInternet()) {
|
||||||
|
log.warn('provision', '有线网络不可用,尝试恢复 WiFi');
|
||||||
|
await this._recoverNetworkWithoutWifi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
led.off();
|
led.off();
|
||||||
} else if (this._state === 'sta') {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._state === 'ap') {
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
led.on();
|
log.info('provision', '检测到有线网络可用,关闭 AP');
|
||||||
} else {
|
this._stopAPServices();
|
||||||
led.off(); // STA 已连热点但无互联网
|
this._state = 'wired';
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSavedWifiConnection() && this._shouldRetrySavedWifiFromAP()) {
|
||||||
|
await this._retrySavedWifiFromAP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
led.off();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, MONITOR_INTERVAL_MS);
|
|
||||||
|
async _recoverNetworkWithoutWifi() {
|
||||||
|
this._state = 'connecting';
|
||||||
|
led.blink();
|
||||||
|
|
||||||
|
if (hasSavedWifiConnection()) {
|
||||||
|
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
||||||
|
if (connected) {
|
||||||
|
this._state = 'sta';
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasInternet()) {
|
||||||
|
this._state = 'wired';
|
||||||
|
led.off();
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._safeReenterAP();
|
||||||
|
}
|
||||||
|
|
||||||
|
_shouldRetrySavedWifiFromAP() {
|
||||||
|
const now = Date.now();
|
||||||
|
if (this._apStartedAt && now - this._apStartedAt < AP_MIN_UP_BEFORE_RETRY_MS) return false;
|
||||||
|
if (this._lastApSavedWifiRetryAt && now - this._lastApSavedWifiRetryAt < AP_SAVED_WIFI_RETRY_INTERVAL_MS) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _retrySavedWifiFromAP() {
|
||||||
|
this._lastApSavedWifiRetryAt = Date.now();
|
||||||
|
log.info('provision', 'AP 模式下定期尝试已保存 WiFi');
|
||||||
|
this._state = 'connecting';
|
||||||
|
led.blink();
|
||||||
|
this._stopAPServices();
|
||||||
|
await new Promise((r) => setTimeout(r, 3500));
|
||||||
|
|
||||||
|
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
||||||
|
if (connected) {
|
||||||
|
this._state = 'sta';
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasInternet()) {
|
||||||
|
this._state = 'wired';
|
||||||
|
led.off();
|
||||||
|
this._emitNetworkReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn('provision', 'AP 模式下重试已保存 WiFi 失败,恢复 AP');
|
||||||
|
this._safeReenterAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopMonitor() {
|
_stopMonitor() {
|
||||||
|
|||||||
Reference in New Issue
Block a user