Skip to content

Commit dc36134

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

File tree

2 files changed

+214
-2
lines changed

2 files changed

+214
-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: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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+
30+
def __init__(self, socket):
31+
self._socket = socket
32+
33+
def get_socket( # noqa: PLR0913, Too many arguments
34+
self,
35+
host: str,
36+
port: int,
37+
proto: str,
38+
session_id: Optional[str] = None,
39+
*,
40+
timeout: float = 1.0,
41+
is_ssl: bool = False,
42+
ssl_context: Optional[SSLContextType] = None,
43+
) -> SocketType:
44+
"""
45+
Return the specified socket.
46+
"""
47+
return self._socket
48+
49+
def close_socket(self, socket) -> None:
50+
pass
51+
52+
53+
def handle_subscribe(client, user_data, topic, qos):
54+
"""
55+
Record topics into user data.
56+
"""
57+
assert topic
58+
assert user_data["topics"] is not None
59+
assert qos == 0
60+
61+
user_data["topics"].append(topic)
62+
63+
64+
def handle_disconnect(client, user_data, zero):
65+
"""
66+
Record disconnect.
67+
"""
68+
69+
user_data["disconnect"] = True
70+
71+
72+
# The MQTT packet contents below were captured using Mosquitto client+server.
73+
testdata = [
74+
(
75+
[],
76+
bytearray(
77+
[
78+
0x20, # CONNACK
79+
0x02,
80+
0x00,
81+
0x00,
82+
0x90, # SUBACK
83+
0x03,
84+
0x00,
85+
0x01,
86+
0x00,
87+
0x20, # CONNACK
88+
0x02,
89+
0x00,
90+
0x00,
91+
0x90, # SUBACK
92+
0x03,
93+
0x00,
94+
0x02,
95+
0x00,
96+
]
97+
),
98+
),
99+
(
100+
[("foo/bar", 0)],
101+
bytearray(
102+
[
103+
0x20, # CONNACK
104+
0x02,
105+
0x00,
106+
0x00,
107+
0x90, # SUBACK
108+
0x03,
109+
0x00,
110+
0x01,
111+
0x00,
112+
0x20, # CONNACK
113+
0x02,
114+
0x00,
115+
0x00,
116+
0x90, # SUBACK
117+
0x03,
118+
0x00,
119+
0x02,
120+
0x00,
121+
]
122+
),
123+
),
124+
(
125+
[("foo/bar", 0), ("bah", 0)],
126+
bytearray(
127+
[
128+
0x20, # CONNACK
129+
0x02,
130+
0x00,
131+
0x00,
132+
0x90, # SUBACK
133+
0x03,
134+
0x00,
135+
0x01,
136+
0x00,
137+
0x00,
138+
0x20, # CONNACK
139+
0x02,
140+
0x00,
141+
0x00,
142+
0x90, # SUBACK
143+
0x03,
144+
0x00,
145+
0x02,
146+
0x00,
147+
0x90, # SUBACK
148+
0x03,
149+
0x00,
150+
0x03,
151+
0x00,
152+
]
153+
),
154+
),
155+
]
156+
157+
158+
@pytest.mark.parametrize(
159+
"topics,to_send",
160+
testdata,
161+
ids=[
162+
"no_topic",
163+
"single_topic",
164+
"multi_topic",
165+
],
166+
)
167+
def test_reconnect(topics, to_send) -> None:
168+
"""
169+
Test reconnect() handling, mainly that it performs disconnect on already connected socket.
170+
171+
Nothing will travel over the wire, it is all fake.
172+
"""
173+
logging.basicConfig()
174+
logger = logging.getLogger(__name__)
175+
logger.setLevel(logging.DEBUG)
176+
177+
host = "localhost"
178+
port = 1883
179+
180+
user_data = {"topics": [], "disconnect": False}
181+
mqtt_client = MQTT.MQTT(
182+
broker=host,
183+
port=port,
184+
ssl_context=ssl.create_default_context(),
185+
connect_retries=1,
186+
user_data=user_data,
187+
)
188+
189+
mocket = Mocket(to_send)
190+
mqtt_client._connection_manager = FakeConnectionManager(mocket)
191+
mqtt_client.connect()
192+
193+
mqtt_client.logger = logger
194+
195+
if topics:
196+
logger.info(f"subscribing to {topics}")
197+
mqtt_client.subscribe(topics)
198+
199+
logger.info("reconnecting")
200+
mqtt_client.on_subscribe = handle_subscribe
201+
mqtt_client.on_disconnect = handle_disconnect
202+
mqtt_client.reconnect()
203+
204+
assert user_data.get("disconnect") == True
205+
assert set(user_data.get("topics")) == set([t[0] for t in topics])

0 commit comments

Comments
 (0)