Skip to content

Commit ecfc57b

Browse files
authored
Merge pull request adafruit#21 from brentru/update-for-ethernet
Updates for Ethernet, Refactor
2 parents 6b3c2aa + ab1dfc8 commit ecfc57b

9 files changed

+329
-183
lines changed

adafruit_minimqtt.py

+69-115
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,31 @@
7676
}
7777

7878

79+
_the_interface = None # pylint: disable=invalid-name
80+
_the_sock = None # pylint: disable=invalid-name
81+
7982
class MMQTTException(Exception):
8083
"""MiniMQTT Exception class."""
8184

8285
# pylint: disable=unnecessary-pass
8386
# pass
8487

8588

89+
def set_socket(sock, iface=None):
90+
"""Helper to set the global socket and optionally set the global network interface.
91+
:param sock: socket object.
92+
:param iface: internet interface object
93+
94+
"""
95+
global _the_sock # pylint: disable=invalid-name, global-statement
96+
_the_sock = sock
97+
if iface:
98+
global _the_interface # pylint: disable=invalid-name, global-statement
99+
_the_interface = iface
100+
_the_sock.set_interface(iface)
101+
86102
class MQTT:
87103
"""MQTT Client for CircuitPython
88-
:param socket: Socket object for provided network interface
89104
:param str broker: MQTT Broker URL or IP Address.
90105
:param int port: Optional port definition, defaults to 8883.
91106
:param str username: Username for broker authentication.
@@ -95,33 +110,18 @@ class MQTT:
95110
:param bool is_ssl: Sets a secure or insecure connection with the broker.
96111
:param bool log: Attaches a logger to the MQTT client, defaults to logging level INFO.
97112
:param int keep_alive: KeepAlive interval between the broker and the MiniMQTT client.
113+
98114
"""
99115

100116
# pylint: disable=too-many-arguments,too-many-instance-attributes, not-callable, invalid-name, no-member
101-
def __init__(
102-
self,
103-
socket,
104-
broker,
105-
port=None,
106-
username=None,
107-
password=None,
108-
network_manager=None,
109-
client_id=None,
110-
is_ssl=True,
111-
log=False,
112-
keep_alive=60,
113-
):
114-
# network management
115-
self._socket = socket
116-
network_manager_type = str(type(network_manager))
117-
if "ESPSPI_WiFiManager" in network_manager_type:
118-
self._wifi = network_manager
119-
else:
120-
raise TypeError("This library requires a NetworkManager object.")
117+
def __init__(self, broker, port=None, username=None,
118+
password=None, client_id=None,
119+
is_ssl=True, log=False, keep_alive=60):
120+
self._sock = None
121121
# broker
122-
try: # set broker IP
123-
self.broker = self._wifi.esp.unpretty_ip(broker)
124-
except ValueError: # set broker URL
122+
try: # set broker IP
123+
self.broker = _the_interface.unpretty_ip(broker)
124+
except ValueError: # set broker URL
125125
self.broker = broker
126126
# port/ssl
127127
self.port = MQTT_TCP_PORT
@@ -181,6 +181,7 @@ def __exit__(self, exception_type, exception_value, traceback):
181181
def deinit(self):
182182
"""De-initializes the MQTT client and disconnects from
183183
the mqtt broker.
184+
184185
"""
185186
self.disconnect()
186187

