Skip to content

Commit 470c19c

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

File tree

1 file changed

+45
-19
lines changed

1 file changed

+45
-19
lines changed

adafruit_minimqtt/adafruit_minimqtt.py

Lines changed: 45 additions & 19 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
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,26 @@ 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.monotonic_ns = False
204+
try:
205+
time.monotonic_ns()
206+
self.monotonic_ns = True
207+
except AttributeError:
208+
if use_imprecise_time:
209+
self.monotonic_ns = False
210+
else:
211+
raise MMQTTException("time.monotonic_ns() is not available. " # pylint: disable=raise-missing-from
212+
"Will use imprecise time however only if the"
213+
"use_imprecise_time argument is set to True.")
214+
200215
if recv_timeout <= socket_timeout:
201216
raise MMQTTException(
202217
"recv_timeout must be strictly greater than socket_timeout"
@@ -249,7 +264,7 @@ def __init__(
249264
else:
250265
# assign a unique client_id
251266
self.client_id = (
252-
f"cpy{randint(0, int(time.monotonic() * 100) % 1000)}{randint(0, 99)}"
267+
f"cpy{randint(0, int(self.get_monotonic_time() * 100) % 1000)}{randint(0, 99)}"
253268
)
254269
# generated client_id's enforce spec.'s length rules
255270
if len(self.client_id.encode("utf-8")) > 23 or not self.client_id:
@@ -273,6 +288,17 @@ def __init__(
273288
self.on_subscribe = None
274289
self.on_unsubscribe = None
275290

291+
def get_monotonic_time(self) -> float:
292+
"""
293+
Provide monotonic time in seconds. Based on underlying implementation
294+
this might result in imprecise time, that will result in the library
295+
not being able to operate if running contiguously for more than 24 days or so.
296+
"""
297+
if self.monotonic_ns:
298+
return time.monotonic_ns() / 1000000
299+
300+
return time.monotonic()
301+
276302
# pylint: disable=too-many-branches
277303
def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1):
278304
"""Obtains a new socket and connects to a broker.
@@ -627,7 +653,7 @@ def _connect(
627653
self._send_str(self._username)
628654
self._send_str(self._password)
629655
self.logger.debug("Receiving CONNACK packet from broker")
630-
stamp = time.monotonic()
656+
stamp = self.get_monotonic_time()
631657
while True:
632658
op = self._wait_for_msg()
633659
if op == 32:
@@ -643,7 +669,7 @@ def _connect(
643669
return result
644670

645671
if op is None:
646-
if time.monotonic() - stamp > self._recv_timeout:
672+
if self.get_monotonic_time() - stamp > self._recv_timeout:
647673
raise MMQTTException(
648674
f"No data received from broker for {self._recv_timeout} seconds."
649675
)
@@ -672,13 +698,13 @@ def ping(self) -> list[int]:
672698
self.logger.debug("Sending PINGREQ")
673699
self._sock.send(MQTT_PINGREQ)
674700
ping_timeout = self.keep_alive
675-
stamp = time.monotonic()
701+
stamp = self.get_monotonic_time()
676702
rc, rcs = None, []
677703
while rc != MQTT_PINGRESP:
678704
rc = self._wait_for_msg()
679705
if rc:
680706
rcs.append(rc)
681-
if time.monotonic() - stamp > ping_timeout:
707+
if self.get_monotonic_time() - stamp > ping_timeout:
682708
raise MMQTTException("PINGRESP not returned from broker.")
683709
return rcs
684710

@@ -759,7 +785,7 @@ def publish(
759785
if qos == 0 and self.on_publish is not None:
760786
self.on_publish(self, self._user_data, topic, self._pid)
761787
if qos == 1:
762-
stamp = time.monotonic()
788+
stamp = self.get_monotonic_time()
763789
while True:
764790
op = self._wait_for_msg()
765791
if op == 0x40:
@@ -773,7 +799,7 @@ def publish(
773799
return
774800

775801
if op is None:
776-
if time.monotonic() - stamp > self._recv_timeout:
802+
if self.get_monotonic_time() - stamp > self._recv_timeout:
777803
raise MMQTTException(
778804
f"No data received from broker for {self._recv_timeout} seconds."
779805
)
@@ -825,11 +851,11 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
825851
for t, q in topics:
826852
self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q)
827853
self._sock.send(packet)
828-
stamp = time.monotonic()
854+
stamp = self.get_monotonic_time()
829855
while True:
830856
op = self._wait_for_msg()
831857
if op is None:
832-
if time.monotonic() - stamp > self._recv_timeout:
858+
if self.get_monotonic_time() - stamp > self._recv_timeout:
833859
raise MMQTTException(
834860
f"No data received from broker for {self._recv_timeout} seconds."
835861
)
@@ -892,10 +918,10 @@ def unsubscribe(self, topic: str) -> None:
892918
self._sock.send(packet)
893919
self.logger.debug("Waiting for UNSUBACK...")
894920
while True:
895-
stamp = time.monotonic()
921+
stamp = self.get_monotonic_time()
896922
op = self._wait_for_msg()
897923
if op is None:
898-
if time.monotonic() - stamp > self._recv_timeout:
924+
if self.get_monotonic_time() - stamp > self._recv_timeout:
899925
raise MMQTTException(
900926
f"No data received from broker for {self._recv_timeout} seconds."
901927
)
@@ -989,8 +1015,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
9891015

9901016
self.logger.debug(f"waiting for messages for {timeout} seconds")
9911017
if self._timestamp == 0:
992-
self._timestamp = time.monotonic()
993-
current_time = time.monotonic()
1018+
self._timestamp = self.get_monotonic_time()
1019+
current_time = self.get_monotonic_time()
9941020
if current_time - self._timestamp >= self.keep_alive:
9951021
self._timestamp = 0
9961022
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
@@ -1000,14 +1026,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
10001026
rcs = self.ping()
10011027
return rcs
10021028

1003-
stamp = time.monotonic()
1029+
stamp = self.get_monotonic_time()
10041030
rcs = []
10051031

10061032
while True:
10071033
rc = self._wait_for_msg()
10081034
if rc is not None:
10091035
rcs.append(rc)
1010-
if time.monotonic() - stamp > timeout:
1036+
if self.get_monotonic_time() - stamp > timeout:
10111037
self.logger.debug(f"Loop timed out after {timeout} seconds")
10121038
break
10131039

@@ -1106,7 +1132,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11061132
:param int bufsize: number of bytes to receive
11071133
:return: byte array
11081134
"""
1109-
stamp = time.monotonic()
1135+
stamp = self.get_monotonic_time()
11101136
if not self._backwards_compatible_sock:
11111137
# CPython/Socketpool Impl.
11121138
rc = bytearray(bufsize)
@@ -1121,7 +1147,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11211147
recv_len = self._sock.recv_into(mv, to_read)
11221148
to_read -= recv_len
11231149
mv = mv[recv_len:]
1124-
if time.monotonic() - stamp > read_timeout:
1150+
if self.get_monotonic_time() - stamp > read_timeout:
11251151
raise MMQTTException(
11261152
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11271153
)
@@ -1141,7 +1167,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11411167
recv = self._sock.recv(to_read)
11421168
to_read -= len(recv)
11431169
rc += recv
1144-
if time.monotonic() - stamp > read_timeout:
1170+
if self.get_monotonic_time() - stamp > read_timeout:
11451171
raise MMQTTException(
11461172
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11471173
)

0 commit comments

Comments
 (0)