Skip to content

Commit 5462177

Browse files
authored
Merge pull request adafruit#197 from justmobilize/connection-manager
Switch to using ConnectionManager
2 parents 4742286 + 8a1b617 commit 5462177

27 files changed

+297
-336
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,9 @@ _build
4747
.vscode
4848
*~
4949

50-
# tox local cache
50+
# tox-specific files
5151
.tox
52+
build
53+
54+
# coverage-specific files
55+
.coverage

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
repos:
66
- repo: https://github.com/python/black
7-
rev: 23.3.0
7+
rev: 24.2.0
88
hooks:
99
- id: black
1010
- repo: https://github.com/fsfe/reuse-tool
@@ -32,11 +32,11 @@ repos:
3232
types: [python]
3333
files: "^examples/"
3434
args:
35-
- --disable=missing-docstring,invalid-name,consider-using-f-string,duplicate-code
35+
- --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name
3636
- id: pylint
3737
name: pylint (test code)
3838
description: Run pylint rules on "tests/*.py" files
3939
types: [python]
4040
files: "^tests/"
4141
args:
42-
- --disable=missing-docstring,consider-using-f-string,duplicate-code
42+
- --disable=consider-using-f-string,duplicate-code,missing-docstring,invalid-name,protected-access

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Dependencies
2424
This driver depends on:
2525

2626
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
27+
* `Adafruit CircuitPython ConnectionManager <https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager/>`_
2728

2829
Please ensure all dependencies are available on the CircuitPython filesystem.
2930
This is easily achieved by downloading

adafruit_minimqtt/adafruit_minimqtt.py