@@ -190,6 +191,7 @@ def last_will(self, topic=None, message=None, qos=0, retain=False):
190191
:param str message: Last will disconnection message.
191192
:param int qos: Quality of Service level.
192193
:param bool retain: Specifies if the message is to be retained when it is published.
194+
193195
"""
194196
if self._is_connected:
195197
raise MMQTTException(
@@ -204,37 +206,45 @@ def last_will(self, topic=None, message=None, qos=0, retain=False):
204206
self._lw_msg = message
205207
self._lw_retain = retain
206208

207-
# pylint: disable=too-many-branches, too-many-statements
209+
# pylint: disable=too-many-branches, too-many-statements, too-many-locals
208210
def connect(self, clean_session=True):
209211
"""Initiates connection with the MQTT Broker.
210212
:param bool clean_session: Establishes a persistent session.
213+
211214
"""
212-
self._set_interface()
213-
if self.logger is not None:
214-
self.logger.debug("Creating new socket")
215-
self._sock = self._socket.socket()
216-
self._sock.settimeout(10)
215+
try:
216+
proto, dummy, self.broker, path = self.broker.split("/", 3)
217+
# replace spaces in path
218+
path = path.replace(" ", "%20")
219+
except ValueError:
220+
proto, dummy, self.broker = self.broker.split("/", 2)
221+
path = ""
222+
if proto == "http:":
223+
self.port = MQTT_TCP_PORT
224+
elif proto == "https:":
225+
self.port = MQTT_TLS_PORT
226+
else:
227+
raise ValueError("Unsupported protocol: " + proto)
228+
229+
if ":" in self.broker:
230+
self.broker, port = self.broker.split(":", 1)
231+
port = int(port)
232+
233+
addr = _the_sock.getaddrinfo(self.broker, self.port, 0, _the_sock.SOCK_STREAM)[0]
234+
self._sock = _the_sock.socket(addr[0], addr[1], addr[2])
235+
self._sock.settimeout(15)
217236
if self.port == 8883:
218237
try:
219238
if self.logger is not None:
220-
self.logger.debug(
221-
"Attempting to establish secure MQTT connection..."
222-
)
223-
self._sock.connect((self.broker, self.port), TLS_MODE)
224-
except RuntimeError:
225-
raise MMQTTException("Invalid broker address defined.")
239+
self.logger.debug('Attempting to establish secure MQTT connection...')
240+
self._sock.connect((self.broker, self.port), _the_interface.TLS_MODE)
241+
except RuntimeError as e:
242+
raise MMQTTException("Invalid broker address defined.", e)
226243
else:
227-
if isinstance(self.broker, str):
228-
addr = self._socket.getaddrinfo(self.broker, self.port)[0][-1]
229-
else:
230-
addr = (self.broker, self.port)
231244
try:
232245
if self.logger is not None:
233-
self.logger.debug(
234-
"Attempting to establish insecure MQTT connection..."
235-
)
236-
# self._sock.connect((self.broker, self.port), TCP_MODE)
237-
self._sock.connect(addr, TCP_MODE)
246+
self.logger.debug('Attempting to establish insecure MQTT connection...')
247+
self._sock.connect(addr[-1], TCP_MODE)
238248
except RuntimeError as e:
239249
raise MMQTTException("Invalid broker address defined.", e)
240250

@@ -376,9 +386,9 @@ def publish(self, topic, msg, retain=False, qos=0):
376386
raise MMQTTException("Publish topic can not contain wildcards.")
377387
# check msg/qos kwargs
378388
if msg is None:
379-
raise MMQTTException("Message can not be None.")
389+
raise MMQTTException('Message can not be None.')
380390
if isinstance(msg, (int, float)):
381-
msg = str(msg).encode("ascii")
391+
msg = str(msg).encode('ascii')
382392
elif isinstance(msg, str):
383393
msg = str(msg).encode("utf-8")
384394
else:
@@ -574,55 +584,6 @@ def unsubscribe(self, topic):
574584
self._subscribed_topics.remove(t)
575585
return
576586

577-
@property
578-
def is_wifi_connected(self):
579-
"""Returns if the ESP module is connected to
580-
an access point, resets module if False"""
581-
if self._wifi:
582-
return self._wifi.esp.is_connected
583-
raise MMQTTException("MiniMQTT Client does not use a WiFi NetworkManager.")
584-
585-
# pylint: disable=line-too-long, protected-access
586-
@property
587-
def is_sock_connected(self):
588-
"""Returns if the socket is connected."""
589-
return (
590-
self.is_wifi_connected
591-
and self._sock
592-
and self._wifi.esp.socket_connected(self._sock._socknum)
593-
)
594-
595-
def reconnect_socket(self):
596-
"""Re-establishes the socket's connection with the MQTT broker.
597-
"""
598-
try:
599-
if self.logger is not None:
600-
self.logger.debug("Attempting to reconnect with MQTT Broker...")
601-
self.reconnect()
602-
except RuntimeError as err:
603-
if self.logger is not None:
604-
self.logger.debug(
605-
"Failed to reconnect with MQTT Broker, retrying...", err
606-
)
607-
time.sleep(1)
608-
self.reconnect_socket()
609-
610-
def reconnect_wifi(self):
611-
"""Reconnects to WiFi Access Point and socket, if disconnected.
612-
"""
613-
while not self.is_wifi_connected:
614-
try:
615-
if self.logger is not None:
616-
self.logger.debug("Connecting to WiFi AP...")
617-
self._wifi.connect()
618-
except (RuntimeError, ValueError):
619-
if self.logger is not None:
620-
self.logger.debug("Failed to reset WiFi module, retrying...")
621-
time.sleep(1)
622-
# we just reconnected, is the socket still connected?
623-
if not self.is_sock_connected:
624-
self.reconnect_socket()
625-
626587
def reconnect(self, resub_topics=True):
627588
"""Attempts to reconnect to the MQTT broker.
628589
:param bool resub_topics: Resubscribe to previously subscribed topics.
@@ -645,37 +606,30 @@ def loop_forever(self):
645606
"""Starts a blocking message loop. Use this
646607
method if you want to run a program forever.
647608
Code below a call to this method will NOT execute.
648-
Network reconnection is handled within this call.
609+
610+
NOTE: This method is depreciated and will be removed in the
611+
next major release. Please see examples/minimqtt_pub_sub_blocking.py
612+
for an example of creating a blocking loop which can handle wireless
613+
network events.
649614
650615
"""
651616
while True:
652-
# Check WiFi and socket status
653-
if self.is_sock_connected:
654-
try:
655-
self.loop()
656-
except (RuntimeError, ValueError):
657-
if self._wifi:
658-
# Reconnect the WiFi module and the socket
659-
self.reconnect_wifi()
660-
continue
617+
if self._sock.connected:
618+
self.loop()
661619

