Skip to content

maintain time.monotonic precision by using adafruit_ticks #210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jul 5, 2024
Merged
Changes from 4 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
70f963b
maintain time.monotonic precision by using ns integer timestamps
kevin-tritz Mar 22, 2024
89bf045
formatted with Black
kevin-tritz Mar 22, 2024
639160a
added _ns suffix to vars and func
kevin-tritz Mar 22, 2024
9af0e3f
reverted func name back to get_monotonic_time to pass build check
kevin-tritz Mar 22, 2024
120d8a9
retain imprecise get_monotonic_time, add precision get_monotonic_time…
kevin-tritz Mar 25, 2024
5378f8f
reverted timestamps back to using ticks_ms from adafruit_ticks library
kevin-tritz Mar 25, 2024
51b0550
fix ping_timeout
kevin-tritz Mar 25, 2024
5b0c8cc
add adafruit_ticks to requirements.txt
kevin-tritz Mar 26, 2024
5ad6abb
revert
kevin-tritz Mar 26, 2024
2e3caae
Merge branch 'timestamp_ns' of https://github.com/kevin-tritz/Adafrui…
kevin-tritz Mar 26, 2024
bce70b2
add adafruit_ticks to requirements.txt
kevin-tritz Mar 26, 2024
92035c0
try lower case in requirements.txt
kevin-tritz Mar 26, 2024
1dd4652
ok, adafruit-circuitpython-ticks
kevin-tritz Mar 26, 2024
d5626ec
fix end of file error?
kevin-tritz Mar 26, 2024
128e3f0
purge last remnants of time.monotonic(), get rid of imprecise_time ar…
kevin-tritz May 6, 2024
830af78
remove test_loop dependence on mqtt.get_monotonic_time function
kevin-tritz May 6, 2024
82d6f9d
timestamp uses ticks_ms
kevin-tritz May 6, 2024
a1eec26
fix ticks_ms ref
kevin-tritz May 6, 2024
e873f43
modify ping_timeout test for 3 res
kevin-tritz May 6, 2024
804d7f7
black formatting
kevin-tritz May 6, 2024
b569ce7
Remove micropython from docs/conf.py
kevin-tritz May 7, 2024
89bd1e1
make loop ping timeout test more robust
kevin-tritz May 9, 2024
ee32e5e
cleaned up test_loop
kevin-tritz May 9, 2024
93b7b3e
fixed up ping_timeout
kevin-tritz May 12, 2024
4272252
ok, reverted all of the black reformats and just formatted minimqtt.py
kevin-tritz May 12, 2024
4486578
Merge branch 'main' into timestamp_ns
kevin-tritz Jun 4, 2024
1ffbe5e
fix for timeout test
FoamyGuy Jun 17, 2024
2d562af
tolerance value instead of rounding
FoamyGuy Jul 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 37 additions & 33 deletions adafruit_minimqtt/adafruit_minimqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

from .matcher import MQTTMatcher

__version__ = "0.0.0+auto.0"
__version__ = "7.6.3"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MiniMQTT.git"

