Skip to content

Commit 690007e

Browse files
committed
use time.monotonic_ns() when available
otherwise fall back to time.monotonic() however only if forced fixes #176
1 parent 926846c commit 690007e

File tree

1 file changed

+48
-21
lines changed

1 file changed

+48
-21
lines changed

adafruit_minimqtt/adafruit_minimqtt.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,12 @@ class MQTT:
169169
:param int connect_retries: How many times to try to connect to the broker before giving up
170170
on connect or reconnect. Exponential backoff will be used for the retries.
171171
:param class user_data: arbitrary data to pass as a second argument to the callbacks.
172+
:param bool use_imprecise_time: on boards without time.monotonic_ns() one has to set
173+
this to True in order to operate correctly over more than 24 days or so
172174
173175
"""
174176

175-
# pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements, not-callable, invalid-name, no-member
177+
# pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements,not-callable,invalid-name,no-member,too-many-locals
176178
def __init__(
177179
self,
178180
*,
@@ -190,13 +192,28 @@ def __init__(
190192
socket_timeout: int = 1,
191193
connect_retries: int = 5,
192194
user_data=None,
195+
use_imprecise_time: Optional[bool] = None,
193196
) -> None:
194197
self._socket_pool = socket_pool
195198
self._ssl_context = ssl_context
196199
self._sock = None
197200
self._backwards_compatible_sock = False
198201
self._use_binary_mode = use_binary_mode
199202

203+
self.use_monotonic_ns = False
204+
try:
205+
time.monotonic_ns()
206+
self.use_monotonic_ns = True
207+
except AttributeError:
208+
if use_imprecise_time:
209+
self.use_monotonic_ns = False
210+
else:
211+
raise MMQTTException( # pylint: disable=raise-missing-from
212+
"time.monotonic_ns() is not available. "
213+
"Will use imprecise time however only if the"
214+
"use_imprecise_time argument is set to True."
215+
)
216+
200217
if recv_timeout <= socket_timeout:
201218
raise MMQTTException(
202219
"recv_timeout must be strictly greater than socket_timeout"
@@ -248,9 +265,8 @@ def __init__(
248265
self.client_id = client_id
249266
else:
250267
# assign a unique client_id
251-
self.client_id = (
252-
f"cpy{randint(0, int(time.monotonic() * 100) % 1000)}{randint(0, 99)}"
253-
)
268+
time_int = int(self.get_monotonic_time() * 100) % 1000
269+
self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}"
254270
# generated client_id's enforce spec.'s length rules
255271
if len(self.client_id.encode("utf-8")) > 23 or not self.client_id:
256272
raise ValueError("MQTT Client ID must be between 1 and 23 bytes")
@@ -273,6 +289,17 @@ def __init__(
273289
self.on_subscribe = None
274290
self.on_unsubscribe = None
275291

292+
def get_monotonic_time(self) -> float:
293+
"""
294+
Provide monotonic time in seconds. Based on underlying implementation
295+
this might result in imprecise time, that will result in the library
296+
not being able to operate if running contiguously for more than 24 days or so.
297+
"""
298+
if self.use_monotonic_ns:
299+
return time.monotonic_ns() / 1000000000
300+
301+
return time.monotonic()
302+
276303
# pylint: disable=too-many-branches
277304
def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1):
278305
"""Obtains a new socket and connects to a broker.
@@ -627,7 +654,7 @@ def _connect(
627654
self._send_str(self._username)
628655
self._send_str(self._password)
629656
self.logger.debug("Receiving CONNACK packet from broker")
630-
stamp = time.monotonic()
657+
stamp = self.get_monotonic_time()
631658
while True:
632659
op = self._wait_for_msg()
633660
if op == 32:
@@ -643,7 +670,7 @@ def _connect(
643670
return result
644671

645672
if op is None:
646-
if time.monotonic() - stamp > self._recv_timeout:
673+
if self.get_monotonic_time() - stamp > self._recv_timeout:
647674
raise MMQTTException(
648675
f"No data received from broker for {self._recv_timeout} seconds."
649676
)
@@ -672,13 +699,13 @@ def ping(self) -> list[int]:
672699
self.logger.debug("Sending PINGREQ")
673700
self._sock.send(MQTT_PINGREQ)
674701
ping_timeout = self.keep_alive
675-
stamp = time.monotonic()
702+
stamp = self.get_monotonic_time()
676703
rc, rcs = None, []
677704
while rc != MQTT_PINGRESP:
678705
rc = self._wait_for_msg()
679706
if rc:
680707
rcs.append(rc)
681-
if time.monotonic() - stamp > ping_timeout:
708+
if self.get_monotonic_time() - stamp > ping_timeout:
682709
raise MMQTTException("PINGRESP not returned from broker.")
683710
return rcs
684711

@@ -759,7 +786,7 @@ def publish(
759786
if qos == 0 and self.on_publish is not None:
760787
self.on_publish(self, self._user_data, topic, self._pid)
761788
if qos == 1:
762-
stamp = time.monotonic()
789+
stamp = self.get_monotonic_time()
763790
while True:
764791
op = self._wait_for_msg()
765792
if op == 0x40:
@@ -773,7 +800,7 @@ def publish(
773800
return
774801

775802
if op is None:
776-
if time.monotonic() - stamp > self._recv_timeout:
803+
if self.get_monotonic_time() - stamp > self._recv_timeout:
777804
raise MMQTTException(
778805
f"No data received from broker for {self._recv_timeout} seconds."
779806
)
@@ -825,11 +852,11 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
825852
for t, q in topics:
826853
self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q)
827854
self._sock.send(packet)
828-
stamp = time.monotonic()
855+
stamp = self.get_monotonic_time()
829856
while True:
830857
op = self._wait_for_msg()
831858
if op is None:
832-
if time.monotonic() - stamp > self._recv_timeout:
859+
if self.get_monotonic_time() - stamp > self._recv_timeout:
833860
raise MMQTTException(
834861
f"No data received from broker for {self._recv_timeout} seconds."
835862
)
@@ -892,10 +919,10 @@ def unsubscribe(self, topic: str) -> None:
892919
self._sock.send(packet)
893920
self.logger.debug("Waiting for UNSUBACK...")
894921
while True:
895-
stamp = time.monotonic()
922+
stamp = self.get_monotonic_time()
896923
op = self._wait_for_msg()
897924
if op is None:
898-
if time.monotonic() - stamp > self._recv_timeout:
925+
if self.get_monotonic_time() - stamp > self._recv_timeout:
899926
raise MMQTTException(
900927
f"No data received from broker for {self._recv_timeout} seconds."
901928
)
@@ -989,8 +1016,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
9891016

9901017
self.logger.debug(f"waiting for messages for {timeout} seconds")
9911018
if self._timestamp == 0:
992-
self._timestamp = time.monotonic()
993-
current_time = time.monotonic()
1019+
self._timestamp = self.get_monotonic_time()
1020+
current_time = self.get_monotonic_time()
9941021
if current_time - self._timestamp >= self.keep_alive:
9951022
self._timestamp = 0
9961023
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
@@ -1000,14 +1027,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
10001027
rcs = self.ping()
10011028
return rcs
10021029

1003-
stamp = time.monotonic()
1030+
stamp = self.get_monotonic_time()
10041031
rcs = []
10051032

10061033
while True:
10071034
rc = self._wait_for_msg()
10081035
if rc is not None:
10091036
rcs.append(rc)
1010-
if time.monotonic() - stamp > timeout:
1037+
if self.get_monotonic_time() - stamp > timeout:
10111038
self.logger.debug(f"Loop timed out after {timeout} seconds")
10121039
break
10131040

@@ -1106,7 +1133,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11061133
:param int bufsize: number of bytes to receive
11071134
:return: byte array
11081135
"""
1109-
stamp = time.monotonic()
1136+
stamp = self.get_monotonic_time()
11101137
if not self._backwards_compatible_sock:
11111138
# CPython/Socketpool Impl.
11121139
rc = bytearray(bufsize)
@@ -1121,7 +1148,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11211148
recv_len = self._sock.recv_into(mv, to_read)
11221149
to_read -= recv_len
11231150
mv = mv[recv_len:]
1124-
if time.monotonic() - stamp > read_timeout:
1151+
if self.get_monotonic_time() - stamp > read_timeout:
11251152
raise MMQTTException(
11261153
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11271154
)
@@ -1141,7 +1168,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11411168
recv = self._sock.recv(to_read)
11421169
to_read -= len(recv)
11431170
rc += recv
1144-
if time.monotonic() - stamp > read_timeout:
1171+
if self.get_monotonic_time() - stamp > read_timeout:
11451172
raise MMQTTException(
11461173
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11471174
)

0 commit comments

Comments
 (0)