diff --git a/lib/led/rk3588-lvgl.js b/lib/led/rk3588-lvgl.js index 28cb85e..00115fe 100644 --- a/lib/led/rk3588-lvgl.js +++ b/lib/led/rk3588-lvgl.js @@ -36,19 +36,19 @@ class StatusLed { class Display { showAP() { - if (writeLvglCommand('show_text:AP')) { - log.info('display', '显示屏 → AP'); + if (writeLvglCommand('show_ap')) { + log.info('display', '显示屏 → AP(闪烁)'); } } showConn() { - if (writeLvglCommand('show_text:Conn')) { - log.info('display', '显示屏 → Conn'); + if (writeLvglCommand('show_conn')) { + log.info('display', '显示屏 → Conn(闪烁)'); } } showErr0() { - if (writeLvglCommand('show_text:Err0')) { + if (writeLvglCommand('show_err0')) { log.info('display', '显示屏 → Err0'); } } @@ -61,8 +61,8 @@ class Display { showPin(pin) { const s = String(pin || '').padStart(4, '0').slice(-4); - if (writeLvglCommand(`show_text:${s}`)) { - log.info('display', `显示屏 → PIN: ${s}`); + if (writeLvglCommand(`show_pin:${s}`)) { + log.info('display', `显示屏 → PIN: ${s}(闪烁)`); } } } diff --git a/lib/provisioning.js b/lib/provisioning.js index f94259f..ba6a342 100644 --- a/lib/provisioning.js +++ b/lib/provisioning.js @@ -1,360 +1,360 @@ -'use strict'; - -const EventEmitter = require('events'); -const log = require('./logger'); -const { hasInternet, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network'); -const { DnsHijack } = require('./dns-hijack'); -const { CaptiveServer } = require('./captive-server'); -const led = require('./led'); - -const MONITOR_INTERVAL_MS = 15_000; -const WIFI_RECONNECT_MAX_ROUNDS = 3; -const WIFI_RECONNECT_ROUND_DELAY_MS = 5_000; -const AP_SAVED_WIFI_RETRY_INTERVAL_MS = 180_000; -const AP_MIN_UP_BEFORE_RETRY_MS = 60_000; - -/** - * AP 常驻配网管理器。 - * - * 规则: - * - 启动时:WiFi STA 优先;有已保存 WiFi 时主动让 NM 重连,最多 3 轮 - * - 有线网络可用时:通知网络就绪,但不自动开启 AP - * - 自动开 AP 的唯一兜底:无有线/无 WiFi,且无 saved WiFi 或 saved WiFi 3 轮失败 - * - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则按网络状态决定是否重新开 AP - * - AP 状态下:若仍无有线网络,低频释放 wlan0 并尝试 saved WiFi - */ -class ProvisionManager extends EventEmitter { - constructor(clawId) { - super(); - this._clawId = clawId || 'Setup'; - this._state = 'idle'; // 'idle' | 'ap' | 'connecting' | 'sta' | 'wired' - this._dns = null; - this._server = null; - this._monitorTimer = null; - this._monitorBusy = false; - this._apStartedAt = 0; - this._lastApSavedWifiRetryAt = 0; - } - - /** 是否正处于 AP 模式(WiFi 热点广播中) */ - isApMode() { return this._state === 'ap'; } - - async start() { - led.off(); // WiFi 灯初始状态:熄灭 - - // WiFi STA 已连接 → 直接进入 STA 模式 - if (isWifiStaConnected()) { - this._state = 'sta'; - log.info('provision', 'WiFi STA 已连接,AP 不启动'); - this._emitNetworkReady(); - this._startMonitor(); - return; - } - - // 网络已就绪时先启动 WS;hasInternet() 可能来自 WiFi,也可能来自有线,不能直接当作 wired。 - if (hasInternet()) { - if (isWifiStaConnected()) { - this._state = 'sta'; - log.info('provision', 'WiFi STA 已连接,AP 不启动'); - this._emitNetworkReady(); - } else { - this._state = 'wired'; - log.info('provision', '有线网络就绪,启动 WS;不自动开启 AP'); - led.off(); - this._emitNetworkReady(); - } - this._startMonitor(); - return; - } - - // 无有线可用时,有 saved WiFi 才主动让 NetworkManager 重连;不要只被动等待 NM autoconnect。 - if (hasSavedWifiConnection()) { - log.info('provision', `发现已保存的 WiFi 配置,主动重连(最多 ${WIFI_RECONNECT_MAX_ROUNDS} 轮)...`); - this._state = 'connecting'; - led.blink(); - const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); - if (connected) { - this._state = 'sta'; - log.info('provision', '已保存 WiFi 重连成功,AP 不启动'); - this._emitNetworkReady(); - this._startMonitor(); - return; - } - log.warn('provision', '已保存 WiFi 重连失败'); - } - - // 无有线、无 WiFi;且无 saved WiFi 或 saved WiFi 3 轮失败 → 开 AP 兜底配网。 - this._enterAP(); - this._startMonitor(); - } - - _emitNetworkReady() { - if (hasInternet()) { - // WiFi 灯只在 STA 模式下亮(有线有网而 WiFi 在 AP 模式时不亮) - if (this._state === 'sta') led.on(); - this.emit('network-ready'); - } else { - log.warn('provision', 'hasInternet() 返回 false,LED 保持熄灭'); - } - } - - async _trySavedWifiReconnectRounds(rounds = WIFI_RECONNECT_MAX_ROUNDS) { - for (let i = 1; i <= rounds; i++) { - if (isWifiStaConnected()) return true; - log.info('provision', `尝试已保存 WiFi 重连:第 ${i}/${rounds} 轮`); - const result = await connectSavedWifiConnections(); - if (result.success || isWifiStaConnected()) return true; - if (i < rounds) { - await new Promise((r) => setTimeout(r, WIFI_RECONNECT_ROUND_DELAY_MS)); - } - } - return false; - } - - stop() { - this._stopMonitor(); - this._stopAll(); - this._state = 'idle'; - led.destroy(); // WiFi 灯:停止时关灯、释放闪烁定时器 - } - - // ── 进入 AP 模式 ───────────────────────────────────────────────────────── - - _enterAP() { - if (this._state === 'ap') return; - - led.off(); // AP 模式:WiFi 未连接,WiFi 灯熄灭 - if (!hasInternet()) led.display.showAP(); // 无网时立即显示 AP,有线时等 WS 连接后再定 - - try { - // 若上次进程退出前留下 clawd-hotspot,必须先释放 wlan0;否则会在 AP 模式下扫描,列表可能只剩 2.4G/自身热点。 - stopAP(); - - // AP 模式下无法扫描 WiFi,必须在开 AP 之前扫描并缓存 - log.info('provision', '扫描周边 WiFi...'); - this._cachedWifiList = scanWifi(); - log.info('provision', `扫描到 ${this._cachedWifiList.length} 个网络: ${this._cachedWifiList.map(w => `${w.ssid}${w.band ? `(${w.band})` : ''}`).join(', ')}`); - - // 写 DNS 劫持配置(NM 启动热点时加载);接口名与热点一致,勿写死 wlan0 - this._dns = new DnsHijack(); - this._dns.start(getWifiIface(), AP_IP); - - const ap = startAP(this._clawId); - - this._server = new CaptiveServer({ - clawId: this._clawId, - cachedWifiList: this._cachedWifiList, - onConnect: (ssid, password) => this._handleWifiConnect(ssid, password), - }); - this._server.startListening(); - - this._state = 'ap'; - this._apStartedAt = Date.now(); - this._lastApSavedWifiRetryAt = 0; - log.info('provision', `AP 常驻模式已启动: ${ap.ssid}, 密码 12345678`); - log.info('provision', `配网地址: http://10.42.0.1`); - } catch (e) { - log.error('provision', `AP 启动失败: ${e.message}`); - if (this._state !== 'sta') this._state = 'idle'; - } - } - - // ── 用户提交 WiFi 凭证 ─────────────────────────────────────────────────── - - async _handleWifiConnect(ssid, password) { - if (this._state === 'connecting') return { success: false, error: '正在连接中,请稍候' }; - - this._state = 'connecting'; - log.info('provision', `用户请求连接 WiFi: ${ssid}`); - led.blink(); // 正在连接 → 闪烁 - - try { - this._stopAPServices(); - - // 关热点后射频/模式切换需要时间,立刻 connect 在部分板子上会失败 - await new Promise((r) => setTimeout(r, 3500)); - - const result = await connectWifi(ssid, password); - - if (result.success) { - this._state = 'sta'; - log.info('provision', `WiFi 已连接: ${ssid}`); - led.on(); // WiFi 灯:连接成功 → 常亮 - this.emit('network-ready'); - return result; - } - - log.warn('provision', `WiFi 连接失败: ${result.error},按当前网络状态恢复`); - this._recoverAfterWifiFailure(); - return result; - } catch (e) { - log.error('provision', `配网过程异常: ${e.message}`); - this._recoverAfterWifiFailure(); - return { success: false, error: e.message }; - } - } - - /** WiFi 连接失败后:有线可用则保持 wired;否则开 AP 兜底。 */ - _recoverAfterWifiFailure() { - if (hasInternet()) { - this._state = 'wired'; - led.off(); - this._emitNetworkReady(); - return; - } - this._safeReenterAP(); - } - - /** 重新开 AP;失败时勿把 _state 永久卡在 connecting */ - _safeReenterAP() { - try { - this._enterAP(); - } catch (e) { - log.error('provision', `重新启动 AP 失败: ${e.message}`); - this._state = 'idle'; - } - } - - // ── WiFi 状态监控 ───────────────────────────────────────────────────────── - - _startMonitor() { - this._monitorTimer = setInterval(() => { - if (this._monitorBusy) return; - this._monitorBusy = true; - this._monitorTick() - .catch((e) => log.error('provision', `WiFi 状态监控异常: ${e.message}`)) - .finally(() => { this._monitorBusy = false; }); - }, MONITOR_INTERVAL_MS); - } - - async _monitorTick() { - if (this._state === 'connecting') return; - - const wifiUp = isWifiStaConnected(); - - if (wifiUp && this._state !== 'sta') { - if (this._state === 'ap') { - log.info('provision', 'WiFi 已外部连接,关闭 AP'); - this._stopAPServices(); - } - this._state = 'sta'; - this._emitNetworkReady(); - } - - if (this._state === 'sta' && !wifiUp) { - log.warn('provision', 'WiFi 连接已断开,尝试恢复网络'); - await this._recoverNetworkWithoutWifi(); - return; - } - - if (this._state === 'wired') { - if (!hasInternet()) { - log.warn('provision', '有线网络不可用,尝试恢复 WiFi'); - await this._recoverNetworkWithoutWifi(); - return; - } - led.off(); - return; - } - - if (this._state === 'ap') { - if (hasInternet()) { - log.info('provision', '检测到有线网络可用,关闭 AP'); - this._stopAPServices(); - this._state = 'wired'; - this._emitNetworkReady(); - return; - } - - if (hasSavedWifiConnection() && this._shouldRetrySavedWifiFromAP()) { - await this._retrySavedWifiFromAP(); - return; - } - led.off(); - return; - } - } - - async _recoverNetworkWithoutWifi() { - this._state = 'connecting'; - led.blink(); - - if (hasSavedWifiConnection()) { - const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); - if (connected) { - this._state = 'sta'; - this._emitNetworkReady(); - return; - } - } - - if (hasInternet()) { - this._state = 'wired'; - led.off(); - this._emitNetworkReady(); - return; - } - - this._safeReenterAP(); - } - - _shouldRetrySavedWifiFromAP() { - const now = Date.now(); - 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; - return true; - } - - async _retrySavedWifiFromAP() { - this._lastApSavedWifiRetryAt = Date.now(); - log.info('provision', 'AP 模式下定期尝试已保存 WiFi'); - this._state = 'connecting'; - led.blink(); - this._stopAPServices(); - await new Promise((r) => setTimeout(r, 3500)); - - const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); - if (connected) { - this._state = 'sta'; - this._emitNetworkReady(); - return; - } - - if (hasInternet()) { - this._state = 'wired'; - led.off(); - this._emitNetworkReady(); - return; - } - - log.warn('provision', 'AP 模式下重试已保存 WiFi 失败,恢复 AP'); - this._safeReenterAP(); - } - - _stopMonitor() { - if (this._monitorTimer) { - clearInterval(this._monitorTimer); - this._monitorTimer = null; - } - } - - // ── 清理 ────────────────────────────────────────────────────────────────── - - _stopAPServices() { - if (this._server) { - this._server.stop(); - this._server = null; - } - if (this._dns) { - this._dns.stop(); - this._dns = null; - } - stopAP(); - } - - _stopAll() { - this._stopAPServices(); - } -} - -module.exports = { ProvisionManager }; +'use strict'; + +const EventEmitter = require('events'); +const log = require('./logger'); +const { hasInternet, hasSavedWifiConnection, connectSavedWifiConnections, isWifiStaConnected, scanWifi, startAP, stopAP, connectWifi, getWifiIface, AP_IP } = require('./network'); +const { DnsHijack } = require('./dns-hijack'); +const { CaptiveServer } = require('./captive-server'); +const led = require('./led'); + +const MONITOR_INTERVAL_MS = 15_000; +const WIFI_RECONNECT_MAX_ROUNDS = 3; +const WIFI_RECONNECT_ROUND_DELAY_MS = 5_000; +const AP_SAVED_WIFI_RETRY_INTERVAL_MS = 180_000; +const AP_MIN_UP_BEFORE_RETRY_MS = 60_000; + +/** + * AP 常驻配网管理器。 + * + * 规则: + * - 启动时:WiFi STA 优先;有已保存 WiFi 时主动让 NM 重连,最多 3 轮 + * - 有线网络可用时:通知网络就绪,但不自动开启 AP + * - 自动开 AP 的唯一兜底:无有线/无 WiFi,且无 saved WiFi 或 saved WiFi 3 轮失败 + * - 用户提交 WiFi 凭证 → 关 AP → 尝试连接 → 失败则按网络状态决定是否重新开 AP + * - AP 状态下:若仍无有线网络,低频释放 wlan0 并尝试 saved WiFi + */ +class ProvisionManager extends EventEmitter { + constructor(clawId) { + super(); + this._clawId = clawId || 'Setup'; + this._state = 'idle'; // 'idle' | 'ap' | 'connecting' | 'sta' | 'wired' + this._dns = null; + this._server = null; + this._monitorTimer = null; + this._monitorBusy = false; + this._apStartedAt = 0; + this._lastApSavedWifiRetryAt = 0; + } + + /** 是否正处于 AP 模式(WiFi 热点广播中) */ + isApMode() { return this._state === 'ap'; } + + async start() { + led.off(); // WiFi 灯初始状态:熄灭 + + // WiFi STA 已连接 → 直接进入 STA 模式 + if (isWifiStaConnected()) { + this._state = 'sta'; + log.info('provision', 'WiFi STA 已连接,AP 不启动'); + this._emitNetworkReady(); + this._startMonitor(); + return; + } + + // 网络已就绪时先启动 WS;hasInternet() 可能来自 WiFi,也可能来自有线,不能直接当作 wired。 + if (hasInternet()) { + if (isWifiStaConnected()) { + this._state = 'sta'; + log.info('provision', 'WiFi STA 已连接,AP 不启动'); + this._emitNetworkReady(); + } else { + this._state = 'wired'; + log.info('provision', '有线网络就绪,启动 WS;不自动开启 AP'); + led.off(); + this._emitNetworkReady(); + } + this._startMonitor(); + return; + } + + // 无有线可用时,有 saved WiFi 才主动让 NetworkManager 重连;不要只被动等待 NM autoconnect。 + if (hasSavedWifiConnection()) { + log.info('provision', `发现已保存的 WiFi 配置,主动重连(最多 ${WIFI_RECONNECT_MAX_ROUNDS} 轮)...`); + this._state = 'connecting'; + led.blink(); + const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); + if (connected) { + this._state = 'sta'; + log.info('provision', '已保存 WiFi 重连成功,AP 不启动'); + this._emitNetworkReady(); + this._startMonitor(); + return; + } + log.warn('provision', '已保存 WiFi 重连失败'); + } + + // 无有线、无 WiFi;且无 saved WiFi 或 saved WiFi 3 轮失败 → 开 AP 兜底配网。 + this._enterAP(); + this._startMonitor(); + } + + _emitNetworkReady() { + if (hasInternet()) { + // WiFi 灯只在 STA 模式下亮(有线有网而 WiFi 在 AP 模式时不亮) + if (this._state === 'sta') led.on(); + this.emit('network-ready'); + } else { + log.warn('provision', 'hasInternet() 返回 false,LED 保持熄灭'); + } + } + + async _trySavedWifiReconnectRounds(rounds = WIFI_RECONNECT_MAX_ROUNDS) { + for (let i = 1; i <= rounds; i++) { + if (isWifiStaConnected()) return true; + log.info('provision', `尝试已保存 WiFi 重连:第 ${i}/${rounds} 轮`); + const result = await connectSavedWifiConnections(); + if (result.success || isWifiStaConnected()) return true; + if (i < rounds) { + await new Promise((r) => setTimeout(r, WIFI_RECONNECT_ROUND_DELAY_MS)); + } + } + return false; + } + + stop() { + this._stopMonitor(); + this._stopAll(); + this._state = 'idle'; + led.destroy(); // WiFi 灯:停止时关灯、释放闪烁定时器 + } + + // ── 进入 AP 模式 ───────────────────────────────────────────────────────── + + _enterAP() { + if (this._state === 'ap') return; + + led.off(); // AP 模式:WiFi 未连接,WiFi 灯熄灭 + + try { + // 若上次进程退出前留下 clawd-hotspot,必须先释放 wlan0;否则会在 AP 模式下扫描,列表可能只剩 2.4G/自身热点。 + stopAP(); + + // AP 模式下无法扫描 WiFi,必须在开 AP 之前扫描并缓存 + log.info('provision', '扫描周边 WiFi...'); + this._cachedWifiList = scanWifi(); + log.info('provision', `扫描到 ${this._cachedWifiList.length} 个网络: ${this._cachedWifiList.map(w => `${w.ssid}${w.band ? `(${w.band})` : ''}`).join(', ')}`); + + // 写 DNS 劫持配置(NM 启动热点时加载);接口名与热点一致,勿写死 wlan0 + this._dns = new DnsHijack(); + this._dns.start(getWifiIface(), AP_IP); + + const ap = startAP(this._clawId); + + this._server = new CaptiveServer({ + clawId: this._clawId, + cachedWifiList: this._cachedWifiList, + onConnect: (ssid, password) => this._handleWifiConnect(ssid, password), + }); + this._server.startListening(); + + this._state = 'ap'; + led.display.showAP(); + this._apStartedAt = Date.now(); + this._lastApSavedWifiRetryAt = 0; + log.info('provision', `AP 常驻模式已启动: ${ap.ssid}, 密码 12345678`); + log.info('provision', `配网地址: http://10.42.0.1`); + } catch (e) { + log.error('provision', `AP 启动失败: ${e.message}`); + if (this._state !== 'sta') this._state = 'idle'; + } + } + + // ── 用户提交 WiFi 凭证 ─────────────────────────────────────────────────── + + async _handleWifiConnect(ssid, password) { + if (this._state === 'connecting') return { success: false, error: '正在连接中,请稍候' }; + + this._state = 'connecting'; + log.info('provision', `用户请求连接 WiFi: ${ssid}`); + led.blink(); // 正在连接 → 闪烁 + + try { + this._stopAPServices(); + + // 关热点后射频/模式切换需要时间,立刻 connect 在部分板子上会失败 + await new Promise((r) => setTimeout(r, 3500)); + + const result = await connectWifi(ssid, password); + + if (result.success) { + this._state = 'sta'; + log.info('provision', `WiFi 已连接: ${ssid}`); + led.on(); // WiFi 灯:连接成功 → 常亮 + this.emit('network-ready'); + return result; + } + + log.warn('provision', `WiFi 连接失败: ${result.error},按当前网络状态恢复`); + this._recoverAfterWifiFailure(); + return result; + } catch (e) { + log.error('provision', `配网过程异常: ${e.message}`); + this._recoverAfterWifiFailure(); + return { success: false, error: e.message }; + } + } + + /** WiFi 连接失败后:有线可用则保持 wired;否则开 AP 兜底。 */ + _recoverAfterWifiFailure() { + if (hasInternet()) { + this._state = 'wired'; + led.off(); + this._emitNetworkReady(); + return; + } + this._safeReenterAP(); + } + + /** 重新开 AP;失败时勿把 _state 永久卡在 connecting */ + _safeReenterAP() { + try { + this._enterAP(); + } catch (e) { + log.error('provision', `重新启动 AP 失败: ${e.message}`); + this._state = 'idle'; + } + } + + // ── WiFi 状态监控 ───────────────────────────────────────────────────────── + + _startMonitor() { + this._monitorTimer = setInterval(() => { + if (this._monitorBusy) return; + this._monitorBusy = true; + this._monitorTick() + .catch((e) => log.error('provision', `WiFi 状态监控异常: ${e.message}`)) + .finally(() => { this._monitorBusy = false; }); + }, MONITOR_INTERVAL_MS); + } + + async _monitorTick() { + if (this._state === 'connecting') return; + + const wifiUp = isWifiStaConnected(); + + if (wifiUp && this._state !== 'sta') { + if (this._state === 'ap') { + log.info('provision', 'WiFi 已外部连接,关闭 AP'); + this._stopAPServices(); + } + this._state = 'sta'; + this._emitNetworkReady(); + } + + if (this._state === 'sta' && !wifiUp) { + log.warn('provision', 'WiFi 连接已断开,尝试恢复网络'); + await this._recoverNetworkWithoutWifi(); + return; + } + + if (this._state === 'wired') { + if (!hasInternet()) { + log.warn('provision', '有线网络不可用,尝试恢复 WiFi'); + await this._recoverNetworkWithoutWifi(); + return; + } + led.off(); + return; + } + + if (this._state === 'ap') { + if (hasInternet()) { + log.info('provision', '检测到有线网络可用,关闭 AP'); + this._stopAPServices(); + this._state = 'wired'; + this._emitNetworkReady(); + return; + } + + if (hasSavedWifiConnection() && this._shouldRetrySavedWifiFromAP()) { + await this._retrySavedWifiFromAP(); + return; + } + led.off(); + return; + } + } + + async _recoverNetworkWithoutWifi() { + this._state = 'connecting'; + led.blink(); + + if (hasSavedWifiConnection()) { + const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); + if (connected) { + this._state = 'sta'; + this._emitNetworkReady(); + return; + } + } + + if (hasInternet()) { + this._state = 'wired'; + led.off(); + this._emitNetworkReady(); + return; + } + + this._safeReenterAP(); + } + + _shouldRetrySavedWifiFromAP() { + const now = Date.now(); + 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; + return true; + } + + async _retrySavedWifiFromAP() { + this._lastApSavedWifiRetryAt = Date.now(); + log.info('provision', 'AP 模式下定期尝试已保存 WiFi'); + this._state = 'connecting'; + led.blink(); + this._stopAPServices(); + await new Promise((r) => setTimeout(r, 3500)); + + const connected = await this._trySavedWifiReconnectRounds(WIFI_RECONNECT_MAX_ROUNDS); + if (connected) { + this._state = 'sta'; + this._emitNetworkReady(); + return; + } + + if (hasInternet()) { + this._state = 'wired'; + led.off(); + this._emitNetworkReady(); + return; + } + + log.warn('provision', 'AP 模式下重试已保存 WiFi 失败,恢复 AP'); + this._safeReenterAP(); + } + + _stopMonitor() { + if (this._monitorTimer) { + clearInterval(this._monitorTimer); + this._monitorTimer = null; + } + } + + // ── 清理 ────────────────────────────────────────────────────────────────── + + _stopAPServices() { + if (this._server) { + this._server.stop(); + this._server = null; + } + if (this._dns) { + this._dns.stop(); + this._dns = null; + } + stopAP(); + } + + _stopAll() { + this._stopAPServices(); + } +} + +module.exports = { ProvisionManager }; diff --git a/lib/resource/3588s/demo b/lib/resource/3588s/demo index 9281944..a8d0dbc 100755 Binary files a/lib/resource/3588s/demo and b/lib/resource/3588s/demo differ diff --git a/lib/resource/3588s/src/main.c b/lib/resource/3588s/src/main.c index 27e80b6..e595439 100755 --- a/lib/resource/3588s/src/main.c +++ b/lib/resource/3588s/src/main.c @@ -142,7 +142,6 @@ static void apply_show_text_async(void *arg) { const char *text = (const char *)arg; show_custom_text = true; - custom_text_blink = false; custom_text_visible = true; lv_label_set_text(custom_text_label, text ? text : ""); lv_obj_align(custom_text_label, LV_ALIGN_CENTER, 0, -25); @@ -201,14 +200,28 @@ static void *fifo_thread(void *arg) 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); custom_text_buf[sizeof(custom_text_buf) - 1] = '\0'; - if (strcmp(custom_text_buf, "AP") == 0) { - lv_async_call(apply_show_ap_async, NULL); - } else { - lv_async_call(apply_show_text_async, custom_text_buf); - } + custom_text_blink = false; + lv_async_call(apply_show_text_async, custom_text_buf); } else if (strcmp(line, "show_time") == 0) { lv_async_call(apply_show_time_async, NULL); } diff --git a/package-lock.json b/package-lock.json index 8df6426..5a7e390 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "clawd", - "version": "1.4.4", + "version": "1.4.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "clawd", - "version": "1.4.4", + "version": "1.4.5", "license": "MIT", "dependencies": { "ssh2": "^1.17.0", diff --git a/package.json b/package.json index d2d49ed..fcffe15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clawd", - "version": "1.4.4", + "version": "1.4.5", "description": "Claw Box daemon - connects local Linux box to claw.cutos.ai via WebSocket", "main": "lib/client.js", "bin": {