fix: improve WiFi AP recovery and scan

This commit is contained in:
stswangzhiping
2026-04-26 10:57:54 +08:00
parent 18bea4ae38
commit f6aad310a8
2 changed files with 283 additions and 79 deletions

View File

@@ -180,19 +180,24 @@ function scanWifi() {
// 等扫描完成
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 results = [];
for (const line of out.split('\n')) {
if (!line.trim()) continue;
const parts = line.split(':');
const ssid = parts[0].trim().replace(/\\:/g, ':');
const parts = _parseNmcliTerseLine(line);
const ssid = (parts[0] || '').trim();
if (!ssid || seen.has(ssid)) continue;
seen.add(ssid);
const freq = (parts[3] || '').trim();
const freqMhz = parseInt(freq, 10) || null;
results.push({
ssid,
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);
@@ -275,6 +280,7 @@ async function connectWifi(ssid, password) {
if (password) args.push('password', password);
args.push('ifname', iface);
await nmcliAsync(args, 120000);
await _ensureActiveWifiAutoconnect();
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
while (Date.now() < deadline) {
@@ -324,6 +330,9 @@ function startAP(clawId) {
cmd.push(`password "${AP_PASSWORD}"`);
}
run(cmd.join(' '));
try {
nmcliSync(['connection', 'modify', CON_NAME, 'connection.autoconnect', 'no'], 8000);
} catch (_) {}
// 等待 AP 启动
sleep(2000);
@@ -361,20 +370,120 @@ function sleep(ms) {
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 连接(排除自身热点)
*/
function hasSavedWifiConnection() {
return listSavedWifiConnections().length > 0;
}
function getWifiActiveConnectionName() {
const iface = getWifiIface();
try {
const out = run('nmcli -t -f NAME,TYPE connection show');
for (const line of out.split('\n')) {
const [name, type] = line.split(':');
if (type === '802-11-wireless' && name !== CON_NAME) {
return true;
}
}
const conn = nmcliSync(['-g', 'GENERAL.CONNECTION', 'device', 'show', iface], 8000).trim();
return conn && conn !== '--' ? conn : null;
} catch (_) {
return null;
}
}
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 (_) {}
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,
hasWiredInternetProbe,
getWiredIfaceWithCarrier,
listSavedWifiConnections,
hasSavedWifiConnection,
connectSavedWifiConnections,
isWifiStaConnected,
getWifiIface,
scanWifi,