Skip to content

Commit 839c535

Browse files
committed
disconnect on reconnect() if connected
fixes adafruit#243
1 parent 1778c7c commit 839c535

File tree

2 files changed

+154
-2
lines changed

2 files changed

+154
-2
lines changed

adafruit_minimqtt/adafruit_minimqtt.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,11 +939,18 @@ def reconnect(self, resub_topics: bool = True) -> int:
939939
"""
940940

941941
self.logger.debug("Attempting to reconnect with MQTT broker")
942+
subscribed_topics = []
943+
if self.is_connected():
944+
# disconnect() will reset subscribed topics so stash them now.
945+
if resub_topics:
946+
subscribed_topics = self._subscribed_topics.copy()
947+
self.disconnect()
948+
942949
ret = self.connect()
943950
self.logger.debug("Reconnected with broker")
944-
if resub_topics:
951+
952+
if resub_topics and subscribed_topics:
945953
self.logger.debug("Attempting to resubscribe to previously subscribed topics.")
946-
subscribed_topics = self._subscribed_topics.copy()
947954
self._subscribed_topics = []
948955
while subscribed_topics:
949956
feed = subscribed_topics.pop()

tests/test_reconnect.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# SPDX-FileCopyrightText: 2025 Vladimír Kotal
2+
#
3+
# SPDX-License-Identifier: Unlicense
4+
5+
"""reconnect tests"""
6+
7+
import logging
8+
import ssl
9+
import sys
10+
11+
import pytest
12+
from mocket import Mocket
13+
14+
import adafruit_minimqtt.adafruit_minimqtt as MQTT
15+
16+
if not sys.implementation.name == "circuitpython":
17+
from typing import Optional
18+
19+
from circuitpython_typing.socket import (
20+
SocketType,
21+
SSLContextType,
22+
)
23+
24+
25+
class FakeConnectionManager:
26+
"""
27+
Fake ConnectionManager class
28+
"""
29+
def __init__(self, socket):
30+
self._socket = socket
31+
32+
def get_socket(
33+
self,
34+
host: str,
35+
port: int,
36+
proto: str,
37+
session_id: Optional[str] = None,
38+
*,
39+
timeout: float = 1.0,
40+
is_ssl: bool = False,
41+
ssl_context: Optional[SSLContextType] = None,
42+
) -> SocketType:
43+
"""
44+
Replicate what the ConnectionManager does and what MiniMQTT was doing previously by itself.
45+
"""
46+
return self._socket
47+
48+
def close_socket(self, socket) -> None:
49+
pass
50+
51+
52+
def handle_subscribe(client, user_data, topic, qos):
53+
"""
54+
Record topics into user data.
55+
"""
56+
assert topic
57+
assert user_data["topics"] is not None
58+
assert qos == 0
59+
60+
user_data["topics"].append(topic)
61+
62+
63+
def handle_disconnect(client, user_data, zero):
64+
"""
65+
Record disconnect.
66+
"""
67+
68+
user_data["disconnect"] = True
69+
70+
71+
# The MQTT packet contents below were captured using Mosquitto client+server.
72+
testdata = [
73+
(
74+
[],
75+
bytearray([0x20, 0x02, 0x00, 0x00, # CONNACK
76+
0x90, 0x03, 0x00, 0x01, 0x00, # SUBACK
77+
0x20, 0x02, 0x00, 0x00, # CONNACK
78+
0x90, 0x03, 0x00, 0x02, 0x00]), # SUBACK
79+
),
80+
(
81+
[("foo/bar", 0)],
82+
bytearray([0x20, 0x02, 0x00, 0x00, # CONNACK
83+
0x90, 0x03, 0x00, 0x01, 0x00, # SUBACK
84+
0x20, 0x02, 0x00, 0x00, # CONNACK
85+
0x90, 0x03, 0x00, 0x02, 0x00]), # SUBACK
86+
),
87+
(
88+
[("foo/bar", 0), ("bah", 0)],
89+
bytearray([0x20, 0x02, 0x00, 0x00, # CONNACK
90+
0x90, 0x03, 0x00, 0x01, 0x00, 0x00, # SUBACK
91+
0x20, 0x02, 0x00, 0x00, # CONNACK
92+
0x90, 0x03, 0x00, 0x02, 0x00, # SUBACK
93+
0x90, 0x03, 0x00, 0x03, 0x00]), # SUBACK
94+
),
95+
]
96+
97+
98+
@pytest.mark.parametrize(
99+
"topics,to_send",
100+
testdata,
101+
ids=[
102+
"no_topic",
103+
"single_topic",
104+
"multi_topic",
105+
],
106+
)
107+
def test_reconnect(topics, to_send) -> None:
108+
"""
109+
Test reconnect() handling, mainly that it performs disconnect on already connected socket.
110+
111+
Nothing will travel over the wire, it is all fake.
112+
"""
113+
logging.basicConfig()
114+
logger = logging.getLogger(__name__)
115+
logger.setLevel(logging.DEBUG)
116+
117+
host = "localhost"
118+
port = 1883
119+
120+
user_data = {"topics": [], "disconnect": False}
121+
mqtt_client = MQTT.MQTT(
122+
broker=host,
123+
port=port,
124+
ssl_context=ssl.create_default_context(),
125+
connect_retries=1,
126+
user_data=user_data,
127+
)
128+
129+
mocket = Mocket(to_send)
130+
mqtt_client._connection_manager = FakeConnectionManager(mocket)
131+
mqtt_client.connect()
132+
133+
mqtt_client.logger = logger
134+
135+
if topics:
136+
logger.info(f"subscribing to {topics}")
137+
mqtt_client.subscribe(topics)
138+
139+
logger.info("reconnecting")
140+
mqtt_client.on_subscribe = handle_subscribe
141+
mqtt_client.on_disconnect = handle_disconnect
142+
mqtt_client.reconnect()
143+
144+
assert user_data.get("disconnect") == True
145+
assert set(user_data.get("topics")) == set([t[0] for t in topics])

0 commit comments

Comments
 (0)