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