Skip to content

Commit a24384c

Browse files
committed
Merge remote-tracking branch 'root/ble-logging' into pr-fixbluetooth
2 parents adbfb32 + 897adfb commit a24384c

9 files changed

Lines changed: 145 additions & 59 deletions

File tree

.github/workflows/release.yml

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
- name: Get version
4242
id: get_version
4343
run: >-
44-
poetry version
44+
poetry version --short | sed 's/^/::set-output name=version::/'
4545
4646
- name: Create GitHub release
4747
uses: actions/create-release@v1
@@ -152,31 +152,31 @@ jobs:
152152
asset_name: readme.txt
153153
asset_content_type: text/plain
154154

155-
build-and-publish-windows:
156-
runs-on: windows-latest
157-
needs: release_create
158-
steps:
159-
- name: Checkout
160-
uses: actions/checkout@v4
161-
with:
162-
ref: ${{ needs.release_create.outputs.new_sha }}
155+
# build-and-publish-windows:
156+
# runs-on: windows-latest
157+
# needs: release_create
158+
# steps:
159+
# - name: Checkout
160+
# uses: actions/checkout@v4
161+
# with:
162+
# ref: ${{ needs.release_create.outputs.new_sha }}
163163

164-
- name: Set up Python 3.9
165-
uses: actions/setup-python@v5
166-
with:
167-
python-version: "3.9"
164+
# - name: Set up Python 3.9
165+
# uses: actions/setup-python@v5
166+
# with:
167+
# python-version: "3.9"
168168

169-
- name: Build
170-
run: |
171-
pip install poetry
172-
bin/build-bin.sh
169+
# - name: Build
170+
# run: |
171+
# pip install poetry
172+
# bin/build-bin.sh
173173

174-
- name: Add windows to release
175-
uses: actions/upload-release-asset@v1
176-
env:
177-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178-
with:
179-
upload_url: ${{ needs.release_create.outputs.upload_url }}
180-
asset_path: dist/meshtastic.exe
181-
asset_name: meshtastic_windows
182-
asset_content_type: application/zip
174+
# - name: Add windows to release
175+
# uses: actions/upload-release-asset@v1
176+
# env:
177+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
178+
# with:
179+
# upload_url: ${{ needs.release_create.outputs.upload_url }}
180+
# asset_path: dist/meshtastic.exe
181+
# asset_name: meshtastic_windows
182+
# asset_content_type: application/zip

meshtastic/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect
7676

7777
import google.protobuf.json_format
7878
import serial # type: ignore[import-untyped]
79-
import timeago # type: ignore[import-untyped]
79+
from dotmap import DotMap # type: ignore[import-untyped]
8080
from google.protobuf.json_format import MessageToJson
8181
from pubsub import pub # type: ignore[import-untyped]
8282
from tabulate import tabulate

meshtastic/ble_interface.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import time
77
from threading import Event, Thread
88
from typing import Optional
9+
from print_color import print
910

1011
from bleak import BleakClient, BleakScanner, BLEDevice
1112

@@ -16,6 +17,8 @@
1617
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
1718
FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
1819
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
20+
LOGRADIO_UUID = "6c6fd238-78fa-436b-aacf-15c5be1ef2e2"
21+
1922

2023

2124
class BLEInterface(MeshInterface):
@@ -75,12 +78,28 @@ def __init__(
7578

7679
logging.debug("Register FROMNUM notify callback")
7780
self.client.start_notify(FROMNUM_UUID, self.from_num_handler)
81+
self.client.start_notify(LOGRADIO_UUID, self.log_radio_handler)
7882

7983
async def from_num_handler(self, _, b): # pylint: disable=C0116
8084
from_num = struct.unpack("<I", bytes(b))[0]
8185
logging.debug(f"FROMNUM notify: {from_num}")
8286
self.should_read = True
8387

88+
async def log_radio_handler(self, _, b): # pylint: disable=C0116
89+
log_radio = b.decode('utf-8').replace('\n', '')
90+
if log_radio.startswith("DEBUG"):
91+
print(log_radio, color="cyan", end=None)
92+
elif log_radio.startswith("INFO"):
93+
print(log_radio, color="white", end=None)
94+
elif log_radio.startswith("WARN"):
95+
print(log_radio, color="yellow", end=None)
96+
elif log_radio.startswith("ERROR"):
97+
print(log_radio, color="red", end=None)
98+
else:
99+
print(log_radio, end=None)
100+
101+
self.should_read = False
102+
84103
@staticmethod
85104
def scan() -> list[BLEDevice]:
86105
"""Scan for available BLE devices."""

meshtastic/mesh_interface.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from typing import Any, Callable, Dict, List, Optional, Union
1515

1616
import google.protobuf.json_format
17-
import timeago # type: ignore[import-untyped]
1817
from pubsub import pub # type: ignore[import-untyped]
1918
from tabulate import tabulate
2019

@@ -42,6 +41,29 @@
4241
)
4342

4443