Lines changed: 17 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@
2626
* Adafruit CircuitPython firmware for the supported boards:
2727
https://github.com/adafruit/circuitpython/releases
2828
29+
* Adafruit's Connection Manager library:
30+
https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager
31+
2932
"""
3033
import errno
3134
import struct
3235
import time
3336
from random import randint
3437

38+
from adafruit_connection_manager import get_connection_manager
39+
3540
try:
3641
from typing import List, Optional, Tuple, Type, Union
3742
except ImportError:
@@ -82,64 +87,13 @@
8287
class MMQTTException(Exception):
8388
"""MiniMQTT Exception class."""
8489

85-
# pylint: disable=unnecessary-pass
86-
# pass
87-
88-
89-
class TemporaryError(Exception):
90-
"""Temporary error class used for handling reconnects."""
91-
92-
93-
# Legacy ESP32SPI Socket API
94-
def set_socket(sock, iface=None) -> None:
95-
"""Legacy API for setting the socket and network interface.
96-
97-
:param sock: socket object.
98-
:param iface: internet interface object
99-
100-
"""
101-
global _default_sock # pylint: disable=invalid-name, global-statement
102-
global _fake_context # pylint: disable=invalid-name, global-statement
103-
_default_sock = sock
104-
if iface:
105-
_default_sock.set_interface(iface)
106-
_fake_context = _FakeSSLContext(iface)
107-
108-
109-
class _FakeSSLSocket:
110-
def __init__(self, socket, tls_mode) -> None:
111-
self._socket = socket
112-
self._mode = tls_mode
113-
self.settimeout = socket.settimeout
114-
self.send = socket.send
115-
self.recv = socket.recv
116-
self.close = socket.close
117-
118-
def connect(self, address):
119-
"""connect wrapper to add non-standard mode parameter"""
120-
try:
121-
return self._socket.connect(address, self._mode)
122-
except RuntimeError as error:
123-
raise OSError(errno.ENOMEM) from error
124-
125-
126-
class _FakeSSLContext:
127-
def __init__(self, iface) -> None:
128-
self._iface = iface
129-
130-
def wrap_socket(self, socket, server_hostname=None) -> _FakeSSLSocket:
131-
"""Return the same socket"""
132-
# pylint: disable=unused-argument
133-
return _FakeSSLSocket(socket, self._iface.TLS_MODE)
134-
13590

13691
class NullLogger:
13792
"""Fake logger class that does not do anything"""
13893

13994
# pylint: disable=unused-argument
14095
def nothing(self, msg: str, *args) -> None:
14196
"""no action"""
142-
pass
14397

14498
def __init__(self) -> None:
14599
for log_level in ["debug", "info", "warning", "error", "critical"]:
@@ -194,6 +148,7 @@ def __init__(
194148
user_data=None,
195149
use_imprecise_time: Optional[bool] = None,
196150
) -> None:
151+
self._connection_manager = get_connection_manager(socket_pool)
197152
self._socket_pool = socket_pool
198153
self._ssl_context = ssl_context
199154
self._sock = None
@@ -300,75 +255,6 @@ def get_monotonic_time(self) -> float:
300255

301256
return time.monotonic()
302257

303-
# pylint: disable=too-many-branches
304-
def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1):
305-
"""Obtains a new socket and connects to a broker.
306-
307-
:param str host: Desired broker hostname
308-
:param int port: Desired broker port
309-
:param int timeout: Desired socket timeout, in seconds
310-
"""
311-
# For reconnections - check if we're using a socket already and close it
312-
if self._sock:
313-
self._sock.close()
314-
self._sock = None
315-
316-
# Legacy API - use the interface's socket instead of a passed socket pool
317-
if self._socket_pool is None:
318-
self._socket_pool = _default_sock
319-
320-
# Legacy API - fake the ssl context
321-
if self._ssl_context is None:
322-
self._ssl_context = _fake_context
323-
324-
if not isinstance(port, int):
325-
raise RuntimeError("Port must be an integer")
326-
327-
if self._is_ssl and not self._ssl_context:
328-
raise RuntimeError(
329-
"ssl_context must be set before using adafruit_mqtt for secure MQTT."
330-
)
331-
332-
if self._is_ssl:
333-
self.logger.info(f"Establishing a SECURE SSL connection to {host}:{port}")
334-
else:
335-
self.logger.info(f"Establishing an INSECURE connection to {host}:{port}")
336-
337-
addr_info = self._socket_pool.getaddrinfo(
338-
host, port, 0, self._socket_pool.SOCK_STREAM
339-
)[0]
340-
341-
try:
342-
sock = self._socket_pool.socket(addr_info[0], addr_info[1])
343-
except OSError as exc:
344-
# Do not consider this for back-off.
345-
self.logger.warning(
346-
f"Failed to create socket for host {addr_info[0]} and port {addr_info[1]}"
347-
)
348-
raise TemporaryError from exc
349-
350-
connect_host = addr_info[-1][0]
351-
if self._is_ssl:
352-
sock = self._ssl_context.wrap_socket(sock, server_hostname=host)
353-
connect_host = host
354-
sock.settimeout(timeout)
355-
356-
try:
357-
sock.connect((connect_host, port))
358-
except MemoryError as exc:
359-
sock.close()
360-
self.logger.warning(f"Failed to allocate memory for connect: {exc}")
361-
# Do not consider this for back-off.
362-
raise TemporaryError from exc
363-
except OSError as exc:
364-
sock.close()
365-
self.logger.warning(f"Failed to connect: {exc}")
366-
# Do not consider this for back-off.
367-
raise TemporaryError from exc
368-
369-
self._backwards_compatible_sock = not hasattr(sock, "recv_into")
370-
return sock
371-
372258
def __enter__(self):
373259
return self
374260

@@ -538,8 +424,8 @@ def connect(
538424
)
539425
self._reset_reconnect_backoff()
540426
return ret
541-
except TemporaryError as e:
542-
self.logger.warning(f"temporary error when connecting: {e}")
427+
except RuntimeError as e:
428+
self.logger.warning(f"Socket error when connecting: {e}")
543429
backoff = False
544430
except MMQTTException as e:
545431
last_exception = e
@@ -587,9 +473,15 @@ def _connect(
587473
time.sleep(self._reconnect_timeout)
588474

589475
# Get a new socket
590-
self._sock = self._get_connect_socket(
591-
self.broker, self.port, timeout=self._socket_timeout
476+
self._sock = self._connection_manager.get_socket(
477+
self.broker,
478+
self.port,
479+
proto="mqtt:",
480+
timeout=self._socket_timeout,
481+
is_ssl=self._is_ssl,
482+
ssl_context=self._ssl_context,
592483
)
484+
self._backwards_compatible_sock = not hasattr(self._sock, "recv_into")
593485

594486
fixed_header = bytearray([0x10])
595487

@@ -686,7 +578,7 @@ def disconnect(self) -> None:
686578
except RuntimeError as e:
687579
self.logger.warning(f"Unable to send DISCONNECT packet: {e}")
688580
self.logger.debug("Closing socket")
689-
self._sock.close()
581+
self._connection_manager.free_socket(self._sock)
690582
self._is_connected = False
691583
self._subscribed_topics = []
692584
self._last_msg_sent_timestamp = 0

examples/cellular/minimqtt_adafruitio_cellular.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
22
# SPDX-License-Identifier: MIT
33

4+
import os
45
import time
56
import board
67
import busio
78
import digitalio
9+
import adafruit_connection_manager
810
from adafruit_fona.adafruit_fona import FONA
911
import adafruit_fona.adafruit_fona_network as network
10-
import adafruit_fona.adafruit_fona_socket as socket
12+
import adafruit_fona.adafruit_fona_socket as pool
1113

1214
import adafruit_minimqtt.adafruit_minimqtt as MQTT
1315

14-
# Get Adafruit IO details and more from a secrets.py file
15-
try:
16-
from secrets import secrets
17-
except ImportError:
18-
print("GPRS secrets are kept in secrets.py, please add them there!")
19-
raise
16+
# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
17+
# with your GPRS credentials. Add your Adafruit IO username and key as well.
18+
# DO NOT share that file or commit it into Git or other source control.
19+
20+
aio_username = os.getenv("aio_username")
21+
aio_key = os.getenv("aio_key")
2022

2123
### Cellular ###
2224

@@ -29,10 +31,10 @@
2931
### Feeds ###
3032

3133
# Setup a feed named 'photocell' for publishing to a feed
32-
photocell_feed = secrets["aio_username"] + "/feeds/photocell"
34+
photocell_feed = aio_username + "/feeds/photocell"
3335

3436
# Setup a feed named 'onoff' for subscribing to changes
35-
onoff_feed = secrets["aio_username"] + "/feeds/onoff"
37+
onoff_feed = aio_username + "/feeds/onoff"
3638

3739
### Code ###
3840

@@ -60,7 +62,7 @@ def message(client, topic, message):
6062

6163
# Initialize cellular data network
6264
network = network.CELLULAR(
63-
fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"])
65+
fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password"))
6466
)
6567

6668
while not network.is_attached:
@@ -74,16 +76,17 @@ def message(client, topic, message):
7476
time.sleep(0.5)
7577
print("Network Connected!")
7678

77-
# Initialize MQTT interface with the cellular interface
78-
MQTT.set_socket(socket, fona)
79+
ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, fona)
7980

8081
# Set up a MiniMQTT Client
8182
# NOTE: We'll need to connect insecurely for ethernet configurations.
8283
mqtt_client = MQTT.MQTT(
8384
broker="io.adafruit.com",
84-
username=secrets["aio_username"],
85-
password=secrets["aio_key"],
85+
username=aio_username,
86+
password=aio_key,
8687
is_ssl=False,
88+
socket_pool=pool,
89+
ssl_context=ssl_context,
8790
)
8891

8992
# Setup the callback methods above

examples/cellular/minimqtt_simpletest_cellular.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
22
# SPDX-License-Identifier: MIT
33

4+
import os
45
import time
56
import board
67
import busio
78
import digitalio
9+
import adafruit_connection_manager
810
from adafruit_fona.adafruit_fona import FONA
911
import adafruit_fona.adafruit_fona_network as network
10-
import adafruit_fona.adafruit_fona_socket as socket
12+
import adafruit_fona.adafruit_fona_socket as pool
1113

1214
import adafruit_minimqtt.adafruit_minimqtt as MQTT
1315

14-
### Cellular ###
16+
# Add settings.toml to your filesystem CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD keys
17+
# with your GPRS credentials. Add your Adafruit IO username and key as well.
18+
# DO NOT share that file or commit it into Git or other source control.
1519

16-
# Get cellular details and more from a secrets.py file
17-
try:
18-
from secrets import secrets
19-
except ImportError:
20-
print("Cellular secrets are kept in secrets.py, please add them there!")
21-
raise
20+
aio_username = os.getenv("aio_username")
21+
aio_key = os.getenv("aio_key")
2222

2323
# Create a serial connection for the FONA connection
2424
uart = busio.UART(board.TX, board.RX)
@@ -71,7 +71,7 @@ def publish(client, userdata, topic, pid):
7171

7272
# Initialize cellular data network
7373
network = network.CELLULAR(
74-
fona, (secrets["apn"], secrets["apn_username"], secrets["apn_password"])
74+
fona, (os.getenv("apn"), os.getenv("apn_username"), os.getenv("apn_password"))
7575
)
7676

7777
while not network.is_attached:
@@ -85,15 +85,16 @@ def publish(client, userdata, topic, pid):
8585
time.sleep(0.5)
8686
print("Network Connected!")
8787

88-
# Initialize MQTT interface with the cellular interface
89-
MQTT.set_socket(socket, fona)
88+
ssl_context = adafruit_connection_manager.create_fake_ssl_context(pool, fona)
9089

9190
# Set up a MiniMQTT Client
9291
client = MQTT.MQTT(
93-
broker=secrets["broker"],
94-
username=secrets["user"],
95-
password=secrets["pass"],
92+
broker=os.getenv("broker"),
93+
username=os.getenv("username"),
94+
password=os.getenv("password"),
9695
is_ssl=False,
96+
socket_pool=pool,
97+
ssl_context=ssl_context,
9798
)
9899

99100
# Connect callback handlers to client

0 commit comments

Comments
 (0)