fix: 连接状态判定修复 — 收到首个数据包后才标记已连接,CONNECTING 5秒超时回退
- ProtocolClient: 仅收到首个有效数据包后设置 CONNECTED,不再 socket 创建后立即标记 - ProtocolClient: timeoutCheckLoop 在 connect() 中立即启动,覆盖 CONNECTING(5s) 和 CONNECTED(3s) - RobotConnection: 允许 CONNECTING → TIMEOUT 合法转换 - MainScreen: CONNECTING 时点击连接按钮仅断开回到 DISCONNECTED,不再自动重连 Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -161,7 +161,7 @@ gradlew installDebug
|
||||
- **状态管理**: 使用 `StateFlow` 暴露连接状态、机器人状态
|
||||
- **生命周期**: 网络连接生命周期绑定到 Activity/Service,前台时活跃
|
||||
- **MsgId**: 从 0 递增 u16,循环回绕
|
||||
- **断线判定**: 3 秒无任何 UDP 包 → 标记离线
|
||||
- **断线判定**: 收到首个有效数据包前为 `CONNECTING`(5 秒无数据 → `TIMEOUT`);`CONNECTED` 后 3 秒无任何 UDP 包 → `TIMEOUT`
|
||||
|
||||
---
|
||||
|
||||
@@ -208,6 +208,9 @@ gradlew installDebug
|
||||
- [x] UI 布局:底部控制区移除"更多"展开区域,仅保留左右摇杆
|
||||
- [x] UI 布局:所有功能按钮统一使用平行四边形形状(左侧按钮斜边左下→右上,右侧按钮斜边左上→右下)
|
||||
- [x] UI 布局:所有控制按钮在未连接时灰色不可点击
|
||||
- [x] 连接状态准确性修复:`ProtocolClient` 收到首个有效数据包后才标记 `CONNECTED`,不再 socket 创建后立即标记;`CONNECTING` 状态 5 秒无响应自动超时回退 `TIMEOUT`
|
||||
- [x] 连接按钮交互优化:`CONNECTING` 状态下点击连接按钮仅断开回到 `DISCONNECTED`,不再自动重连,由用户手动再次点击发起重试
|
||||
- [x] 状态机补充:`RobotConnection` 允许 `CONNECTING → TIMEOUT` 合法转换
|
||||
|
||||
### 待办
|
||||
|
||||
|
||||
@@ -89,9 +89,10 @@ class ProtocolClient(
|
||||
/**
|
||||
* 建立连接:
|
||||
* 1. 创建 UDP socket
|
||||
* 2. 启动接收协程
|
||||
* 2. 启动接收/发送协程
|
||||
* 3. 发送订阅请求
|
||||
* 4. 启动心跳和断线检测
|
||||
* 4. 启动断线检测(CONNECTING 5s / CONNECTED 3s 无数据 → TIMEOUT)
|
||||
* 5. 首次收到有效数据包后标记已连接,并启动心跳 1Hz(在接收循环中处理)
|
||||
*/
|
||||
fun connect() {
|
||||
if (_state.value != State.DISCONNECTED && _state.value != State.ERROR) return
|
||||
@@ -131,14 +132,7 @@ class ProtocolClient(
|
||||
// 发送订阅请求(触发服务端 UDP 推送)
|
||||
sendSubscriptionRequests()
|
||||
|
||||
_state.value = State.CONNECTED
|
||||
|
||||
// 启动心跳
|
||||
heartbeatJob = scope.launch(Dispatchers.IO) {
|
||||
heartbeatLoop(sock, address)
|
||||
}
|
||||
|
||||
// 启动断线检测
|
||||
// 启动断线检测(立即开始,覆盖 CONNECTING 和 CONNECTED 状态)
|
||||
timeoutJob = scope.launch {
|
||||
timeoutCheckLoop()
|
||||
}
|
||||
@@ -232,6 +226,14 @@ class ProtocolClient(
|
||||
if (report != null) {
|
||||
_statusReport.tryEmit(report)
|
||||
}
|
||||
|
||||
// 首次收到有效数据包:标记已连接,启动心跳
|
||||
if (_state.value == State.CONNECTING) {
|
||||
_state.value = State.CONNECTED
|
||||
heartbeatJob = scope.launch(Dispatchers.IO) {
|
||||
heartbeatLoop(sock, remoteAddress!!)
|
||||
}
|
||||
}
|
||||
} catch (_: SocketTimeoutException) {
|
||||
// 超时正常,继续循环
|
||||
continue
|
||||
@@ -258,13 +260,23 @@ class ProtocolClient(
|
||||
}
|
||||
}
|
||||
|
||||
/** 断线检测:3 秒无数据 → 标记 TIMEOUT */
|
||||
/** 断线检测:CONNECTED 状态 3 秒无数据 → TIMEOUT;CONNECTING 状态 5 秒无数据 → TIMEOUT */
|
||||
private suspend fun CoroutineScope.timeoutCheckLoop() {
|
||||
while (isActive && _state.value != State.DISCONNECTED) {
|
||||
val elapsed = System.currentTimeMillis() - lastPacketTimeMs
|
||||
if (elapsed > 3000 && _state.value == State.CONNECTED) {
|
||||
when (_state.value) {
|
||||
State.CONNECTED -> {
|
||||
if (elapsed > 3000) {
|
||||
_state.value = State.TIMEOUT
|
||||
}
|
||||
}
|
||||
State.CONNECTING -> {
|
||||
if (elapsed > 5000) {
|
||||
_state.value = State.TIMEOUT
|
||||
}
|
||||
}
|
||||
else -> { /* DISCONNECTED/TIMEOUT/ERROR: 无需超时处理 */ }
|
||||
}
|
||||
delay(500)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class RobotConnection {
|
||||
ProtocolClient.State.CONNECTING ->
|
||||
to == ProtocolClient.State.CONNECTED ||
|
||||
to == ProtocolClient.State.ERROR ||
|
||||
to == ProtocolClient.State.TIMEOUT ||
|
||||
to == ProtocolClient.State.DISCONNECTED
|
||||
ProtocolClient.State.CONNECTED ->
|
||||
to == ProtocolClient.State.TIMEOUT ||
|
||||
|
||||
@@ -172,7 +172,9 @@ fun MainScreen(
|
||||
onStatusClick = { statusPanelVisible = true },
|
||||
onSettingsClick = onSettingsClick,
|
||||
onConnectToggle = {
|
||||
if (connectionState == ProtocolClient.State.CONNECTED) {
|
||||
if (connectionState == ProtocolClient.State.CONNECTED ||
|
||||
connectionState == ProtocolClient.State.CONNECTING
|
||||
) {
|
||||
viewModel.disconnect()
|
||||
} else {
|
||||
viewModel.connect()
|
||||
|
||||
Reference in New Issue
Block a user