44+
def _timeago(delta_secs: int) -> str:
45+
"""Convert a number of seconds in the past into a short, friendly string
46+
e.g. "now", "30 sec ago", "1 hour ago"
47+
Zero or negative intervals simply return "now"
48+
"""
49+
intervals = (
50+
("year", 60 * 60 * 24 * 365),
51+
("month", 60 * 60 * 24 * 30),
52+
("day", 60 * 60 * 24),
53+
("hour", 60 * 60),
54+
("min", 60),
55+
("sec", 1),
56+
)
57+
for name, interval_duration in intervals:
58+
if delta_secs < interval_duration:
59+
continue
60+
x = delta_secs // interval_duration
61+
plur = "s" if x > 1 else ""
62+
return f"{x} {name}{plur} ago"
63+
64+
return "now"
65+
66+
4567
class MeshInterface: # pylint: disable=R0902
4668
"""Interface class for meshtastic devices
4769
@@ -158,11 +180,13 @@ def getLH(ts) -> Optional[str]:
158180

159181
def getTimeAgo(ts) -> Optional[str]:
160182
"""Format how long ago have we heard from this node (aka timeago)."""
161-
return (
162-
timeago.format(datetime.fromtimestamp(ts), datetime.now())
163-
if ts
164-
else None
165-
)
183+
if ts is None:
184+
return None
185+
delta = datetime.now() - datetime.fromtimestamp(ts)
186+
delta_secs = int(delta.total_seconds())
187+
if delta_secs < 0:
188+
return None # not handling a timestamp from the future
189+
return _timeago(delta_secs)
166190

167191
rows: List[Dict[str, Any]] = []
168192
if self.nodesByNum:

meshtastic/tests/test_mesh_interface.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from unittest.mock import MagicMock, patch
66

77
import pytest
8+
from hypothesis import given, strategies as st
89

910
from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR
10-
from ..mesh_interface import MeshInterface
11+
from ..mesh_interface import MeshInterface, _timeago
1112
from ..node import Node
1213

1314
# TODO
@@ -684,3 +685,21 @@ def test_waitConnected_isConnected_timeout(capsys):
684685
out, err = capsys.readouterr()
685686
assert re.search(r"warn about something", err, re.MULTILINE)
686687
assert out == ""
688+
689+
690+
@pytest.mark.unit
691+
def test_timeago():
692+
"""Test that the _timeago function returns sane values"""
693+
assert _timeago(0) == "now"
694+
assert _timeago(1) == "1 sec ago"
695+
assert _timeago(15) == "15 secs ago"
696+
assert _timeago(333) == "5 mins ago"
697+
assert _timeago(99999) == "1 day ago"
698+
assert _timeago(9999999) == "3 months ago"
699+
assert _timeago(-999) == "now"
700+
701+
@given(seconds=st.integers())
702+
def test_timeago_fuzz(seconds):
703+
"""Fuzz _timeago to ensure it works with any integer"""
704+
val = _timeago(seconds)
705+
assert re.match(r"(now|\d+ (secs?|mins?|hours?|days?|months?|years?))", val)

meshtastic/util.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,16 @@
2424
from meshtastic.supported_device import supported_devices
2525
from meshtastic.version import get_active_version
2626

27-
"""Some devices such as a seger jlink we never want to accidentally open"""
28-
blacklistVids = dict.fromkeys([0x1366])
27+
"""Some devices such as a seger jlink or st-link we never want to accidentally open
28+
0x1915 NordicSemi (PPK2)
29+
"""
30+
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915])
31+
32+
"""Some devices are highly likely to be meshtastic.
33+
0x239a RAK4631
34+
0x303a Heltec tracker"""
35+
whitelistVids = dict.fromkeys([0x239a, 0x303a])
36+
2937

3038
def quoteBooleans(a_string):
3139
"""Quote booleans
@@ -130,19 +138,35 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]:
130138
Returns:
131139
list -- a list of device paths
132140
"""
133-
l = list(
141+
all_ports = serial.tools.list_ports.comports()
142+
143+
# look for 'likely' meshtastic devices
144+
ports = list(
134145
map(
135146
lambda port: port.device,
136147
filter(
137-
lambda port: port.vid is not None and port.vid not in blacklistVids,
138-
serial.tools.list_ports.comports(),
148+
lambda port: port.vid is not None and port.vid in whitelistVids,
149+
all_ports,
139150
),
140151
)
141152
)
142-
l.sort()
153+
154+
# if no likely devices, just list everything not blacklisted
155+
if len(ports) == 0:
156+
ports = list(
157+
map(
158+
lambda port: port.device,
159+
filter(
160+
lambda port: port.vid is not None and port.vid not in blacklistVids,
161+
all_ports,
162+
),
163+
)
164+
)
165+
166+
ports.sort()
143167
if eliminate_duplicates:
144-
l = eliminate_duplicate_port(l)
145-
return l
168+
ports = eliminate_duplicate_port(ports)
169+
return ports
146170

147171

148172
class dotdict(dict):

poetry.lock

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "meshtastic"
3-
version = "2.3.11"
3+
version = "2.3.12"
44
description = "Python API & client shell for talking to Meshtastic devices"
55
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
66
license = "GPL-3.0-only"
@@ -14,14 +14,14 @@ dotmap = "^1.3.30"
1414
pexpect = "^4.9.0"
1515
pyqrcode = "^1.2.1"
1616
tabulate = "^0.9.0"
17-
timeago = "^1.0.16"
1817
webencodings = "^0.5.1"
1918
requests = "^2.31.0"
2019
pyparsing = "^3.1.2"
2120
pyyaml = "^6.0.1"
2221
pypubsub = "^4.0.3"
2322
bleak = "^0.21.1"
2423
packaging = "^24.0"
24+
print-color = "^0.4.6"
2525

2626
[tool.poetry.group.dev.dependencies]
2727
hypothesis = "^6.103.2"

tests/hello_world.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import time
2-
3-
import meshtastic
1+
import meshtastic.serial_interface
42

53
interface = (
6-
meshtastic.SerialInterface()
4+
meshtastic.serial_interface.SerialInterface()
75
) # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
86
interface.sendText("hello mesh")
97
interface.close()

0 commit comments

Comments
 (0)