@@ -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