662620
def loop(self):
663621
"""Non-blocking message loop. Use this method to
664622
check incoming subscription messages.
665623
666-
This method does NOT handle networking or
667-
network hardware management, use loop_forever
668-
or handle in code instead.
669624
"""
670625
if self._timestamp == 0:
671626
self._timestamp = time.monotonic()
672627
current_time = time.monotonic()
673628
if current_time - self._timestamp >= self.keep_alive:
674629
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
675630
if self.logger is not None:
676-
self.logger.debug(
677-
"KeepAlive period elapsed - requesting a PINGRESP from the server..."
678-
)
631+
self.logger.debug('KeepAlive period elapsed - \
632+
requesting a PINGRESP from the server...')
679633
self.ping()
680634
self._timestamp = 0
681635
self._sock.settimeout(0.1)
@@ -745,10 +699,10 @@ def _check_topic(topic):
745699
raise MMQTTException("Topic may not be NoneType")
746700
# [MQTT-4.7.3-1]
747701
if not topic:
748-
raise MMQTTException("Topic may not be empty.")
702+
raise MMQTTException('Topic may not be empty.')
749703
# [MQTT-4.7.3-3]
750-
if len(topic.encode("utf-8")) > MQTT_TOPIC_LENGTH_LIMIT:
751-
raise MMQTTException("Topic length is too large.")
704+
if len(topic.encode('utf-8')) > MQTT_TOPIC_LENGTH_LIMIT:
705+
raise MMQTTException('Topic length is too large.')
752706

753707
@staticmethod
754708
def _check_qos(qos_level):

examples/minimqtt_adafruitio_eth.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Adafruit MiniMQTT Pub/Sub Example
2+
# Written by Tony DiCola for Adafruit Industries
3+
# Modified by Brent Rubell for Adafruit Industries
4+
import time
5+
import board
6+
import busio
7+
from digitalio import DigitalInOut
8+
9+
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
10+
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
11+
12+
import adafruit_minimqtt as MQTT
13+
14+
# Get Adafruit IO details and more from a secrets.py file
15+
try:
16+
from secrets import secrets
17+
except ImportError:
18+
print("Adafruit IO secrets are kept in secrets.py, please add them there!")
19+
raise
20+
21+
cs = DigitalInOut(board.D10)
22+
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
23+
24+
# Initialize ethernet interface with DHCP
25+
eth = WIZNET5K(spi_bus, cs)
26+
27+
### Feeds ###
28+
29+
# Setup a feed named 'photocell' for publishing to a feed
30+
photocell_feed = secrets['aio_username'] + '/feeds/photocell'
31+
32+
# Setup a feed named 'onoff' for subscribing to changes
33+
onoff_feed = secrets['aio_username'] + '/feeds/onoff'
34+
35+
### Code ###
36+
37+
# Define callback methods which are called when events occur
38+
# pylint: disable=unused-argument, redefined-outer-name
39+
def connected(client, userdata, flags, rc):
40+
# This function will be called when the client is connected
41+
# successfully to the broker.
42+
print('Connected to Adafruit IO! Listening for topic changes on %s' % onoff_feed)
43+
# Subscribe to all changes on the onoff_feed.
44+
client.subscribe(onoff_feed)
45+
46+
47+
def disconnected(client, userdata, rc):
48+
# This method is called when the client is disconnected
49+
print('Disconnected from Adafruit IO!')
50+
51+
52+
def message(client, topic, message):
53+
# This method is called when a topic the client is subscribed to
54+
# has a new message.
55+
print('New message on topic {0}: {1}'.format(topic, message))
56+
57+
58+
# Initialize MQTT interface with the ethernet interface
59+
MQTT.set_socket(socket, eth)
60+
61+
# Set up a MiniMQTT Client
62+
# NOTE: We'll need to connect insecurely for ethernet configurations.
63+
mqtt_client = MQTT.MQTT(broker = 'http://io.adafruit.com',
64+
username = secrets['aio_username'],
65+
password = secrets['aio_key'])
66+
67+
# Setup the callback methods above
68+
mqtt_client.on_connect = connected
69+
mqtt_client.on_disconnect = disconnected
70+
mqtt_client.on_message = message
71+
72+
# Connect the client to the MQTT broker.
73+
print('Connecting to Adafruit IO...')
74+
mqtt_client.connect()
75+
76+
photocell_val = 0
77+
while True:
78+
# Poll the message queue
79+
mqtt_client.loop()
80+
81+
# Send a new message
82+
print('Sending photocell value: %d...' % photocell_val)
83+
mqtt_client.publish(photocell_feed, photocell_val)
84+
print('Sent!')
85+
photocell_val += 1
86+
time.sleep(5)

0 commit comments

Comments
 (0)