diff --git a/meshtastic/ble_interface.py b/meshtastic/ble_interface.py index 038cee53..8e60e7db 100644 --- a/meshtastic/ble_interface.py +++ b/meshtastic/ble_interface.py @@ -3,6 +3,7 @@ import asyncio import atexit import logging +import platform import struct import time from threading import Thread @@ -79,7 +80,8 @@ def __init__( # We MUST run atexit (if we can) because otherwise (at least on linux) the BLE device is not disconnected # and future connection attempts will fail. (BlueZ kinda sucks) # Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit - self._exit_handler = atexit.register(self.client.disconnect) + if platform.system() == "Linux": + self._exit_handler = atexit.register(self.client.disconnect) def from_num_handler(self, _, b): # pylint: disable=C0116 """Handle callbacks for fromnum notify. @@ -207,7 +209,8 @@ def _sendToRadioImpl(self, toRadio): self.should_read = True def close(self): - atexit.unregister(self._exit_handler) + if platform.system() == "Linux": + atexit.unregister(self._exit_handler) try: MeshInterface.close(self) except Exception as e: @@ -218,7 +221,7 @@ def close(self): self._receiveThread.join(timeout=2) # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect) self._receiveThread = None - if self.client: + if hasattr(self, "client"): self.client.disconnect() self.client.close() self.client = None diff --git a/meshtastic/tests/test_ble_interface.py b/meshtastic/tests/test_ble_interface.py new file mode 100644 index 00000000..4ed60ce3 --- /dev/null +++ b/meshtastic/tests/test_ble_interface.py @@ -0,0 +1,64 @@ +"""Meshtastic unit tests for ble_interface.py""" +import logging +import os +from unittest.mock import patch + +import pytest + +from meshtastic.ble_interface import BLEClient, BLEInterface + + +def test_ble_client_no_addr_logs_message(caplog): + """ + We want to see a debug message describing the error + if we try to initialize a BLEClient with no address. + """ + caplog.set_level(level=logging.DEBUG) + test_ble_client = BLEClient(address=None) + test_ble_client.close() + assert "No address provided - only discover method will work." in caplog.text + +def test_ble_interface_sanitize_address_returns_lower(): + """ + _sanitize_address should only return lower case letters + """ + assert BLEInterface._sanitize_address("HELLO") == "hello" + +def test_ble_interface_sanitize_address_returns_no_underscores(): + """ + _sanitize_address should only return strings without underscores + """ + assert BLEInterface._sanitize_address("hello_world") == "helloworld" + +def test_ble_interface_sanitize_address_returns_no_dash(): + """ + _sanitize_address should only return strings without dashes + """ + assert BLEInterface._sanitize_address("hello-world") == "helloworld" + +def test_ble_interface_sanitize_address_returns_no_colon(): + """ + _sanitize_address should only return strings without colons + """ + assert BLEInterface._sanitize_address("hello:world") == "helloworld" + +def test_linux_exit_handler(): + """ + Given a platform.system of Linux (or as I like to call it, Ganoo plus Linux), + we should register an exit handler. + """ + with patch("platform.system") as fake_platform: + fake_platform.return_value = "Linux" + test_interface = BLEInterface(address="") + assert test_interface._exit_handler is not None + + +@pytest.mark.skipif(os.environ.get("CI") == "true", reason="Bluetooth tests are not supported in CI environment") +def test_ble_interface_bogus_addr_exits_process(): + """ + If we initialize BLEInterface with a BT address that doesn't + exist, we should exit the process + """ + with pytest.raises(BLEInterface.BLEError) as exc: + BLEInterface(address="bogus") + assert "No Meshtastic BLE peripheral with identifier or address 'bogus' found" in exc.value.args[0] diff --git a/poetry.lock b/poetry.lock index 4c4b23d4..8dfc8bf8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "altgraph" @@ -4009,4 +4009,4 @@ tunnel = [] [metadata] lock-version = "2.0" python-versions = "^3.9,<3.13" -content-hash = "a6032933510dcce0d89660fb1548219dee51e3373a65cf4addcec1f2b93fbceb" +content-hash = "7f256b39b5a108cf14addd9803c903d2c18ad2febce53f648b54941a5ed8d2f3" diff --git a/pyproject.toml b/pyproject.toml index d089606f..1d1b3de7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ ppk2-api = "^0.9.2" pyarrow = "^16.1.0" platformdirs = "^4.2.2" print-color = "^0.4.6" +dbus-fast = "^2.22.1" [tool.poetry.group.dev.dependencies] hypothesis = "^6.103.2"