feat(rk3588s): unify display state semantics and bump version to 1.4.5
This commit is contained in:
@@ -36,19 +36,19 @@ class StatusLed {
|
|||||||
|
|
||||||
class Display {
|
class Display {
|
||||||
showAP() {
|
showAP() {
|
||||||
if (writeLvglCommand('show_text:AP')) {
|
if (writeLvglCommand('show_ap')) {
|
||||||
log.info('display', '显示屏 → AP');
|
log.info('display', '显示屏 → AP(闪烁)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showConn() {
|
showConn() {
|
||||||
if (writeLvglCommand('show_text:Conn')) {
|
if (writeLvglCommand('show_conn')) {
|
||||||
log.info('display', '显示屏 → Conn');
|
log.info('display', '显示屏 → Conn(闪烁)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showErr0() {
|
showErr0() {
|
||||||
if (writeLvglCommand('show_text:Err0')) {
|
if (writeLvglCommand('show_err0')) {
|
||||||
log.info('display', '显示屏 → Err0');
|
log.info('display', '显示屏 → Err0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,8 +61,8 @@ class Display {
|
|||||||
|
|
||||||
showPin(pin) {
|
showPin(pin) {
|
||||||
const s = String(pin || '').padStart(4, '0').slice(-4);
|
const s = String(pin || '').padStart(4, '0').slice(-4);
|
||||||
if (writeLvglCommand(`show_text:${s}`)) {
|
if (writeLvglCommand(`show_pin:${s}`)) {
|
||||||
log.info('display', `显示屏 → PIN: ${s}`);
|
log.info('display', `显示屏 → PIN: ${s}(闪烁)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,360 +1,360 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
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, 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 WIFI_RECONNECT_MAX_ROUNDS = 3;
|
const WIFI_RECONNECT_MAX_ROUNDS = 3;
|
||||||
const WIFI_RECONNECT_ROUND_DELAY_MS = 5_000;
|
const WIFI_RECONNECT_ROUND_DELAY_MS = 5_000;
|
||||||
const AP_SAVED_WIFI_RETRY_INTERVAL_MS = 180_000;
|
const AP_SAVED_WIFI_RETRY_INTERVAL_MS = 180_000;
|
||||||
const AP_MIN_UP_BEFORE_RETRY_MS = 60_000;
|
const AP_MIN_UP_BEFORE_RETRY_MS = 60_000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AP 常驻配网管理器。
|
* AP 常驻配网管理器。
|
||||||
*
|
*
|
||||||
* 规则:
|
* 规则:
|
||||||
* - 启动时:WiFi STA 优先;有已保存 WiFi 时主动让 NM 重连,最多 3 轮
|
* - 启动时:WiFi STA 优先;有已保存 WiFi 时主动让 NM 重连,最多 3 轮
|
||||||
* - 有线网络可用时:通知网络就绪,但不自动开启 AP
|
* - 有线网络可用时:通知网络就绪,但不自动开启 AP
|
||||||
* - 自动开 AP 的唯一兜底:无有线/无 WiFi,且无 saved WiFi 或 saved WiFi 3 轮失败
|
* - 自动开 AP 的唯一兜底:无有线/无 WiFi,且无 saved WiFi 或 saved WiFi 3 轮失败
|
||||||
* - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则按网络状态决定是否重新开 AP
|
* - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则按网络状态决定是否重新开 AP
|
||||||
* - AP 状态下:若仍无有线网络,低频释放 wlan0 并尝试 saved WiFi
|
* - 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' | 'wired'
|
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._monitorBusy = false;
|
||||||
this._apStartedAt = 0;
|
this._apStartedAt = 0;
|
||||||
this._lastApSavedWifiRetryAt = 0;
|
this._lastApSavedWifiRetryAt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 是否正处于 AP 模式(WiFi 热点广播中) */
|
/** 是否正处于 AP 模式(WiFi 热点广播中) */
|
||||||
isApMode() { return this._state === 'ap'; }
|
isApMode() { return this._state === 'ap'; }
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
led.off(); // WiFi 灯初始状态:熄灭
|
led.off(); // WiFi 灯初始状态:熄灭
|
||||||
|
|
||||||
// WiFi STA 已连接 → 直接进入 STA 模式
|
// WiFi STA 已连接 → 直接进入 STA 模式
|
||||||
if (isWifiStaConnected()) {
|
if (isWifiStaConnected()) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
log.info('provision', 'WiFi STA 已连接,AP 不启动');
|
log.info('provision', 'WiFi STA 已连接,AP 不启动');
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 网络已就绪时先启动 WS;hasInternet() 可能来自 WiFi,也可能来自有线,不能直接当作 wired。
|
// 网络已就绪时先启动 WS;hasInternet() 可能来自 WiFi,也可能来自有线,不能直接当作 wired。
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
if (isWifiStaConnected()) {
|
if (isWifiStaConnected()) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
log.info('provision', 'WiFi STA 已连接,AP 不启动');
|
log.info('provision', 'WiFi STA 已连接,AP 不启动');
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
} else {
|
} else {
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
log.info('provision', '有线网络就绪,启动 WS;不自动开启 AP');
|
log.info('provision', '有线网络就绪,启动 WS;不自动开启 AP');
|
||||||
led.off();
|
led.off();
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
}
|
}
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无有线可用时,有 saved WiFi 才主动让 NetworkManager 重连;不要只被动等待 NM autoconnect。
|
// 无有线可用时,有 saved WiFi 才主动让 NetworkManager 重连;不要只被动等待 NM autoconnect。
|
||||||
if (hasSavedWifiConnection()) {
|
if (hasSavedWifiConnection()) {
|
||||||
log.info('provision', `发现已保存的 WiFi 配置,主动重连(最多 ${WIFI_RECONNECT_MAX_ROUNDS} 轮)...`);
|
log.info('provision', `发现已保存的 WiFi 配置,主动重连(最多 ${WIFI_RECONNECT_MAX_ROUNDS} 轮)...`);
|
||||||
this._state = 'connecting';
|
this._state = 'connecting';
|
||||||
led.blink();
|
led.blink();
|
||||||
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
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 重连失败');
|
log.warn('provision', '已保存 WiFi 重连失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无有线、无 WiFi;且无 saved WiFi 或 saved WiFi 3 轮失败 → 开 AP 兜底配网。
|
// 无有线、无 WiFi;且无 saved WiFi 或 saved WiFi 3 轮失败 → 开 AP 兜底配网。
|
||||||
this._enterAP();
|
this._enterAP();
|
||||||
this._startMonitor();
|
this._startMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
_emitNetworkReady() {
|
_emitNetworkReady() {
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
// WiFi 灯只在 STA 模式下亮(有线有网而 WiFi 在 AP 模式时不亮)
|
// WiFi 灯只在 STA 模式下亮(有线有网而 WiFi 在 AP 模式时不亮)
|
||||||
if (this._state === 'sta') led.on();
|
if (this._state === 'sta') led.on();
|
||||||
this.emit('network-ready');
|
this.emit('network-ready');
|
||||||
} else {
|
} else {
|
||||||
log.warn('provision', 'hasInternet() 返回 false,LED 保持熄灭');
|
log.warn('provision', 'hasInternet() 返回 false,LED 保持熄灭');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _trySavedWifiReconnectRounds(rounds = WIFI_RECONNECT_MAX_ROUNDS) {
|
async _trySavedWifiReconnectRounds(rounds = WIFI_RECONNECT_MAX_ROUNDS) {
|
||||||
for (let i = 1; i <= rounds; i++) {
|
for (let i = 1; i <= rounds; i++) {
|
||||||
if (isWifiStaConnected()) return true;
|
if (isWifiStaConnected()) return true;
|
||||||
log.info('provision', `尝试已保存 WiFi 重连:第 ${i}/${rounds} 轮`);
|
log.info('provision', `尝试已保存 WiFi 重连:第 ${i}/${rounds} 轮`);
|
||||||
const result = await connectSavedWifiConnections();
|
const result = await connectSavedWifiConnections();
|
||||||
if (result.success || isWifiStaConnected()) return true;
|
if (result.success || isWifiStaConnected()) return true;
|
||||||
if (i < rounds) {
|
if (i < rounds) {
|
||||||
await new Promise((r) => setTimeout(r, WIFI_RECONNECT_ROUND_DELAY_MS));
|
await new Promise((r) => setTimeout(r, WIFI_RECONNECT_ROUND_DELAY_MS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this._stopMonitor();
|
this._stopMonitor();
|
||||||
this._stopAll();
|
this._stopAll();
|
||||||
this._state = 'idle';
|
this._state = 'idle';
|
||||||
led.destroy(); // WiFi 灯:停止时关灯、释放闪烁定时器
|
led.destroy(); // WiFi 灯:停止时关灯、释放闪烁定时器
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 进入 AP 模式 ─────────────────────────────────────────────────────────
|
// ── 进入 AP 模式 ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_enterAP() {
|
_enterAP() {
|
||||||
if (this._state === 'ap') return;
|
if (this._state === 'ap') return;
|
||||||
|
|
||||||
led.off(); // AP 模式:WiFi 未连接,WiFi 灯熄灭
|
led.off(); // AP 模式:WiFi 未连接,WiFi 灯熄灭
|
||||||
if (!hasInternet()) led.display.showAP(); // 无网时立即显示 AP,有线时等 WS 连接后再定
|
|
||||||
|
try {
|
||||||
try {
|
// 若上次进程退出前留下 clawd-hotspot,必须先释放 wlan0;否则会在 AP 模式下扫描,列表可能只剩 2.4G/自身热点。
|
||||||
// 若上次进程退出前留下 clawd-hotspot,必须先释放 wlan0;否则会在 AP 模式下扫描,列表可能只剩 2.4G/自身热点。
|
stopAP();
|
||||||
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} 个网络: ${this._cachedWifiList.map(w => `${w.ssid}${w.band ? `(${w.band})` : ''}`).join(', ')}`);
|
||||||
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();
|
this._dns.start(getWifiIface(), AP_IP);
|
||||||
this._dns.start(getWifiIface(), AP_IP);
|
|
||||||
|
const ap = startAP(this._clawId);
|
||||||
const ap = startAP(this._clawId);
|
|
||||||
|
this._server = new CaptiveServer({
|
||||||
this._server = new CaptiveServer({
|
clawId: this._clawId,
|
||||||
clawId: this._clawId,
|
cachedWifiList: this._cachedWifiList,
|
||||||
cachedWifiList: this._cachedWifiList,
|
onConnect: (ssid, password) => this._handleWifiConnect(ssid, password),
|
||||||
onConnect: (ssid, password) => this._handleWifiConnect(ssid, password),
|
});
|
||||||
});
|
this._server.startListening();
|
||||||
this._server.startListening();
|
|
||||||
|
this._state = 'ap';
|
||||||
this._state = 'ap';
|
led.display.showAP();
|
||||||
this._apStartedAt = Date.now();
|
this._apStartedAt = Date.now();
|
||||||
this._lastApSavedWifiRetryAt = 0;
|
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) {
|
||||||
log.error('provision', `AP 启动失败: ${e.message}`);
|
log.error('provision', `AP 启动失败: ${e.message}`);
|
||||||
if (this._state !== 'sta') this._state = 'idle';
|
if (this._state !== 'sta') this._state = 'idle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 用户提交 WiFi 凭证 ───────────────────────────────────────────────────
|
// ── 用户提交 WiFi 凭证 ───────────────────────────────────────────────────
|
||||||
|
|
||||||
async _handleWifiConnect(ssid, password) {
|
async _handleWifiConnect(ssid, password) {
|
||||||
if (this._state === 'connecting') return { success: false, error: '正在连接中,请稍候' };
|
if (this._state === 'connecting') return { success: false, error: '正在连接中,请稍候' };
|
||||||
|
|
||||||
this._state = 'connecting';
|
this._state = 'connecting';
|
||||||
log.info('provision', `用户请求连接 WiFi: ${ssid}`);
|
log.info('provision', `用户请求连接 WiFi: ${ssid}`);
|
||||||
led.blink(); // 正在连接 → 闪烁
|
led.blink(); // 正在连接 → 闪烁
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
|
|
||||||
// 关热点后射频/模式切换需要时间,立刻 connect 在部分板子上会失败
|
// 关热点后射频/模式切换需要时间,立刻 connect 在部分板子上会失败
|
||||||
await new Promise((r) => setTimeout(r, 3500));
|
await new Promise((r) => setTimeout(r, 3500));
|
||||||
|
|
||||||
const result = await connectWifi(ssid, password);
|
const result = await connectWifi(ssid, password);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
log.info('provision', `WiFi 已连接: ${ssid}`);
|
log.info('provision', `WiFi 已连接: ${ssid}`);
|
||||||
led.on(); // WiFi 灯:连接成功 → 常亮
|
led.on(); // WiFi 灯:连接成功 → 常亮
|
||||||
this.emit('network-ready');
|
this.emit('network-ready');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn('provision', `WiFi 连接失败: ${result.error},按当前网络状态恢复`);
|
log.warn('provision', `WiFi 连接失败: ${result.error},按当前网络状态恢复`);
|
||||||
this._recoverAfterWifiFailure();
|
this._recoverAfterWifiFailure();
|
||||||
return result;
|
return result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('provision', `配网过程异常: ${e.message}`);
|
log.error('provision', `配网过程异常: ${e.message}`);
|
||||||
this._recoverAfterWifiFailure();
|
this._recoverAfterWifiFailure();
|
||||||
return { success: false, error: e.message };
|
return { success: false, error: e.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** WiFi 连接失败后:有线可用则保持 wired;否则开 AP 兜底。 */
|
/** WiFi 连接失败后:有线可用则保持 wired;否则开 AP 兜底。 */
|
||||||
_recoverAfterWifiFailure() {
|
_recoverAfterWifiFailure() {
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
led.off();
|
led.off();
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._safeReenterAP();
|
this._safeReenterAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新开 AP;失败时勿把 _state 永久卡在 connecting */
|
/** 重新开 AP;失败时勿把 _state 永久卡在 connecting */
|
||||||
_safeReenterAP() {
|
_safeReenterAP() {
|
||||||
try {
|
try {
|
||||||
this._enterAP();
|
this._enterAP();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error('provision', `重新启动 AP 失败: ${e.message}`);
|
log.error('provision', `重新启动 AP 失败: ${e.message}`);
|
||||||
this._state = 'idle';
|
this._state = 'idle';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── WiFi 状态监控 ─────────────────────────────────────────────────────────
|
// ── WiFi 状态监控 ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_startMonitor() {
|
_startMonitor() {
|
||||||
this._monitorTimer = setInterval(() => {
|
this._monitorTimer = setInterval(() => {
|
||||||
if (this._monitorBusy) return;
|
if (this._monitorBusy) return;
|
||||||
this._monitorBusy = true;
|
this._monitorBusy = true;
|
||||||
this._monitorTick()
|
this._monitorTick()
|
||||||
.catch((e) => log.error('provision', `WiFi 状态监控异常: ${e.message}`))
|
.catch((e) => log.error('provision', `WiFi 状态监控异常: ${e.message}`))
|
||||||
.finally(() => { this._monitorBusy = false; });
|
.finally(() => { this._monitorBusy = false; });
|
||||||
}, MONITOR_INTERVAL_MS);
|
}, MONITOR_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _monitorTick() {
|
async _monitorTick() {
|
||||||
if (this._state === 'connecting') return;
|
if (this._state === 'connecting') return;
|
||||||
|
|
||||||
const wifiUp = isWifiStaConnected();
|
const wifiUp = isWifiStaConnected();
|
||||||
|
|
||||||
if (wifiUp && this._state !== 'sta') {
|
if (wifiUp && this._state !== 'sta') {
|
||||||
if (this._state === 'ap') {
|
if (this._state === 'ap') {
|
||||||
log.info('provision', 'WiFi 已外部连接,关闭 AP');
|
log.info('provision', 'WiFi 已外部连接,关闭 AP');
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
}
|
}
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._state === 'sta' && !wifiUp) {
|
if (this._state === 'sta' && !wifiUp) {
|
||||||
log.warn('provision', 'WiFi 连接已断开,尝试恢复网络');
|
log.warn('provision', 'WiFi 连接已断开,尝试恢复网络');
|
||||||
await this._recoverNetworkWithoutWifi();
|
await this._recoverNetworkWithoutWifi();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._state === 'wired') {
|
if (this._state === 'wired') {
|
||||||
if (!hasInternet()) {
|
if (!hasInternet()) {
|
||||||
log.warn('provision', '有线网络不可用,尝试恢复 WiFi');
|
log.warn('provision', '有线网络不可用,尝试恢复 WiFi');
|
||||||
await this._recoverNetworkWithoutWifi();
|
await this._recoverNetworkWithoutWifi();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
led.off();
|
led.off();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._state === 'ap') {
|
if (this._state === 'ap') {
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
log.info('provision', '检测到有线网络可用,关闭 AP');
|
log.info('provision', '检测到有线网络可用,关闭 AP');
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSavedWifiConnection() && this._shouldRetrySavedWifiFromAP()) {
|
if (hasSavedWifiConnection() && this._shouldRetrySavedWifiFromAP()) {
|
||||||
await this._retrySavedWifiFromAP();
|
await this._retrySavedWifiFromAP();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
led.off();
|
led.off();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recoverNetworkWithoutWifi() {
|
async _recoverNetworkWithoutWifi() {
|
||||||
this._state = 'connecting';
|
this._state = 'connecting';
|
||||||
led.blink();
|
led.blink();
|
||||||
|
|
||||||
if (hasSavedWifiConnection()) {
|
if (hasSavedWifiConnection()) {
|
||||||
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
||||||
if (connected) {
|
if (connected) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
led.off();
|
led.off();
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._safeReenterAP();
|
this._safeReenterAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
_shouldRetrySavedWifiFromAP() {
|
_shouldRetrySavedWifiFromAP() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (this._apStartedAt && now - this._apStartedAt < AP_MIN_UP_BEFORE_RETRY_MS) return false;
|
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;
|
if (this._lastApSavedWifiRetryAt && now - this._lastApSavedWifiRetryAt < AP_SAVED_WIFI_RETRY_INTERVAL_MS) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _retrySavedWifiFromAP() {
|
async _retrySavedWifiFromAP() {
|
||||||
this._lastApSavedWifiRetryAt = Date.now();
|
this._lastApSavedWifiRetryAt = Date.now();
|
||||||
log.info('provision', 'AP 模式下定期尝试已保存 WiFi');
|
log.info('provision', 'AP 模式下定期尝试已保存 WiFi');
|
||||||
this._state = 'connecting';
|
this._state = 'connecting';
|
||||||
led.blink();
|
led.blink();
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
await new Promise((r) => setTimeout(r, 3500));
|
await new Promise((r) => setTimeout(r, 3500));
|
||||||
|
|
||||||
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS);
|
||||||
if (connected) {
|
if (connected) {
|
||||||
this._state = 'sta';
|
this._state = 'sta';
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasInternet()) {
|
if (hasInternet()) {
|
||||||
this._state = 'wired';
|
this._state = 'wired';
|
||||||
led.off();
|
led.off();
|
||||||
this._emitNetworkReady();
|
this._emitNetworkReady();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.warn('provision', 'AP 模式下重试已保存 WiFi 失败,恢复 AP');
|
log.warn('provision', 'AP 模式下重试已保存 WiFi 失败,恢复 AP');
|
||||||
this._safeReenterAP();
|
this._safeReenterAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopMonitor() {
|
_stopMonitor() {
|
||||||
if (this._monitorTimer) {
|
if (this._monitorTimer) {
|
||||||
clearInterval(this._monitorTimer);
|
clearInterval(this._monitorTimer);
|
||||||
this._monitorTimer = null;
|
this._monitorTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 清理 ──────────────────────────────────────────────────────────────────
|
// ── 清理 ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_stopAPServices() {
|
_stopAPServices() {
|
||||||
if (this._server) {
|
if (this._server) {
|
||||||
this._server.stop();
|
this._server.stop();
|
||||||
this._server = null;
|
this._server = null;
|
||||||
}
|
}
|
||||||
if (this._dns) {
|
if (this._dns) {
|
||||||
this._dns.stop();
|
this._dns.stop();
|
||||||
this._dns = null;
|
this._dns = null;
|
||||||
}
|
}
|
||||||
stopAP();
|
stopAP();
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopAll() {
|
_stopAll() {
|
||||||
this._stopAPServices();
|
this._stopAPServices();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { ProvisionManager };
|
module.exports = { ProvisionManager };
|
||||||
|
|||||||
Binary file not shown.
@@ -142,7 +142,6 @@ static void apply_show_text_async(void *arg)
|
|||||||
{
|
{
|
||||||
const char *text = (const char *)arg;
|
const char *text = (const char *)arg;
|
||||||
show_custom_text = true;
|
show_custom_text = true;
|
||||||
custom_text_blink = false;
|
|
||||||
custom_text_visible = true;
|
custom_text_visible = true;
|
||||||
lv_label_set_text(custom_text_label, text ? text : "");
|
lv_label_set_text(custom_text_label, text ? text : "");
|
||||||
lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25);
|
lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25);
|
||||||
@@ -201,14 +200,28 @@ static void *fifo_thread(void *arg)
|
|||||||
line[--len] = '\0';
|
line[--len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strncmp(line, "show_text:", 10) == 0) {
|
if (strcmp(line, "show_ap") == 0) {
|
||||||
|
lv_async_call(apply_show_ap_async, NULL);
|
||||||
|
} else if (strcmp(line, "show_conn") == 0) {
|
||||||
|
strncpy(custom_text_buf, "Conn", sizeof(custom_text_buf) - 1);
|
||||||
|
custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
|
||||||
|
custom_text_blink = true;
|
||||||
|
lv_async_call(apply_show_text_async, custom_text_buf);
|
||||||
|
} else if (strcmp(line, "show_err0") == 0) {
|
||||||
|
strncpy(custom_text_buf, "Err0", sizeof(custom_text_buf) - 1);
|
||||||
|
custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
|
||||||
|
custom_text_blink = false;
|
||||||
|
lv_async_call(apply_show_text_async, custom_text_buf);
|
||||||
|
} else if (strncmp(line, "show_pin:", 9) == 0) {
|
||||||
|
strncpy(custom_text_buf, line + 9, sizeof(custom_text_buf) - 1);
|
||||||
|
custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
|
||||||
|
custom_text_blink = true;
|
||||||
|
lv_async_call(apply_show_text_async, custom_text_buf);
|
||||||
|
} else if (strncmp(line, "show_text:", 10) == 0) {
|
||||||
strncpy(custom_text_buf, line + 10, sizeof(custom_text_buf) - 1);
|
strncpy(custom_text_buf, line + 10, sizeof(custom_text_buf) - 1);
|
||||||
custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
|
custom_text_buf[sizeof(custom_text_buf) - 1] = '\0';
|
||||||
if (strcmp(custom_text_buf, "AP") == 0) {
|
custom_text_blink = false;
|
||||||
lv_async_call(apply_show_ap_async, NULL);
|
lv_async_call(apply_show_text_async, custom_text_buf);
|
||||||
} else {
|
|
||||||
lv_async_call(apply_show_text_async, custom_text_buf);
|
|
||||||
}
|
|
||||||
} else if (strcmp(line, "show_time") == 0) {
|
} else if (strcmp(line, "show_time") == 0) {
|
||||||
lv_async_call(apply_show_time_async, NULL);
|
lv_async_call(apply_show_time_async, NULL);
|
||||||
}
|
}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "clawd",
|
"name": "clawd",
|
||||||
"version": "1.4.4",
|
"version": "1.4.5",
|
||||||
"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.4",
|
"version": "1.4.5",
|
||||||
"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