fix: improve WiFi AP recovery and scan
This commit is contained in:
135
lib/network.js
135
lib/network.js
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user