diff --git a/lib/network.js b/lib/network.js index 014339b..6261c1f 100644 --- a/lib/network.js +++ b/lib/network.js @@ -1,6 +1,6 @@ 'use strict'; -const { execSync, spawnSync } = require('child_process'); +const { execSync, spawnSync, spawn } = require('child_process'); const fs = require('fs'); const os = require('os'); const log = require('./logger'); @@ -222,27 +222,59 @@ function nmcliSync(args, timeoutMs = 60000) { return (r.stdout || '').trim(); } +function _delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** 异步 nmcli,不阻塞事件循环(systemd Watchdog 依赖 setInterval 在主线程运行) */ +function nmcliAsync(args, timeoutMs = 60000) { + return new Promise((resolve, reject) => { + const child = spawn('nmcli', args, { stdio: ['ignore', 'pipe', 'pipe'] }); + let stdout = ''; + let stderr = ''; + const timer = setTimeout(() => { + child.kill('SIGKILL'); + reject(new Error('nmcli 超时')); + }, timeoutMs); + child.stdout.on('data', (d) => { stdout += d; }); + child.stderr.on('data', (d) => { stderr += d; }); + child.on('error', (err) => { + clearTimeout(timer); + reject(err); + }); + child.on('close', (code) => { + clearTimeout(timer); + if (code !== 0) { + const msg = stderr.trim() || stdout.trim() || `nmcli exit ${code}`; + reject(new Error(msg)); + } else { + resolve(stdout.trim()); + } + }); + }); +} + /** * 连接指定 WiFi(配网场景:成功 = NM 显示 STA 已连上目标网,不要求一定能 ping 通 8.8.8.8) - * @returns {{ success: boolean, error?: string }} + * 必须异步:同步 spawnSync + execSync(sleep) 会卡住主线程,导致 systemd WatchdogSec 内收不到 WATCHDOG=1。 + * @returns {Promise<{ success: boolean, error?: string }>} */ -function connectWifi(ssid, password) { +async function connectWifi(ssid, password) { const iface = getWifiIface(); log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`); try { try { - nmcliSync(['connection', 'delete', ssid], 15000); + await nmcliAsync(['connection', 'delete', ssid], 15000); } catch (_) {} - // 关热点后部分固件需显式保证由 NM 管理、再关联 try { - nmcliSync(['device', 'set', iface, 'managed', 'yes'], 8000); + await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000); } catch (_) {} const args = ['device', 'wifi', 'connect', ssid]; if (password) args.push('password', password); args.push('ifname', iface); - nmcliSync(args, 120000); + await nmcliAsync(args, 120000); const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS; while (Date.now() < deadline) { @@ -257,7 +289,7 @@ function connectWifi(ssid, password) { } return { success: true }; } - sleep(CONNECT_WIFI_STA_POLL_MS); + await _delay(CONNECT_WIFI_STA_POLL_MS); } return { success: false, error: '超时:网卡未进入已连接状态' }; } catch (e) { diff --git a/lib/provisioning.js b/lib/provisioning.js index d56a0bc..065f4e2 100644 --- a/lib/provisioning.js +++ b/lib/provisioning.js @@ -168,7 +168,7 @@ class ProvisionManager extends EventEmitter { // 关热点后射频/模式切换需要时间,立刻 connect 在部分板子上会失败 await new Promise((r) => setTimeout(r, 3500)); - const result = connectWifi(ssid, password); + const result = await connectWifi(ssid, password); if (result.success) { this._state = 'sta';