From b5d1b7612fc3ffcfc29a2b9d613a3f035b81909b Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Mon, 3 Jun 2024 17:55:24 +0200 Subject: [PATCH 1/7] Replace timeago Replace the timeago library with a simple function --- meshtastic/__init__.py | 2 +- meshtastic/mesh_interface.py | 26 +++++++++++++++++++++----- setup.py | 1 - 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/meshtastic/__init__.py b/meshtastic/__init__.py index a5075bc..313cb42 100644 --- a/meshtastic/__init__.py +++ b/meshtastic/__init__.py @@ -76,7 +76,7 @@ from typing import * import google.protobuf.json_format import serial # type: ignore[import-untyped] -import timeago # type: ignore[import-untyped] +from dotmap import DotMap # type: ignore[import-untyped] from google.protobuf.json_format import MessageToJson from pubsub import pub # type: ignore[import-untyped] from tabulate import tabulate diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index 5b3403c..bb04b74 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -14,7 +14,6 @@ from decimal import Decimal from typing import Any, Callable, Dict, List, Optional, Union import google.protobuf.json_format -import timeago # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped] from tabulate import tabulate @@ -158,11 +157,28 @@ class MeshInterface: # pylint: disable=R0902 def getTimeAgo(ts) -> Optional[str]: """Format how long ago have we heard from this node (aka timeago).""" - return ( - timeago.format(datetime.fromtimestamp(ts), datetime.now()) - if ts - else None + if ts is None: + return None + delta = datetime.now() - datetime.fromtimestamp(ts) + delta_secs = int(delta.total_seconds()) + if delta_secs < 0: + return None # not handling a timestamp from the future + intervals = ( + ("year", 60 * 60 * 24 * 365), + ("month", 60 * 60 * 24 * 30), + ("day", 60 * 60 * 24), + ("hour", 60 * 60), + ("min", 60), + ("sec", 1), ) + for name, interval_duration in intervals: + if delta_secs < interval_duration: + continue + x = delta_secs // interval_duration + plur = "s" if x > 1 else "" + return f"{x} {name}{plur} ago" + + return "now" rows: List[Dict[str, Any]] = [] if self.nodesByNum: diff --git a/setup.py b/setup.py index 7aaacf9..eed7c1c 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,6 @@ setup( "dotmap>=1.3.14", "pyqrcode>=1.2.1", "tabulate>=0.8.9", - "timeago>=1.0.15", "pyyaml", "bleak>=0.21.1", "packaging", From c34d08b0e53cd9c6e252d5e92b456f0566779ffc Mon Sep 17 00:00:00 2001 From: Federico Ceratto Date: Fri, 21 Jun 2024 10:28:45 +0200 Subject: [PATCH 2/7] Refactor timeago and add tests _timeago is not specialized for mesh interfaces so it is factored out into a private function --- meshtastic/mesh_interface.py | 40 +++++++++++++++---------- meshtastic/tests/test_mesh_interface.py | 13 +++++++- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/meshtastic/mesh_interface.py b/meshtastic/mesh_interface.py index bb04b74..66ce7f2 100644 --- a/meshtastic/mesh_interface.py +++ b/meshtastic/mesh_interface.py @@ -41,6 +41,29 @@ from meshtastic.util import ( ) +def _timeago(delta_secs: int) -> str: + """Convert a number of seconds in the past into a short, friendly string + e.g. "now", "30 sec ago", "1 hour ago" + Zero or negative intervals simply return "now" + """ + intervals = ( + ("year", 60 * 60 * 24 * 365), + ("month", 60 * 60 * 24 * 30), + ("day", 60 * 60 * 24), + ("hour", 60 * 60), + ("min", 60), + ("sec", 1), + ) + for name, interval_duration in intervals: + if delta_secs < interval_duration: + continue + x = delta_secs // interval_duration + plur = "s" if x > 1 else "" + return f"{x} {name}{plur} ago" + + return "now" + + class MeshInterface: # pylint: disable=R0902 """Interface class for meshtastic devices @@ -163,22 +186,7 @@ class MeshInterface: # pylint: disable=R0902 delta_secs = int(delta.total_seconds()) if delta_secs < 0: return None # not handling a timestamp from the future - intervals = ( - ("year", 60 * 60 * 24 * 365), - ("month", 60 * 60 * 24 * 30), - ("day", 60 * 60 * 24), - ("hour", 60 * 60), - ("min", 60), - ("sec", 1), - ) - for name, interval_duration in intervals: - if delta_secs < interval_duration: - continue - x = delta_secs // interval_duration - plur = "s" if x > 1 else "" - return f"{x} {name}{plur} ago" - - return "now" + return _timeago(delta_secs) rows: List[Dict[str, Any]] = [] if self.nodesByNum: diff --git a/meshtastic/tests/test_mesh_interface.py b/meshtastic/tests/test_mesh_interface.py index 037e0bf..22950e5 100644 --- a/meshtastic/tests/test_mesh_interface.py +++ b/meshtastic/tests/test_mesh_interface.py @@ -7,7 +7,7 @@ from unittest.mock import MagicMock, patch import pytest from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR -from ..mesh_interface import MeshInterface +from ..mesh_interface import MeshInterface, _timeago from ..node import Node # TODO @@ -684,3 +684,14 @@ def test_waitConnected_isConnected_timeout(capsys): out, err = capsys.readouterr() assert re.search(r"warn about something", err, re.MULTILINE) assert out == "" + + +@pytest.mark.unit +def test_timeago(): + assert _timeago(0) == "now" + assert _timeago(1) == "1 sec ago" + assert _timeago(15) == "15 secs ago" + assert _timeago(333) == "5 mins ago" + assert _timeago(99999) == "1 day ago" + assert _timeago(9999999) == "3 months ago" + assert _timeago(-999) == "now" From ccfb04720f95f5052409540f684e4f2eb96580e2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 19 Jun 2024 14:45:59 -0700 Subject: [PATCH 3/7] Add a whitelist of known meshtastic USB VIDs to use a default serial ports. Initially only RAK4631 and heltec tracker are listed --- meshtastic/util.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index 14f6a54..86de1e9 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -24,8 +24,14 @@ import serial.tools.list_ports # type: ignore[import-untyped] from meshtastic.supported_device import supported_devices from meshtastic.version import get_active_version -"""Some devices such as a seger jlink we never want to accidentally open""" -blacklistVids = dict.fromkeys([0x1366]) +"""Some devices such as a seger jlink or st-link we never want to accidentally open""" +blacklistVids = dict.fromkeys([0x1366, 0x0483]) + +"""Some devices are highly likely to be meshtastic. +0x239a RAK4631 +0x303a Heltec tracker""" +whitelistVids = dict.fromkeys([0x239a, 0x303a]) + def quoteBooleans(a_string): """Quote booleans @@ -130,19 +136,35 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]: Returns: list -- a list of device paths """ - l = list( + all_ports = serial.tools.list_ports.comports() + + # look for 'likely' meshtastic devices + ports = list( map( lambda port: port.device, filter( - lambda port: port.vid is not None and port.vid not in blacklistVids, - serial.tools.list_ports.comports(), + lambda port: port.vid is not None and port.vid in whitelistVids, + all_ports, ), ) ) - l.sort() + + # if no likely devices, just list everything not blacklisted + if len(ports) == 0: + ports = list( + map( + lambda port: port.device, + filter( + lambda port: port.vid is not None and port.vid not in blacklistVids, + all_ports, + ), + ) + ) + + ports.sort() if eliminate_duplicates: - l = eliminate_duplicate_port(l) - return l + ports = eliminate_duplicate_port(ports) + return ports class dotdict(dict): From 8456f36c6bd1e386571099e512232f9d789774fd Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Sun, 23 Jun 2024 17:18:04 -0700 Subject: [PATCH 4/7] add NordicSemi Power Profiler Kit 2 device to the USB blacklist --- meshtastic/util.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/meshtastic/util.py b/meshtastic/util.py index 86de1e9..3193802 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -24,8 +24,10 @@ import serial.tools.list_ports # type: ignore[import-untyped] from meshtastic.supported_device import supported_devices from meshtastic.version import get_active_version -"""Some devices such as a seger jlink or st-link we never want to accidentally open""" -blacklistVids = dict.fromkeys([0x1366, 0x0483]) +"""Some devices such as a seger jlink or st-link we never want to accidentally open +0x1915 NordicSemi (PPK2) +""" +blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915]) """Some devices are highly likely to be meshtastic. 0x239a RAK4631 From b30cde979caea2bb899a222bffe2b5a3c36a2b1d Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Tue, 25 Jun 2024 11:31:02 -0700 Subject: [PATCH 5/7] fix bitrot in an old sanity test - use correct namespace --- tests/hello_world.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/hello_world.py b/tests/hello_world.py index 8fd8f79..8c25f8f 100644 --- a/tests/hello_world.py +++ b/tests/hello_world.py @@ -1,9 +1,7 @@ -import time - -import meshtastic +import meshtastic.serial_interface interface = ( - meshtastic.SerialInterface() + meshtastic.serial_interface.SerialInterface() ) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0 interface.sendText("hello mesh") interface.close() From 9ab1b32bdb0dcc0b05ed2cae9483a880c6ac2e48 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Tue, 25 Jun 2024 18:09:20 -0700 Subject: [PATCH 6/7] make pylint happy with a docstring --- meshtastic/tests/test_mesh_interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meshtastic/tests/test_mesh_interface.py b/meshtastic/tests/test_mesh_interface.py index 22950e5..810138c 100644 --- a/meshtastic/tests/test_mesh_interface.py +++ b/meshtastic/tests/test_mesh_interface.py @@ -688,6 +688,7 @@ def test_waitConnected_isConnected_timeout(capsys): @pytest.mark.unit def test_timeago(): + """Test that the _timeago function returns sane values""" assert _timeago(0) == "now" assert _timeago(1) == "1 sec ago" assert _timeago(15) == "15 secs ago" From 267923fdc5a7418254150904af6dc6f8a1461e35 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Tue, 25 Jun 2024 18:14:07 -0700 Subject: [PATCH 7/7] Add hypothesis fuzzing test for _timeago --- meshtastic/tests/test_mesh_interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/meshtastic/tests/test_mesh_interface.py b/meshtastic/tests/test_mesh_interface.py index 810138c..5e8441c 100644 --- a/meshtastic/tests/test_mesh_interface.py +++ b/meshtastic/tests/test_mesh_interface.py @@ -5,6 +5,7 @@ import re from unittest.mock import MagicMock, patch import pytest +from hypothesis import given, strategies as st from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR from ..mesh_interface import MeshInterface, _timeago @@ -696,3 +697,9 @@ def test_timeago(): assert _timeago(99999) == "1 day ago" assert _timeago(9999999) == "3 months ago" assert _timeago(-999) == "now" + +@given(seconds=st.integers()) +def test_timeago_fuzz(seconds): + """Fuzz _timeago to ensure it works with any integer""" + val = _timeago(seconds) + assert re.match(r"(now|\d+ (secs?|mins?|hours?|days?|months?|years?))", val)