# Client-specific variables
Expand Down Expand Up @@ -181,7 +181,7 @@ def __init__(
self._is_connected = False
self._msg_size_lim = MQTT_MSG_SZ_LIM
self._pid = 0
self._last_msg_sent_timestamp: float = 0
self._last_msg_sent_timestamp_ns: int = 0
self.logger = NullLogger()
"""An optional logging attribute that can be set with with a Logger
to enable debug logging."""
Expand Down Expand Up @@ -220,7 +220,7 @@ def __init__(
self.client_id = client_id
else:
# assign a unique client_id
time_int = int(self.get_monotonic_time() * 100) % 1000
time_int = (self.get_monotonic_time() % 10000000000) // 10000000
self.client_id = f"cpy{randint(0, time_int)}{randint(0, 99)}"
# generated client_id's enforce spec.'s length rules
if len(self.client_id.encode("utf-8")) > 23 or not self.client_id:
Expand All @@ -246,14 +246,22 @@ def __init__(

def get_monotonic_time(self) -> float:
"""
Provide monotonic time in seconds. Based on underlying implementation
Provide monotonic time in nanoseconds. Based on underlying implementation
this might result in imprecise time, that will result in the library
not being able to operate if running contiguously for more than 24 days or so.
Keeping timestamps in nanosecond ints from monotonic_ns should preserve precision.
"""
if self.use_monotonic_ns:
return time.monotonic_ns() / 1000000000
return time.monotonic_ns()

return time.monotonic()
return int(time.monotonic() * 1000000000)

def diff_ns(self, stamp_ns):
"""
Taking timestamp differences using nanosecond ints before dividing
should maintain precision.
"""
return (self.get_monotonic_time() - stamp_ns) / 1000000000

def __enter__(self):
return self
Expand Down Expand Up @@ -526,9 +534,9 @@ def _connect(
if self._username is not None:
self._send_str(self._username)
self._send_str(self._password)
self._last_msg_sent_timestamp = self.get_monotonic_time()
self._last_msg_sent_timestamp_ns = self.get_monotonic_time()
self.logger.debug("Receiving CONNACK packet from broker")
stamp = self.get_monotonic_time()
stamp_ns = self.get_monotonic_time()
while True:
op = self._wait_for_msg()
if op == 32:
Expand All @@ -544,7 +552,7 @@ def _connect(
return result

if op is None:
if self.get_monotonic_time() - stamp > self._recv_timeout:
if self.diff_ns(stamp_ns) > self._recv_timeout:
raise MMQTTException(
f"No data received from broker for {self._recv_timeout} seconds."
)
Expand Down Expand Up @@ -581,7 +589,7 @@ def disconnect(self) -> None:
self._connection_manager.close_socket(self._sock)
self._is_connected = False
self._subscribed_topics = []
self._last_msg_sent_timestamp = 0
self._last_msg_sent_timestamp_ns = 0
if self.on_disconnect is not None:
self.on_disconnect(self, self.user_data, 0)

Expand All @@ -594,14 +602,14 @@ def ping(self) -> list[int]:
self.logger.debug("Sending PINGREQ")
self._sock.send(MQTT_PINGREQ)
ping_timeout = self.keep_alive
stamp = self.get_monotonic_time()
self._last_msg_sent_timestamp = stamp
stamp_ns = self.get_monotonic_time()
self._last_msg_sent_timestamp_ns = stamp_ns
rc, rcs = None, []
while rc != MQTT_PINGRESP:
rc = self._wait_for_msg()
if rc:
rcs.append(rc)
if self.get_monotonic_time() - stamp > ping_timeout:
if self.diff_ns(stamp_ns) > ping_timeout:
raise MMQTTException("PINGRESP not returned from broker.")
return rcs

Expand Down Expand Up @@ -670,11 +678,11 @@ def publish(
self._sock.send(pub_hdr_fixed)
self._sock.send(pub_hdr_var)
self._sock.send(msg)
self._last_msg_sent_timestamp = self.get_monotonic_time()
self._last_msg_sent_timestamp_ns = self.get_monotonic_time()
if qos == 0 and self.on_publish is not None:
self.on_publish(self, self.user_data, topic, self._pid)
if qos == 1:
stamp = self.get_monotonic_time()
stamp_ns = self.get_monotonic_time()
while True:
op = self._wait_for_msg()
if op == 0x40:
Expand All @@ -688,7 +696,7 @@ def publish(
return

if op is None:
if self.get_monotonic_time() - stamp > self._recv_timeout:
if self.diff_ns(stamp_ns) > self._recv_timeout:
raise MMQTTException(
f"No data received from broker for {self._recv_timeout} seconds."
)
Expand Down Expand Up @@ -747,12 +755,12 @@ def subscribe(self, topic: Optional[Union[tuple, str, list]], qos: int = 0) -> N
self.logger.debug(f"SUBSCRIBING to topic {t} with QoS {q}")
self.logger.debug(f"payload: {payload}")
self._sock.send(payload)
stamp = self.get_monotonic_time()
self._last_msg_sent_timestamp = stamp
stamp_ns = self.get_monotonic_time()
self._last_msg_sent_timestamp_ns = stamp_ns
while True:
op = self._wait_for_msg()
if op is None:
if self.get_monotonic_time() - stamp > self._recv_timeout:
if self.diff_ns(stamp_ns) > self._recv_timeout:
raise MMQTTException(
f"No data received from broker for {self._recv_timeout} seconds."
)
Expand Down Expand Up @@ -824,13 +832,13 @@ def unsubscribe(self, topic: Optional[Union[str, list]]) -> None:
for t in topics:
self.logger.debug(f"UNSUBSCRIBING from topic {t}")
self._sock.send(payload)
self._last_msg_sent_timestamp = self.get_monotonic_time()
self._last_msg_sent_timestamp_ns = self.get_monotonic_time()
self.logger.debug("Waiting for UNSUBACK...")
while True:
stamp = self.get_monotonic_time()
stamp_ns = self.get_monotonic_time()
op = self._wait_for_msg()
if op is None:
if self.get_monotonic_time() - stamp > self._recv_timeout:
if self.diff_ns(stamp_ns) > self._recv_timeout:
raise MMQTTException(
f"No data received from broker for {self._recv_timeout} seconds."
)
Expand Down Expand Up @@ -930,37 +938,33 @@ def loop(self, timeout: float = 0) -> Optional[list[int]]:
self._connected()
self.logger.debug(f"waiting for messages for {timeout} seconds")

stamp = self.get_monotonic_time()
stamp_ns = self.get_monotonic_time()
rcs = []

while True:
if (
self.get_monotonic_time() - self._last_msg_sent_timestamp
>= self.keep_alive
):
if self.diff_ns(self._last_msg_sent_timestamp_ns) >= self.keep_alive:
# Handle KeepAlive by expecting a PINGREQ/PINGRESP from the server
self.logger.debug(
"KeepAlive period elapsed - requesting a PINGRESP from the server..."
)
rcs.extend(self.ping())
# ping() itself contains a _wait_for_msg() loop which might have taken a while,
# so check here as well.
if self.get_monotonic_time() - stamp > timeout:
if self.diff_ns(stamp_ns) > timeout:
self.logger.debug(f"Loop timed out after {timeout} seconds")
break

rc = self._wait_for_msg()
if rc is not None:
rcs.append(rc)
if self.get_monotonic_time() - stamp > timeout:
if self.diff_ns(stamp_ns) > timeout:
self.logger.debug(f"Loop timed out after {timeout} seconds")
break

return rcs if rcs else None

def _wait_for_msg(self, timeout: Optional[float] = None) -> Optional[int]:
# pylint: disable = too-many-return-statements

"""Reads and processes network events.
Return the packet type or None if there is nothing to be received.

Expand Down Expand Up @@ -1059,7 +1063,7 @@ def _sock_exact_recv(
:param float timeout: timeout, in seconds. Defaults to keep_alive
:return: byte array
"""
stamp = self.get_monotonic_time()
stamp_ns = self.get_monotonic_time()
if not self._backwards_compatible_sock:
# CPython/Socketpool Impl.
rc = bytearray(bufsize)
Expand All @@ -1074,7 +1078,7 @@ def _sock_exact_recv(
recv_len = self._sock.recv_into(mv, to_read)
to_read -= recv_len
mv = mv[recv_len:]
if self.get_monotonic_time() - stamp > read_timeout:
if self.diff_ns(stamp_ns) > read_timeout:
raise MMQTTException(
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
)
Expand All @@ -1094,7 +1098,7 @@ def _sock_exact_recv(
recv = self._sock.recv(to_read)
to_read -= len(recv)
rc += recv
if self.get_monotonic_time() - stamp > read_timeout:
if self.diff_ns(stamp_ns) > read_timeout:
raise MMQTTException(
f"Unable to receive {to_read} bytes within {read_timeout} seconds."
)
Expand Down