fix: 点击连接按钮后立即启动心跳线程,不再等待数据包接收

ProtocolClient.connect() 现在立即启动心跳循环(1Hz),而不是
等到 receiveLoop 首次收到数据包后才启动。心跳在 CONNECTING 和
CONNECTED 状态下均正常运行,确保连接建立过程中机器人能持续收到
心跳包。

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-07-18 14:58:19 +08:00
parent fbc3f1529f
commit d4db91235b
2 changed files with 13 additions and 7 deletions
+1
View File
@@ -211,6 +211,7 @@ gradlew installDebug
- [x] 连接状态准确性修复:`ProtocolClient` 收到首个有效数据包后才标记 `CONNECTED`,不再 socket 创建后立即标记;`CONNECTING` 状态 5 秒无响应自动超时回退 `TIMEOUT` - [x] 连接状态准确性修复:`ProtocolClient` 收到首个有效数据包后才标记 `CONNECTED`,不再 socket 创建后立即标记;`CONNECTING` 状态 5 秒无响应自动超时回退 `TIMEOUT`
- [x] 连接按钮交互优化:`CONNECTING` 状态下点击连接按钮仅断开回到 `DISCONNECTED`,不再自动重连,由用户手动再次点击发起重试 - [x] 连接按钮交互优化:`CONNECTING` 状态下点击连接按钮仅断开回到 `DISCONNECTED`,不再自动重连,由用户手动再次点击发起重试
- [x] 状态机补充:`RobotConnection` 允许 `CONNECTING → TIMEOUT` 合法转换 - [x] 状态机补充:`RobotConnection` 允许 `CONNECTING → TIMEOUT` 合法转换
- [x] 心跳启动时机修复:`ProtocolClient.connect()` 立即启动心跳线程,不再等待首次收到数据包后才启动;心跳在 `CONNECTING``CONNECTED` 状态下均正常运行
### 待办 ### 待办
@@ -92,7 +92,8 @@ class ProtocolClient(
* 2. 启动接收/发送协程 * 2. 启动接收/发送协程
* 3. 发送订阅请求 * 3. 发送订阅请求
* 4. 启动断线检测(CONNECTING 5s / CONNECTED 3s 无数据 → TIMEOUT * 4. 启动断线检测(CONNECTING 5s / CONNECTED 3s 无数据 → TIMEOUT
* 5. 首次收到有效数据包后标记已连接,并启动心跳 1Hz(在接收循环中处理 * 5. 启动心跳 1Hz(立即发送,覆盖 CONNECTING 和 CONNECTED 状态
* 6. 首次收到有效数据包后标记已连接
*/ */
fun connect() { fun connect() {
if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return
@@ -136,6 +137,11 @@ class ProtocolClient(
timeoutJob = scope.launch { timeoutJob = scope.launch {
timeoutCheckLoop() timeoutCheckLoop()
} }
// 启动心跳(立即开始,覆盖 CONNECTING 和 CONNECTED 状态)
heartbeatJob = scope.launch(Dispatchers.IO) {
heartbeatLoop(sock, address)
}
} catch (e: Exception) { } catch (e: Exception) {
_state.value = State.ERROR _state.value = State.ERROR
closeSocket() closeSocket()
@@ -227,12 +233,9 @@ class ProtocolClient(
_statusReport.tryEmit(report) _statusReport.tryEmit(report)
} }
// 首次收到有效数据包:标记已连接,启动心跳 // 首次收到有效数据包:标记已连接
if (_state.value == State.CONNECTING) { if (_state.value == State.CONNECTING) {
_state.value = State.CONNECTED _state.value = State.CONNECTED
heartbeatJob = scope.launch(Dispatchers.IO) {
heartbeatLoop(sock, remoteAddress!!)
}
} }
} catch (_: SocketTimeoutException) { } catch (_: SocketTimeoutException) {
// 超时正常,继续循环 // 超时正常,继续循环
@@ -246,9 +249,11 @@ class ProtocolClient(
} }
} }
/** 心跳循环(1 Hz */ /** 心跳循环(1 Hz—— 在 CONNECTING 和 CONNECTED 状态下运行 */
private suspend fun CoroutineScope.heartbeatLoop(sock: DatagramSocket, address: InetAddress) { private suspend fun CoroutineScope.heartbeatLoop(sock: DatagramSocket, address: InetAddress) {
while (isActive && _state.value == State.CONNECTED) { while (isActive &&
(_state.value == State.CONNECTING || _state.value == State.CONNECTED)
) {
try { try {
val json = ControlCommands.heartbeat() val json = ControlCommands.heartbeat()
val packet = encoder.encode(json) val packet = encoder.encode(json)