| .. | .. |
|---|
| 515 | 515 | switch (state) { |
|---|
| 516 | 516 | case AppLifecycleState.resumed: |
|---|
| 517 | 517 | if (_intentionalClose) break; |
|---|
| 518 | | - // iOS kills the TCP socket during suspend. The MQTT client may still |
|---|
| 519 | | - // think it's connected until the next ping timeout (30s+). Force a |
|---|
| 520 | | - // fresh connection to avoid the "dead but looks alive" state. |
|---|
| 521 | | - final client = _client; |
|---|
| 522 | | - if (client != null && client.connectionStatus?.state == MqttConnectionState.connected) { |
|---|
| 523 | | - // Verify the connection is actually alive by checking disconnect state |
|---|
| 524 | | - // If auto-reconnect already fired, this is a no-op |
|---|
| 525 | | - _mqttLog('MQTT: app resumed, verifying connection...'); |
|---|
| 526 | | - // Force disconnect + reconnect to get a clean state |
|---|
| 527 | | - client.autoReconnect = false; |
|---|
| 528 | | - client.disconnect(); |
|---|
| 529 | | - _client = null; |
|---|
| 530 | | - _setStatus(ConnectionStatus.reconnecting); |
|---|
| 531 | | - Future.delayed(const Duration(milliseconds: 500), () => connect()); |
|---|
| 532 | | - } else if (_status != ConnectionStatus.connected) { |
|---|
| 518 | + _mqttLog('MQTT: app resumed, status=$_status'); |
|---|
| 519 | + if (_status != ConnectionStatus.connected) { |
|---|
| 520 | + // Already knows it's disconnected — just reconnect |
|---|
| 533 | 521 | connect(); |
|---|
| 522 | + } else { |
|---|
| 523 | + // Thinks it's connected — verify by sending a ping command. |
|---|
| 524 | + // If the connection is dead, the publish will fail or we won't |
|---|
| 525 | + // get a pong back. Set a watchdog timer. |
|---|
| 526 | + _mqttLog('MQTT: sending health check...'); |
|---|
| 527 | + _publish('pailot/control/in', { |
|---|
| 528 | + 'type': 'command', |
|---|
| 529 | + 'command': 'ping', |
|---|
| 530 | + 'msgId': DateTime.now().millisecondsSinceEpoch.toString(), |
|---|
| 531 | + 'ts': DateTime.now().millisecondsSinceEpoch, |
|---|
| 532 | + }, MqttQos.atLeastOnce); |
|---|
| 533 | + // If no pong within 3s, force reconnect |
|---|
| 534 | + Future.delayed(const Duration(seconds: 3), () { |
|---|
| 535 | + if (_status == ConnectionStatus.connected) { |
|---|
| 536 | + // Check if client is still actually connected |
|---|
| 537 | + final client = _client; |
|---|
| 538 | + if (client == null || client.connectionStatus?.state != MqttConnectionState.connected) { |
|---|
| 539 | + _mqttLog('MQTT: health check failed, reconnecting...'); |
|---|
| 540 | + _client = null; |
|---|
| 541 | + _setStatus(ConnectionStatus.reconnecting); |
|---|
| 542 | + connect(); |
|---|
| 543 | + } |
|---|
| 544 | + } |
|---|
| 545 | + }); |
|---|
| 534 | 546 | } |
|---|
| 535 | 547 | case AppLifecycleState.paused: |
|---|
| 536 | 548 | break; |
|---|