fix: stabilize rk3588 wifi provisioning
This commit is contained in:
@@ -263,11 +263,12 @@ class ClawClient {
|
|||||||
_connect() {
|
_connect() {
|
||||||
if (this._stopped) return;
|
if (this._stopped) return;
|
||||||
|
|
||||||
// AP 模式 + 无网:不建立 WS,5s 后重新检查(有线经 -I ping 仍通则建立,避免热点误挡 WS)
|
// AP 模式下 hasInternet() 可能被热点本地网络 / NetworkManager limited 状态误判。
|
||||||
if (this._provisionMgr && this._provisionMgr.isApMode() && !hasInternet() && !hasWiredInternetProbe()) {
|
// 只有明确探测到有线口可访问公网时,才允许进入 WS 连接流程并显示 Conn。
|
||||||
|
if (this._provisionMgr && this._provisionMgr.isApMode() && !hasWiredInternetProbe()) {
|
||||||
led.display.showAP();
|
led.display.showAP();
|
||||||
log.info('clawd', 'AP 模式无网络,5s 后重新检查...');
|
log.info('clawd', 'AP 模式无有线网络,5s 后重新检查...');
|
||||||
this._backoff = 1_000; // 有网时立即快速重连
|
this._backoff = 1_000; // 有线恢复时立即快速重连
|
||||||
this._wsFailCount = 0; // 不计入失败
|
this._wsFailCount = 0; // 不计入失败
|
||||||
setTimeout(() => this._connect(), 5_000);
|
setTimeout(() => this._connect(), 5_000);
|
||||||
return;
|
return;
|
||||||
|
|||||||
298
lib/network.js
298
lib/network.js
@@ -10,6 +10,7 @@ const AP_IP = '10.42.0.1';
|
|||||||
const AP_PASSWORD = '12345678';
|
const AP_PASSWORD = '12345678';
|
||||||
const AP_IFACE = process.env.CLAWD_WIFI_IFACE || '';
|
const AP_IFACE = process.env.CLAWD_WIFI_IFACE || '';
|
||||||
const CON_NAME = 'clawd-hotspot';
|
const CON_NAME = 'clawd-hotspot';
|
||||||
|
const AP_RETRY_TOKEN_FILE = '/run/clawd-ap-retry.token';
|
||||||
|
|
||||||
/** 产品 RJ45 在 sysfs 中的默认名;等价于检测 `cat /sys/class/net/end0/carrier` */
|
/** 产品 RJ45 在 sysfs 中的默认名;等价于检测 `cat /sys/class/net/end0/carrier` */
|
||||||
const DEFAULT_ETH_IFACE = 'end0';
|
const DEFAULT_ETH_IFACE = 'end0';
|
||||||
@@ -92,28 +93,15 @@ function hasLanCableCarrier() {
|
|||||||
return hasWiredCarrier();
|
return hasWiredCarrier();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _tryPingInternet() {
|
function _tryPingDefaultInternet() {
|
||||||
try {
|
try {
|
||||||
run('ping -c 1 -W 3 8.8.8.8');
|
run('ping -c 1 -W 3 8.8.8.8');
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// 开热点时默认路由可能走 wlan,无 -I 的 ping 会误判;指定有线口再试
|
|
||||||
const wired = getWiredIfaceWithCarrier();
|
|
||||||
if (wired) {
|
|
||||||
try {
|
|
||||||
run(`ping -c 1 -W 3 -I ${wired} 8.8.8.8`);
|
|
||||||
return true;
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function _tryPingWiredInternet() {
|
||||||
* 仅经有线口 ping 公网(不依赖默认路由)。
|
|
||||||
* AP 开启时 hasInternet() 易误判;维持 WS / 网络监视时用此兜底。
|
|
||||||
*/
|
|
||||||
function hasWiredInternetProbe() {
|
|
||||||
const wired = getWiredIfaceWithCarrier();
|
const wired = getWiredIfaceWithCarrier();
|
||||||
if (!wired) return false;
|
if (!wired) return false;
|
||||||
try {
|
try {
|
||||||
@@ -124,18 +112,31 @@ function hasWiredInternetProbe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测是否有互联网连接(nmcli 连通性 + ping 兜底)
|
* 仅经有线口 ping 公网(不依赖默认路由)。
|
||||||
|
*/
|
||||||
|
function hasWiredInternetProbe() {
|
||||||
|
return _tryPingWiredInternet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否有真实互联网连接。
|
||||||
|
* 注意:NetworkManager 的 limited/local 可能只是 AP 本地网络或 captive 状态,不能当公网可用。
|
||||||
*/
|
*/
|
||||||
function hasInternet() {
|
function hasInternet() {
|
||||||
|
const wifiSta = isWifiStaConnected();
|
||||||
|
const wired = getWiredIfaceWithCarrier();
|
||||||
|
|
||||||
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信)
|
// 物理层快检:无 WiFi STA 且无任何有线 carrier → 立即 false(nmcli 有缓存,不可信)
|
||||||
if (!isWifiStaConnected() && !hasWiredCarrier()) return false;
|
if (!wifiSta && !wired) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const out = run('nmcli networking connectivity check').trim();
|
const out = run('nmcli networking connectivity check').trim();
|
||||||
if (out === 'full' || out === 'limited') return true;
|
if (out === 'full') return true;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
return _tryPingInternet();
|
if (wifiSta) return _tryPingDefaultInternet();
|
||||||
|
if (wired) return _tryPingWiredInternet();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,6 +266,7 @@ function nmcliAsync(args, timeoutMs = 60000) {
|
|||||||
* @returns {Promise<{ success: boolean, error?: string }>}
|
* @returns {Promise<{ success: boolean, error?: string }>}
|
||||||
*/
|
*/
|
||||||
async function connectWifi(ssid, password) {
|
async function connectWifi(ssid, password) {
|
||||||
|
cancelHotspotRadioRetry(`准备连接 WiFi: ${ssid}`);
|
||||||
const iface = getWifiIface();
|
const iface = getWifiIface();
|
||||||
log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`);
|
log.info('network', `尝试连接 WiFi: ${ssid}(ifname=${iface})`);
|
||||||
try {
|
try {
|
||||||
@@ -272,14 +274,36 @@ async function connectWifi(ssid, password) {
|
|||||||
await nmcliAsync(['connection', 'delete', ssid], 15000);
|
await nmcliAsync(['connection', 'delete', ssid], 15000);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
try {
|
await _resetWifiRadioForSTA(iface);
|
||||||
await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000);
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
const args = ['device', 'wifi', 'connect', ssid];
|
if (password) {
|
||||||
if (password) args.push('password', password);
|
// 显式创建 STA profile,并固定为 WPA2-PSK only。
|
||||||
args.push('ifname', iface);
|
// RK3588/Broadcom DHD 对 NetworkManager 默认生成的 SAE/FT/WPA-PSK-SHA256 混合参数不稳定,
|
||||||
await nmcliAsync(args, 120000);
|
// 可能表现为一直 associating -> disconnected,最后误报“需要密钥”。
|
||||||
|
await nmcliAsync([
|
||||||
|
'connection', 'add',
|
||||||
|
'type', 'wifi',
|
||||||
|
'ifname', iface,
|
||||||
|
'con-name', ssid,
|
||||||
|
'ssid', ssid,
|
||||||
|
], 15000);
|
||||||
|
|
||||||
|
await nmcliAsync([
|
||||||
|
'connection', 'modify', ssid,
|
||||||
|
// 连接成功前先禁止自动连接,避免失败恢复 AP 时 NM 又自动抢占 wlan0。
|
||||||
|
'connection.autoconnect', 'no',
|
||||||
|
'802-11-wireless-security.key-mgmt', 'wpa-psk',
|
||||||
|
'802-11-wireless-security.proto', 'rsn',
|
||||||
|
'802-11-wireless-security.pairwise', 'ccmp',
|
||||||
|
'802-11-wireless-security.group', 'ccmp',
|
||||||
|
'802-11-wireless-security.pmf', 'disable',
|
||||||
|
'802-11-wireless-security.psk', password,
|
||||||
|
], 15000);
|
||||||
|
|
||||||
|
await nmcliAsync(['connection', 'up', 'id', ssid, 'ifname', iface], 120000);
|
||||||
|
} else {
|
||||||
|
await nmcliAsync(['device', 'wifi', 'connect', ssid, 'ifname', iface], 120000);
|
||||||
|
}
|
||||||
await _ensureActiveWifiAutoconnect();
|
await _ensureActiveWifiAutoconnect();
|
||||||
|
|
||||||
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
|
const deadline = Date.now() + CONNECT_WIFI_STA_WAIT_MS;
|
||||||
@@ -299,11 +323,201 @@ async function connectWifi(ssid, password) {
|
|||||||
}
|
}
|
||||||
return { success: false, error: '超时:网卡未进入已连接状态' };
|
return { success: false, error: '超时:网卡未进入已连接状态' };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
try { await nmcliAsync(['connection', 'modify', ssid, 'connection.autoconnect', 'no'], 8000); } catch (_) {}
|
||||||
log.error('network', `WiFi 连接失败: ${e.message}`);
|
log.error('network', `WiFi 连接失败: ${e.message}`);
|
||||||
return { success: false, error: e.message };
|
return { success: false, error: e.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _newHotspotRetryToken() {
|
||||||
|
const token = `${process.pid}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(AP_RETRY_TOKEN_FILE, token, { mode: 0o600 });
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `写入 AP retry token 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelHotspotRadioRetry(reason = 'cancel') {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(AP_RETRY_TOKEN_FILE);
|
||||||
|
log.info('network', `已取消后台 AP retry: ${reason}`);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _resetWifiRadioForSTA(iface, reason = '准备连接 STA 前重置 WiFi radio') {
|
||||||
|
log.warn('network', `${reason}: ${iface}`);
|
||||||
|
|
||||||
|
try { await nmcliAsync(['connection', 'down', CON_NAME], 8000); } catch (_) {}
|
||||||
|
try { await nmcliAsync(['connection', 'delete', CON_NAME], 8000); } catch (_) {}
|
||||||
|
try { await nmcliAsync(['device', 'disconnect', iface], 8000); } catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await nmcliAsync(['radio', 'wifi', 'off'], 10000);
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `关闭 WiFi radio 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _delay(2500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await nmcliAsync(['radio', 'wifi', 'on'], 10000);
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `开启 WiFi radio 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _delay(5000);
|
||||||
|
try { await nmcliAsync(['device', 'set', iface, 'managed', 'yes'], 8000); } catch (_) {}
|
||||||
|
try { await nmcliAsync(['device', 'wifi', 'rescan', 'ifname', iface], 15000); } catch (_) {}
|
||||||
|
await _delay(1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resetWifiRadioForAP(iface, reason = '准备 AP 前重置 WiFi radio') {
|
||||||
|
log.warn('network', `${reason}: ${iface}`);
|
||||||
|
|
||||||
|
try { nmcliSync(['connection', 'down', CON_NAME], 8000); } catch (_) {}
|
||||||
|
try { nmcliSync(['connection', 'delete', CON_NAME], 8000); } catch (_) {}
|
||||||
|
try { nmcliSync(['device', 'disconnect', iface], 8000); } catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
nmcliSync(['radio', 'wifi', 'off'], 10000);
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `关闭 WiFi radio 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2500);
|
||||||
|
|
||||||
|
try {
|
||||||
|
nmcliSync(['radio', 'wifi', 'on'], 10000);
|
||||||
|
} catch (e) {
|
||||||
|
log.warn('network', `开启 WiFi radio 失败: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(5000);
|
||||||
|
try { nmcliSync(['device', 'set', iface, 'managed', 'yes'], 8000); } catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _spawnHotspotRadioRetry(ssid, iface) {
|
||||||
|
const token = _newHotspotRetryToken();
|
||||||
|
const script = `
|
||||||
|
set -u
|
||||||
|
log() { logger -t clawd-ap-retry "$*"; }
|
||||||
|
check_token() {
|
||||||
|
if [ ! -f "$TOKEN_FILE" ] || [ "$(cat "$TOKEN_FILE" 2>/dev/null || true)" != "$TOKEN" ]; then
|
||||||
|
log "AP retry canceled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
log "AP retry started: ssid=$SSID iface=$IFACE"
|
||||||
|
check_token
|
||||||
|
nmcli connection down "$CON_NAME" >/dev/null 2>&1 || true
|
||||||
|
nmcli connection delete "$CON_NAME" >/dev/null 2>&1 || true
|
||||||
|
nmcli device disconnect "$IFACE" >/dev/null 2>&1 || true
|
||||||
|
check_token
|
||||||
|
nmcli radio wifi off >/dev/null 2>&1 || true
|
||||||
|
sleep 2.5
|
||||||
|
# If canceled while radio is off, always turn it back on before exiting.
|
||||||
|
nmcli radio wifi on >/dev/null 2>&1 || true
|
||||||
|
sleep 5
|
||||||
|
check_token
|
||||||
|
nmcli device set "$IFACE" managed yes >/dev/null 2>&1 || true
|
||||||
|
check_token
|
||||||
|
if ! nmcli connection add type wifi ifname "$IFACE" con-name "$CON_NAME" ssid "$SSID" >/dev/null 2>&1; then
|
||||||
|
log "AP retry failed: connection add failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
args=(
|
||||||
|
connection modify "$CON_NAME"
|
||||||
|
connection.autoconnect no
|
||||||
|
802-11-wireless.mode ap
|
||||||
|
802-11-wireless.band bg
|
||||||
|
802-11-wireless.channel 1
|
||||||
|
802-11-wireless-security.key-mgmt wpa-psk
|
||||||
|
802-11-wireless-security.proto rsn
|
||||||
|
802-11-wireless-security.pairwise ccmp
|
||||||
|
802-11-wireless-security.group ccmp
|
||||||
|
802-11-wireless-security.pmf disable
|
||||||
|
ipv4.method shared
|
||||||
|
ipv4.addresses "$AP_IP/24"
|
||||||
|
ipv6.method ignore
|
||||||
|
)
|
||||||
|
if [ -n "\${AP_PASSWORD:-}" ]; then
|
||||||
|
args+=(802-11-wireless-security.psk "$AP_PASSWORD")
|
||||||
|
fi
|
||||||
|
check_token
|
||||||
|
if ! nmcli "\${args[@]}" >/dev/null 2>&1; then
|
||||||
|
log "AP retry failed: connection modify failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
check_token
|
||||||
|
if nmcli connection up "$CON_NAME" >/dev/null 2>&1; then
|
||||||
|
if [ ! -f "$TOKEN_FILE" ] || [ "$(cat "$TOKEN_FILE" 2>/dev/null || true)" != "$TOKEN" ]; then
|
||||||
|
log "AP retry canceled after connection up; tearing hotspot down"
|
||||||
|
nmcli connection down "$CON_NAME" >/dev/null 2>&1 || true
|
||||||
|
nmcli connection delete "$CON_NAME" >/dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
log "AP retry success: $SSID"
|
||||||
|
rm -f "$TOKEN_FILE"
|
||||||
|
else
|
||||||
|
log "AP retry failed: connection up failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
`;
|
||||||
|
|
||||||
|
const child = spawn('/bin/bash', ['-lc', script], {
|
||||||
|
detached: true,
|
||||||
|
stdio: 'ignore',
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
SSID: ssid,
|
||||||
|
IFACE: iface,
|
||||||
|
CON_NAME,
|
||||||
|
AP_IP,
|
||||||
|
AP_PASSWORD: AP_PASSWORD || '',
|
||||||
|
TOKEN_FILE: AP_RETRY_TOKEN_FILE,
|
||||||
|
TOKEN: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
child.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createHotspotProfile(ssid, iface) {
|
||||||
|
nmcliSync([
|
||||||
|
'connection', 'add',
|
||||||
|
'type', 'wifi',
|
||||||
|
'ifname', iface,
|
||||||
|
'con-name', CON_NAME,
|
||||||
|
'ssid', ssid,
|
||||||
|
], 15000);
|
||||||
|
|
||||||
|
const modifyArgs = [
|
||||||
|
'connection', 'modify', CON_NAME,
|
||||||
|
'connection.autoconnect', 'no',
|
||||||
|
'802-11-wireless.mode', 'ap',
|
||||||
|
'802-11-wireless.band', 'bg',
|
||||||
|
'802-11-wireless.channel', '1',
|
||||||
|
'802-11-wireless-security.key-mgmt', 'wpa-psk',
|
||||||
|
'802-11-wireless-security.proto', 'rsn',
|
||||||
|
'802-11-wireless-security.pairwise', 'ccmp',
|
||||||
|
'802-11-wireless-security.group', 'ccmp',
|
||||||
|
'802-11-wireless-security.pmf', 'disable',
|
||||||
|
'ipv4.method', 'shared',
|
||||||
|
'ipv4.addresses', `${AP_IP}/24`,
|
||||||
|
'ipv6.method', 'ignore',
|
||||||
|
];
|
||||||
|
if (AP_PASSWORD) {
|
||||||
|
modifyArgs.push('802-11-wireless-security.psk', AP_PASSWORD);
|
||||||
|
}
|
||||||
|
nmcliSync(modifyArgs, 15000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _activateHotspot(ssid, iface, timeoutMs = 8000) {
|
||||||
|
_createHotspotProfile(ssid, iface);
|
||||||
|
nmcliSync(['connection', 'up', CON_NAME], timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动 WiFi AP 热点
|
* 启动 WiFi AP 热点
|
||||||
*/
|
*/
|
||||||
@@ -313,29 +527,24 @@ function startAP(clawId) {
|
|||||||
|
|
||||||
log.info('network', `启动 AP 热点: ${ssid} (${iface})`);
|
log.info('network', `启动 AP 热点: ${ssid} (${iface})`);
|
||||||
|
|
||||||
// 关闭已有热点
|
// 关闭已有热点,并在重新拉起 AP 前真正 power-cycle WiFi 芯片。
|
||||||
|
// RK3588/Broadcom DHD 在 LAN 断开后切 AP 时,单纯 ip link down/up 不一定清掉固件残留状态。
|
||||||
stopAP();
|
stopAP();
|
||||||
|
_resetWifiRadioForAP(iface, '准备 AP 前重置 WiFi radio');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// nmcli 创建热点(开放网络)
|
// 显式创建并激活热点,固定为 WPA2-PSK only。
|
||||||
const cmd = [
|
// 避免 NetworkManager 自动生成 WPA-PSK-SHA256/SAE 混合配置,部分 3588 Broadcom DHD 驱动重启 AP 时会拒绝该 RSN 参数。
|
||||||
'nmcli device wifi hotspot',
|
|
||||||
`ifname ${iface}`,
|
|
||||||
`con-name ${CON_NAME}`,
|
|
||||||
`ssid "${ssid}"`,
|
|
||||||
'band bg',
|
|
||||||
];
|
|
||||||
// 如果需要密码
|
|
||||||
if (AP_PASSWORD) {
|
|
||||||
cmd.push(`password "${AP_PASSWORD}"`);
|
|
||||||
}
|
|
||||||
run(cmd.join(' '));
|
|
||||||
try {
|
try {
|
||||||
nmcliSync(['connection', 'modify', CON_NAME, 'connection.autoconnect', 'no'], 8000);
|
_activateHotspot(ssid, iface, 8000);
|
||||||
} catch (_) {}
|
} catch (firstError) {
|
||||||
|
log.warn('network', `AP 启动未在短超时内完成,后台再次重置 WiFi radio 后重试;避免阻塞 watchdog: ${firstError.message}`);
|
||||||
|
_spawnHotspotRadioRetry(ssid, iface);
|
||||||
|
return { ssid, ip: AP_IP, iface, pending: true };
|
||||||
|
}
|
||||||
|
|
||||||
// 等待 AP 启动
|
// 等待 AP 启动
|
||||||
sleep(2000);
|
sleep(1000);
|
||||||
log.info('network', `AP 已启动: ${ssid}, 网关 ${AP_IP}`);
|
log.info('network', `AP 已启动: ${ssid}, 网关 ${AP_IP}`);
|
||||||
return { ssid, ip: AP_IP, iface };
|
return { ssid, ip: AP_IP, iface };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -348,6 +557,7 @@ function startAP(clawId) {
|
|||||||
* 关闭热点,恢复普通 WiFi 模式
|
* 关闭热点,恢复普通 WiFi 模式
|
||||||
*/
|
*/
|
||||||
function stopAP() {
|
function stopAP() {
|
||||||
|
cancelHotspotRadioRetry('停止 AP');
|
||||||
try {
|
try {
|
||||||
run(`nmcli connection down ${CON_NAME}`);
|
run(`nmcli connection down ${CON_NAME}`);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@@ -454,6 +664,7 @@ async function _ensureActiveWifiAutoconnect() {
|
|||||||
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
|
* clawd 只做调度;真正的认证、DHCP、重连细节仍交给 NM。
|
||||||
*/
|
*/
|
||||||
async function connectSavedWifiConnections() {
|
async function connectSavedWifiConnections() {
|
||||||
|
cancelHotspotRadioRetry('准备连接已保存 WiFi');
|
||||||
const iface = getWifiIface();
|
const iface = getWifiIface();
|
||||||
const profiles = listSavedWifiConnections();
|
const profiles = listSavedWifiConnections();
|
||||||
if (profiles.length === 0) {
|
if (profiles.length === 0) {
|
||||||
@@ -574,6 +785,7 @@ module.exports = {
|
|||||||
connectWifi,
|
connectWifi,
|
||||||
startAP,
|
startAP,
|
||||||
stopAP,
|
stopAP,
|
||||||
|
cancelHotspotRadioRetry,
|
||||||
AP_IP,
|
AP_IP,
|
||||||
getLocalIps,
|
getLocalIps,
|
||||||
getLocalNetworks,
|
getLocalNetworks,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
const log = require('./logger');
|
const log = require('./logger');
|
||||||
const { hasInternet, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network');
|
const { hasInternet, hasWiredInternetProbe, 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');
|
||||||
@@ -258,7 +258,9 @@ class ProvisionManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._state === 'ap') {
|
if (this._state === 'ap') {
|
||||||
if (hasInternet()) {
|
// AP 模式下 hasInternet() 可能被热点本地网络 / NetworkManager limited 状态误判。
|
||||||
|
// 只有明确探测到有线口可访问公网时,才关闭配网 AP。
|
||||||
|
if (hasWiredInternetProbe()) {
|
||||||
log.info('provision', '检测到有线网络可用,关闭 AP');
|
log.info('provision', '检测到有线网络可用,关闭 AP');
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ssh2": "^1.17.0",
|
"ssh2": "^1.17.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.4.5",
|
"version": "1.4.6",
|
||||||
"description": "Claw Box daemon - connects local Linux box to claw.cutos.ai via WebSocket",
|
"description": "Claw Box daemon - connects local Linux box to claw.cutos.ai via WebSocket",
|
||||||
"main": "lib/client.js",
|
"main": "lib/client.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
Reference in New Issue
Block a user