@@ -172,10 +172,12 @@ class MQTT:
172
172
This works with all callbacks but the "on_message" and those added via add_topic_callback();
173
173
for those, to get access to the user_data use the 'user_data' member of the MQTT object
174
174
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
175
177
176
178
"""
177
179
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
179
181
def __init__ (
180
182
self ,
181
183
* ,
@@ -193,13 +195,28 @@ def __init__(
193
195
socket_timeout : int = 1 ,
194
196
connect_retries : int = 5 ,
195
197
user_data = None ,
198
+ use_imprecise_time : Optional [bool ] = None ,
196
199
) -> None :
197
200
self ._socket_pool = socket_pool
198
201
self ._ssl_context = ssl_context
199
202
self ._sock = None
200
203
self ._backwards_compatible_sock = False
201
204
self ._use_binary_mode = use_binary_mode
202
205
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
+
203
220
if recv_timeout <= socket_timeout :
204
221
raise MMQTTException (
205
222
"recv_timeout must be strictly greater than socket_timeout"
@@ -251,9 +268,8 @@ def __init__(
251
268
self .client_id = client_id
252
269
else :
253
270
# 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 )} "
257
273
# generated client_id's enforce spec.'s length rules
258
274
if len (self .client_id .encode ("utf-8" )) > 23 or not self .client_id :
259
275
raise ValueError ("MQTT Client ID must be between 1 and 23 bytes" )
@@ -276,6 +292,17 @@ def __init__(
276
292
self .on_subscribe = None
277
293
self .on_unsubscribe = None
278
294
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
+
279
306
# pylint: disable=too-many-branches
280
307
def _get_connect_socket (self , host : str , port : int , * , timeout : int = 1 ):
281
308
"""Obtains a new socket and connects to a broker.
@@ -636,7 +663,7 @@ def _connect(
636
663
self ._send_str (self ._username )
637
664
self ._send_str (self ._password )
638
665
self .logger .debug ("Receiving CONNACK packet from broker" )
639
- stamp = time . monotonic ()
666
+ stamp = self . get_monotonic_time ()
640
667
while True :
641
668
op = self ._wait_for_msg ()
642
669
if op == 32 :
@@ -652,7 +679,7 @@ def _connect(
652
679
return result
653
680
654
681
if op is None :
655
- if time . monotonic () - stamp > self ._recv_timeout :
682
+ if self . get_monotonic_time () - stamp > self ._recv_timeout :
656
683
raise MMQTTException (
657
684
f"No data received from broker for { self ._recv_timeout } seconds."
658
685
)
@@ -681,13 +708,13 @@ def ping(self) -> list[int]:
681
708
self .logger .debug ("Sending PINGREQ" )
682
709
self ._sock .send (MQTT_PINGREQ )
683
710
ping_timeout = self .keep_alive
684
- stamp = time . monotonic ()
711
+ stamp = self . get_monotonic_time ()
685
712
rc , rcs = None , []
686
713
while rc != MQTT_PINGRESP :
687
714
rc = self ._wait_for_msg ()
688
715
if rc :
689
716
rcs .append (rc )
690
- if time . monotonic () - stamp > ping_timeout :
717
+ if self . get_monotonic_time () - stamp > ping_timeout :
691
718
raise MMQTTException ("PINGRESP not returned from broker." )
692
719
return rcs
693
720
@@ -768,7 +795,7 @@ def publish(
768
795
if qos == 0 and self .on_publish is not None :
769
796
self .on_publish (self , self .user_data , topic , self ._pid )
770
797
if qos == 1 :
771
- stamp = time . monotonic ()
798
+ stamp = self . get_monotonic_time ()
772
799
while True :
773
800
op = self ._wait_for_msg ()
774
801
if op == 0x40 :
@@ -782,7 +809,7 @@ def publish(
782
809
return
783
810
784
811
if op is None :
785
- if time . monotonic () - stamp > self ._recv_timeout :
812
+ if self . get_monotonic_time () - stamp > self ._recv_timeout :
786
813
raise MMQTTException (
787
814
f"No data received from broker for { self ._recv_timeout } seconds."
788
815
)
@@ -834,11 +861,11 @@ def subscribe(self, topic: str, qos: int = 0) -> None:
834
861
for t , q in topics :
835
862
self .logger .debug ("SUBSCRIBING to topic %s with QoS %d" , t , q )
836
863
self ._sock .send (packet )
837
- stamp = time . monotonic ()
864
+ stamp = self . get_monotonic_time ()
838
865
while True :
839
866
op = self ._wait_for_msg ()
840
867
if op is None :
841
- if time . monotonic () - stamp > self ._recv_timeout :
868
+ if self . get_monotonic_time () - stamp > self ._recv_timeout :
842
869
raise MMQTTException (
843
870
f"No data received from broker for { self ._recv_timeout } seconds."
844
871
)
@@ -901,10 +928,10 @@ def unsubscribe(self, topic: str) -> None:
901
928
self ._sock .send (packet )
902
929
self .logger .debug ("Waiting for UNSUBACK..." )
903
930
while True :
904
- stamp = time . monotonic ()
931
+ stamp = self . get_monotonic_time ()
905
932
op = self ._wait_for_msg ()
906
933
if op is None :
907
- if time . monotonic () - stamp > self ._recv_timeout :
934
+ if self . get_monotonic_time () - stamp > self ._recv_timeout :
908
935
raise MMQTTException (
909
936
f"No data received from broker for { self ._recv_timeout } seconds."
910
937
)
@@ -998,8 +1025,8 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
998
1025
self ._connected ()
999
1026
self .logger .debug (f"waiting for messages for { timeout } seconds" )
1000
1027
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 ()
1003
1030
if current_time - self ._timestamp >= self .keep_alive :
1004
1031
self ._timestamp = 0
1005
1032
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
@@ -1009,14 +1036,14 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
1009
1036
rcs = self .ping ()
1010
1037
return rcs
1011
1038
1012
- stamp = time . monotonic ()
1039
+ stamp = self . get_monotonic_time ()
1013
1040
rcs = []
1014
1041
1015
1042
while True :
1016
1043
rc = self ._wait_for_msg ()
1017
1044
if rc is not None :
1018
1045
rcs .append (rc )
1019
- if time . monotonic () - stamp > timeout :
1046
+ if self . get_monotonic_time () - stamp > timeout :
1020
1047
self .logger .debug (f"Loop timed out after { timeout } seconds" )
1021
1048
break
1022
1049
@@ -1115,7 +1142,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
1115
1142
:param int bufsize: number of bytes to receive
1116
1143
:return: byte array
1117
1144
"""
1118
- stamp = time . monotonic ()
1145
+ stamp = self . get_monotonic_time ()
1119
1146
if not self ._backwards_compatible_sock :
1120
1147
# CPython/Socketpool Impl.
1121
1148
rc = bytearray (bufsize )
@@ -1130,7 +1157,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
1130
1157
recv_len = self ._sock .recv_into (mv , to_read )
1131
1158
to_read -= recv_len
1132
1159
mv = mv [recv_len :]
1133
- if time . monotonic () - stamp > read_timeout :
1160
+ if self . get_monotonic_time () - stamp > read_timeout :
1134
1161
raise MMQTTException (
1135
1162
f"Unable to receive { to_read } bytes within { read_timeout } seconds."
1136
1163
)
@@ -1150,7 +1177,7 @@ def _sock_exact_recv(self, bufsize: int) -> bytearray:
1150
1177
recv = self ._sock .recv (to_read )
1151
1178
to_read -= len (recv )
1152
1179
rc += recv
1153
- if time . monotonic () - stamp > read_timeout :
1180
+ if self . get_monotonic_time () - stamp > read_timeout :
1154
1181
raise MMQTTException (
1155
1182
f"Unable to receive { to_read } bytes within { read_timeout } seconds."
1156
1183
)
0 commit comments