Skip to content

Commit d1e2b7c

Browse files
authored
Merge pull request #182 from vladak/time_monotonic_ns
use time.monotonic_ns() when available
2 parents 6270110 + 20ba1e3 commit d1e2b7c

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
@@ -172,10 +172,12 @@ class MQTT:
172172
This works with all callbacks but the "on_message" and those added via add_topic_callback();
173173
for those, to get access to the user_data use the 'user_data' member of the MQTT object
174174
passed as 1st argument.
175+
:param bool use_imprecise_time: on boards without time.monotonic_ns() one has to set
176+
this to True in order to operate correctly over more than 24 days or so
175177
176178
"""
177179

178-
# pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements, not-callable, invalid-name, no-member
180+
# pylint: disable=too-many-arguments,too-many-instance-attributes,too-many-statements,not-callable,invalid-name,no-member,too-many-locals
179181
def __init__(
180182
self,
181183
*,
@@ -193,13 +195,28 @@ def __init__(
193195
socket_timeout: int = 1,
194196
connect_retries: int = 5,
195197
user_data=None,
198+
use_imprecise_time: Optional[bool] = None,
196199
) -> None:
197200
self._socket_pool = socket_pool
198201
self._ssl_context = ssl_context
199202
self._sock = None
200203
self._backwards_compatible_sock = False
201204
self._use_binary_mode = use_binary_mode
202205

206+
self.use_monotonic_ns = False
207+
try:
208+
time.monotonic_ns()
209+
self.use_monotonic_ns = True
210+
except AttributeError:
211+
if use_imprecise_time:
212+
self.use_monotonic_ns = False
213+
else:
214+
raise MMQTTException( # pylint: disable=raise-missing-from
215+
"time.monotonic_ns() is not available. "
216+
"Will use imprecise time however only if the"
217+
"use_imprecise_time argument is set to True."
218+
)
219+
203220
if recv_timeout <= socket_timeout:
204221
raise MMQTTException(
205222
"recv_timeout must be strictly greater than socket_timeout"
@@ -251,9 +268,8 @@ def __init__(
251268
self.client_id = client_id
252269
else:
253270
# assign a unique client_id
254-
self.client_id = (
255-
f"cpy{randint(0, int(time.monotonic() * 100) % 1000)}{randint(0, 99)}"
256-
)
271+
time_int = int(self.get_monotonic_time() * 100) % 1000
272+
self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}"
257273
# generated client_id's enforce spec.'s length rules
258274
if len(self.client_id.encode("utf-8")) > 23 or not self.client_id:
259275
raise ValueError("MQTT Client ID must be between 1 and 23 bytes")
@@ -276,6 +292,17 @@ def __init__(
276292
self.on_subscribe = None
277293
self.on_unsubscribe = None
278294

295+
def get_monotonic_time(self) -> float:
296+
"""
297+
Provide monotonic time in seconds. Based on underlying implementation
298+
this might result in imprecise time, that will result in the library
299+
not being able to operate if running contiguously for more than 24 days or so.
300+
"""
301+
if self.use_monotonic_ns:
302+
return time.monotonic_ns() / 1000000000
303+
304+
return time.monotonic()
305+
279306
# pylint: disable=too-many-branches
280307
def _get_connect_socket(self, host: str, port: int, *, timeout: int = 1):
281308
"""Obtains a new socket and connects to a broker.
@@ -636,7 +663,7 @@ def _connect(
636663
self._send_str(self._username)
637664
self._send_str(self._password)
638665
self.logger.debug("Receiving CONNACK packet from broker")
639-
stamp = time.monotonic()
666+
stamp = self.get_monotonic_time()
640667
while True:
641668
op = self._wait_for_msg()
642669
if op == 32:
@@ -652,7 +679,7 @@ def _connect(
652679
return result
653680

654681
if op is None:
655-
if time.monotonic() - stamp > self._recv_timeout:
682+
if self.get_monotonic_time() - stamp > self._recv_timeout:
656683
raise MMQTTException(
657684
f"No data received from broker for {self._recv_timeout} seconds."
658685
)
@@ -681,13 +708,13 @@ def ping(self) -> list[int]:
681708
self.logger.debug("Sending PINGREQ")
682709
self._sock.send(MQTT_PINGREQ)
683710
ping_timeout = self.keep_alive
684-
stamp = time.monotonic()
711+
stamp = self.get_monotonic_time()
685712
rc, rcs = None, []
686713
while rc != MQTT_PINGRESP:
687714
rc = self._wait_for_msg()
688715
if rc:
689716
rcs.append(rc)
690-
if time.monotonic() - stamp > ping_timeout:
717+
if self.get_monotonic_time() - stamp > ping_timeout:
691718
raise MMQTTException("PINGRESP not returned from broker.")
692719
return rcs
693720

@@ -768,7 +795,7 @@ def publish(
768795
if qos == 0 and self.on_publish is not None:
769796
self.on_publish(self, self.user_data, topic, self._pid)
770797
if qos == 1:
771-
stamp = time.monotonic()
798+
stamp = self.get_monotonic_time()
772799
while True:
773800
op = self._wait_for_msg()
774801
if op == 0x40:
@@ -782,7 +809,7 @@ def publish(
782809
return
783810

784811
if op is None:
785-
if time.monotonic() - stamp > self._recv_timeout:
812+
if self.get_monotonic_time() - stamp > self._recv_timeout:
786813
raise MMQTTException(
787814
f"No data received from broker for {self._recv_timeout} seconds."
788815
)
@@ -834,11 +861,11 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
834861
for t, q in topics:
835862
self.logger.debug("SUBSCRIBING to topic %s with QoS %d", t, q)
836863
self._sock.send(packet)
837-
stamp = time.monotonic()
864+
stamp = self.get_monotonic_time()
838865
while True:
839866
op = self._wait_for_msg()
840867
if op is None:
841-
if time.monotonic() - stamp > self._recv_timeout:
868+
if self.get_monotonic_time() - stamp > self._recv_timeout:
842869
raise MMQTTException(
843870
f"No data received from broker for {self._recv_timeout} seconds."
844871
)
@@ -901,10 +928,10 @@ def unsubscribe(self, topic: str) -> None:
901928
self._sock.send(packet)
902929
self.logger.debug("Waiting for UNSUBACK...")
903930
while True:
904-
stamp = time.monotonic()
931+
stamp = self.get_monotonic_time()
905932
op = self._wait_for_msg()
906933
if op is None:
907-
if time.monotonic() - stamp > self._recv_timeout:
934+
if self.get_monotonic_time() - stamp > self._recv_timeout:
908935
raise MMQTTException(
909936
f"No data received from broker for {self._recv_timeout} seconds."
910937
)
@@ -998,8 +1025,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
9981025
self._connected()
9991026
self.logger.debug(f"waiting for messages for {timeout} seconds")
10001027
if self._timestamp == 0:
1001-
self._timestamp = time.monotonic()
1002-
current_time = time.monotonic()
1028+
self._timestamp = self.get_monotonic_time()
1029+
current_time = self.get_monotonic_time()
10031030
if current_time - self._timestamp >= self.keep_alive:
10041031
self._timestamp = 0
10051032
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
@@ -1009,14 +1036,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
10091036
rcs = self.ping()
10101037
return rcs
10111038

1012-
stamp = time.monotonic()
1039+
stamp = self.get_monotonic_time()
10131040
rcs = []
10141041

10151042
while True:
10161043
rc = self._wait_for_msg()
10171044
if rc is not None:
10181045
rcs.append(rc)
1019-
if time.monotonic() - stamp > timeout:
1046+
if self.get_monotonic_time() - stamp > timeout:
10201047
self.logger.debug(f"Loop timed out after {timeout} seconds")
10211048
break
10221049

@@ -1115,7 +1142,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11151142
:param int bufsize: number of bytes to receive
11161143
:return: byte array
11171144
"""
1118-
stamp = time.monotonic()
1145+
stamp = self.get_monotonic_time()
11191146
if not self._backwards_compatible_sock:
11201147
# CPython/Socketpool Impl.
11211148
rc = bytearray(bufsize)
@@ -1130,7 +1157,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11301157
recv_len = self._sock.recv_into(mv, to_read)
11311158
to_read -= recv_len
11321159
mv = mv[recv_len:]
1133-
if time.monotonic() - stamp > read_timeout:
1160+
if self.get_monotonic_time() - stamp > read_timeout:
11341161
raise MMQTTException(
11351162
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11361163
)
@@ -1150,7 +1177,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
11501177
recv = self._sock.recv(to_read)
11511178
to_read -= len(recv)
11521179
rc += recv
1153-
if time.monotonic() - stamp > read_timeout:
1180+
if self.get_monotonic_time() - stamp > read_timeout:
11541181
raise MMQTTException(
11551182
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
11561183
)

0 commit comments

Comments
 (0)