Compare commits

..

8 Commits

Author SHA1 Message Date
Ben Meadors
658ecb60dc v2.2.21 protos 2024-02-01 08:23:02 -06:00
Ben Meadors
129f68ec14 Merge pull request #483 from bmidgley/brad/longer-timeout
Extend timeout for slow devices like pi zero
2024-01-28 07:51:46 -06:00
Brad Midgley
281ff788f3 Extend timeout for slow devices like pi zero 2024-01-28 06:35:15 -07:00
github-actions
70069323fc bump version 2024-01-16 17:12:28 +00:00
Ben Meadors
4721bc5adf Merge pull request #473 from wnagele/BLE_support
BLE Support
2024-01-16 11:11:50 -06:00
Ben Meadors
bc67546019 V2.2.18 protos 2024-01-16 10:39:50 -06:00
github-actions
dc35ffa12e bump version 2024-01-16 16:38:56 +00:00
Wolfgang Nagele
0a8a193081 BLE Support 2024-01-15 21:33:53 +01:00
14 changed files with 312 additions and 172 deletions

2
.vscode/launch.json vendored
View File

@@ -10,7 +10,7 @@
"request": "launch",
"module": "meshtastic",
"justMyCode": false,
"args": ["--debug", "--ble", "--device", "24:62:AB:DD:DF:3A"]
"args": ["--debug", "--ble", "24:62:AB:DD:DF:3A"]
},
{
"name": "meshtastic admin",

View File

@@ -5,7 +5,6 @@ Basic functionality is complete now.
## Eventual tasks
- Improve documentation on properties/fields
- change back to Bleak for BLE support - now that they fixed https://github.com/hbldh/bleak/issues/139#event-3499535304
- include more examples: textchat.py, replymessage.py all as one little demo
- possibly use tk to make a multiwindow test console: https://stackoverflow.com/questions/12351786/how-to-redirect-print-statements-to-tkinter-text-widget
@@ -17,11 +16,8 @@ Basic functionality is complete now.
## Bluetooth support
(Pre-alpha level feature - you probably don't want this one yet)
- This library supports connecting to Meshtastic devices over either USB (serial) or Bluetooth. Before connecting to the device you must [pair](https://docs.ubuntu.com/core/en/stacks/bluetooth/bluez/docs/reference/pairing/outbound.html) your PC with it.
- We use the pip3 install "pygatt[GATTTOOL]"
- ./bin/run.sh --debug --ble --device 24:62:AB:DD:DF:3A
- ./bin/run.sh --ble-scan # To look for Meshtastic devices
- ./bin/run.sh --ble 24:62:AB:DD:DF:3A --info
## Done

View File

@@ -945,7 +945,17 @@ def common():
our_globals.set_logfile(logfile)
subscribe()
if args.ble:
if args.ble_scan:
logging.debug("BLE scan starting")
client = BLEInterface(None, debugOut=logfile, noProto=args.noproto)
try:
for x in client.scan():
print(f"Found: name='{x[1].local_name}' address='{x[0].address}'")
finally:
client.close()
meshtastic.util.our_exit("BLE scan finished", 0)
return
elif args.ble:
client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto)
elif args.host:
try:
@@ -1310,9 +1320,14 @@ def initParser():
parser.add_argument(
"--ble",
help="BLE mac address to connect to (BLE is not yet supported for this tool)",
help="BLE device address or name to connect to",
default=None,
)
parser.add_argument(
"--ble-scan",
help="Scan for Meshtastic BLE devices",
action="store_true",
)
parser.add_argument(
"--noproto",

View File

@@ -19,7 +19,7 @@ from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/admin.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\x1a\"meshtastic/connection_status.proto\x1a\x1bmeshtastic/deviceonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1emeshtastic/module_config.proto\"\x83\x0f\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x05.UserH\x00\x12\x36\n\x12get_config_request\x18\x05 \x01(\x0e\x32\x18.AdminMessage.ConfigTypeH\x00\x12&\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x07.ConfigH\x00\x12\x43\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x1e.AdminMessage.ModuleConfigTypeH\x00\x12\x33\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32\r.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12\x37\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32\x0f.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12H\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32\x17.DeviceConnectionStatusH\x00\x12&\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\x0e.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12Q\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x1f.NodeRemoteHardwarePinsResponseH\x00\x12 \n\x16\x65nter_dfu_mode_request\x18\x15 \x01(\x08H\x00\x12\x1a\n\tset_owner\x18 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18! \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\nset_config\x18\" \x01(\x0b\x32\x07.ConfigH\x00\x12*\n\x11set_module_config\x18# \x01(\x0b\x32\r.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1b\n\x11remove_by_nodenum\x18& \x01(\rH\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xbb\x02\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x12\x17\n\x13NEIGHBORINFO_CONFIG\x10\t\x12\x1a\n\x16\x41MBIENTLIGHTING_CONFIG\x10\n\x12\x1a\n\x16\x44\x45TECTIONSENSOR_CONFIG\x10\x0b\x12\x15\n\x11PAXCOUNTER_CONFIG\x10\x0c\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"[\n\x1eNodeRemoteHardwarePinsResponse\x12\x39\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32\x16.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/admin.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\x1a\"meshtastic/connection_status.proto\x1a\x1bmeshtastic/deviceonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1emeshtastic/module_config.proto\"\xa2\x0f\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x05.UserH\x00\x12\x36\n\x12get_config_request\x18\x05 \x01(\x0e\x32\x18.AdminMessage.ConfigTypeH\x00\x12&\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x07.ConfigH\x00\x12\x43\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x1e.AdminMessage.ModuleConfigTypeH\x00\x12\x33\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32\r.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12\x37\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32\x0f.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12H\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32\x17.DeviceConnectionStatusH\x00\x12&\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\x0e.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12Q\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x1f.NodeRemoteHardwarePinsResponseH\x00\x12 \n\x16\x65nter_dfu_mode_request\x18\x15 \x01(\x08H\x00\x12\x1d\n\x13\x64\x65lete_file_request\x18\x16 \x01(\tH\x00\x12\x1a\n\tset_owner\x18 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18! \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\nset_config\x18\" \x01(\x0b\x32\x07.ConfigH\x00\x12*\n\x11set_module_config\x18# \x01(\x0b\x32\r.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1b\n\x11remove_by_nodenum\x18& \x01(\rH\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xbb\x02\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x12\x17\n\x13NEIGHBORINFO_CONFIG\x10\t\x12\x1a\n\x16\x41MBIENTLIGHTING_CONFIG\x10\n\x12\x1a\n\x16\x44\x45TECTIONSENSOR_CONFIG\x10\x0b\x12\x15\n\x11PAXCOUNTER_CONFIG\x10\x0c\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"[\n\x1eNodeRemoteHardwarePinsResponse\x12\x39\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32\x16.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.admin_pb2', globals())
@@ -28,13 +28,13 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_ADMINMESSAGE._serialized_start=198
_ADMINMESSAGE._serialized_end=2121
_ADMINMESSAGE_CONFIGTYPE._serialized_start=1635
_ADMINMESSAGE_CONFIGTYPE._serialized_end=1784
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_start=1787
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_end=2102
_HAMPARAMETERS._serialized_start=2123
_HAMPARAMETERS._serialized_end=2214
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_start=2216
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_end=2307
_ADMINMESSAGE._serialized_end=2152
_ADMINMESSAGE_CONFIGTYPE._serialized_start=1666
_ADMINMESSAGE_CONFIGTYPE._serialized_end=1815
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_start=1818
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_end=2133
_HAMPARAMETERS._serialized_start=2154
_HAMPARAMETERS._serialized_end=2245
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_start=2247
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_end=2338
# @@protoc_insertion_point(module_scope)

View File

View File

@@ -1,66 +1,224 @@
"""Bluetooth interface
"""
import logging
import platform
import time
import struct
from threading import Thread, Event
from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import our_exit
if platform.system() == "Linux":
# pylint: disable=E0401
import pygatt
from bleak import BleakScanner, BleakClient
import asyncio
# Our standard BLE characteristics
SERVICE_UUID = "6ba1b218-15a8-461f-9fa8-5dcae273eafd"
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices"""
class BLEError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
def __init__(self, address, noProto=False, debugOut=None):
if platform.system() != "Linux":
our_exit("Linux is the only platform with experimental BLE support.", 1)
self.address = address
if not noProto:
self.adapter = pygatt.GATTToolBackend() # BGAPIBackend()
self.adapter.start()
logging.debug(f"Connecting to {self.address}")
self.device = self.adapter.connect(address)
else:
self.adapter = None
self.device = None
logging.debug("Connected to device")
# fromradio = self.device.char_read(FROMRADIO_UUID)
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
self._readFromRadio() # read the initial responses
class BLEState():
THREADS = False
BLE = False
MESH = False
def handle_data(handle, data): # pylint: disable=W0613
self._handleFromRadio(data)
if self.device:
self.device.subscribe(FROMNUM_UUID, callback=handle_data)
def __init__(self, address, noProto = False, debugOut = None):
self.state = BLEInterface.BLEState()
if not address:
return
self.should_read = False
logging.debug("Threads starting")
self._receiveThread = Thread(target = self._receiveFromRadioImpl)
self._receiveThread_started = Event()
self._receiveThread_stopped = Event()
self._receiveThread.start()
self._receiveThread_started.wait(1)
self.state.THREADS = True
logging.debug("Threads running")
try:
logging.debug(f"BLE connecting to: {address}")
self.client = self.connect(address)
self.state.BLE = True
logging.debug("BLE connected")
except BLEInterface.BLEError as e:
self.close()
our_exit(e.message, 1)
return
logging.debug("Mesh init starting")
MeshInterface.__init__(self, debugOut = debugOut, noProto = noProto)
self._startConfig()
if not self.noProto:
self._waitConnected()
self.waitForConfig()
self.state.MESH = True
logging.debug("Mesh init finished")
logging.debug("Register FROMNUM notify callback")
self.client.start_notify(FROMNUM_UUID, self.from_num_handler)
async def from_num_handler(self, _, b):
from_num = struct.unpack('<I', bytes(b))[0]
logging.debug(f"FROMNUM notify: {from_num}")
self.should_read = True
def scan(self):
with BLEClient() as client:
return [
(x[0], x[1]) for x in (client.discover(
return_adv = True,
service_uuids = [ SERVICE_UUID ]
)).values()
]
def find_device(self, address):
meshtastic_devices = self.scan()
addressed_devices = list(filter(lambda x: address == x[1].local_name or address == x[0].name, meshtastic_devices))
# If nothing is found try on the address
if len(addressed_devices) == 0:
addressed_devices = list(filter(lambda x: BLEInterface._sanitize_address(address) == BLEInterface._sanitize_address(x[0].address), meshtastic_devices))
if len(addressed_devices) == 0:
raise BLEInterface.BLEError(f"No Meshtastic BLE peripheral with identifier or address '{address}' found. Try --ble-scan to find it.")
if len(addressed_devices) > 1:
raise BLEInterface.BLEError(f"More than one Meshtastic BLE peripheral with identifier or address '{address}' found.")
return addressed_devices[0][0]
def _sanitize_address(address):
return address \
.replace("-", "") \
.replace("_", "") \
.replace(":", "") \
.lower()
def connect(self, address):
device = self.find_device(address)
client = BLEClient(device.address)
client.connect()
try:
client.pair()
except NotImplementedError:
# Some bluetooth backends do not require explicit pairing.
# See Bleak docs for details on this.
pass
return client
def _receiveFromRadioImpl(self):
self._receiveThread_started.set()
while self._receiveThread_started.is_set():
if self.should_read:
self.should_read = False
while True:
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
if not b:
break
logging.debug(f"FROMRADIO read: {b.hex()}")
self._handleFromRadio(b)
else:
time.sleep(0.1)
self._receiveThread_stopped.set()
def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device"""
# logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b)
if b:
logging.debug(f"TORADIO write: {b.hex()}")
self.client.write_gatt_char(TORADIO_UUID, b, response = True)
# Allow to propagate and then make sure we read
time.sleep(0.1)
self.should_read = True
def close(self):
MeshInterface.close(self)
if self.adapter:
self.adapter.stop()
if self.state.MESH:
MeshInterface.close(self)
def _readFromRadio(self):
if not self.noProto:
wasEmpty = False
while not wasEmpty:
if self.device:
b = self.device.char_read(FROMRADIO_UUID)
wasEmpty = len(b) == 0
if not wasEmpty:
self._handleFromRadio(b)
if self.state.THREADS:
self._receiveThread_started.clear()
self._receiveThread_stopped.wait(5)
if self.state.BLE:
self.client.disconnect()
self.client.close()
class BLEClient():
def __init__(self, address = None, **kwargs):
self._eventThread = Thread(target = self._run_event_loop)
self._eventThread_started = Event()
self._eventThread_stopped = Event()
self._eventThread.start()
self._eventThread_started.wait(1)
if not address:
logging.debug("No address provided - only discover method will work.")
return
self.bleak_client = BleakClient(address, **kwargs)
def discover(self, **kwargs):
return self.async_await(BleakScanner.discover(**kwargs))
def pair(self, **kwargs):
return self.async_await(self.bleak_client.pair(**kwargs))
def connect(self, **kwargs):
return self.async_await(self.bleak_client.connect(**kwargs))
def disconnect(self, **kwargs):
self.async_await(self.bleak_client.disconnect(**kwargs))
def read_gatt_char(self, *args, **kwargs):
return self.async_await(self.bleak_client.read_gatt_char(*args, **kwargs))
def write_gatt_char(self, *args, **kwargs):
self.async_await(self.bleak_client.write_gatt_char(*args, **kwargs))
def start_notify(self, *args, **kwargs):
self.async_await(self.bleak_client.start_notify(*args, **kwargs))
def close(self):
self.async_run(self._stop_event_loop())
self._eventThread_stopped.wait(5)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def async_await(self, coro, timeout = None):
return self.async_run(coro).result(timeout)
def async_run(self, coro):
return asyncio.run_coroutine_threadsafe(coro, self._eventLoop)
def _run_event_loop(self):
self._eventLoop = asyncio.new_event_loop()
self._eventThread_started.set()
try:
self._eventLoop.run_forever()
finally:
self._eventLoop.close()
self._eventThread_stopped.set()
async def _stop_event_loop(self):
self._eventLoop.stop()

View File

File diff suppressed because one or more lines are too long

View File

@@ -591,7 +591,7 @@ class MeshInterface:
return user.get("shortName", None)
return None
def _waitConnected(self, timeout=15.0):
def _waitConnected(self, timeout=30.0):
"""Block until the initial node db download is complete, or timeout
and raise an exception"""
if not self.noProto:

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
"""Meshtastic unit tests for ble_interface.py"""
from unittest.mock import patch
import pytest
from ..ble_interface import BLEInterface
@pytest.mark.unit
@patch("platform.system", return_value="Linux")
def test_BLEInterface(mock_platform):
"""Test that we can instantiate a BLEInterface"""
iface = BLEInterface("foo", debugOut=True, noProto=True)
iface.close()
mock_platform.assert_called()

View File

@@ -283,26 +283,6 @@ def test_main_info_with_tcp_interface(capsys):
mo.assert_called()
# TODO: comment out ble (for now)
# @pytest.mark.unit
# def test_main_info_with_ble_interface(capsys):
# """Test --info"""
# sys.argv = ['', '--info', '--ble', 'foo']
# Globals.getInstance().set_args(sys.argv)
#
# iface = MagicMock(autospec=BLEInterface)
# def mock_showInfo():
# print('inside mocked showInfo')
# iface.showInfo.side_effect = mock_showInfo
# with patch('meshtastic.ble_interface.BLEInterface', return_value=iface) as mo:
# main()
# out, err = capsys.readouterr()
# assert re.search(r'Connected to radio', out, re.MULTILINE)
# assert re.search(r'inside mocked showInfo', out, re.MULTILINE)
# assert err == ''
# mo.assert_called()
@pytest.mark.unit
@pytest.mark.usefixtures("reset_globals")
def test_main_no_proto(capsys):

View File

@@ -18,4 +18,4 @@ pyyaml
pytap2
pdoc3
pypubsub
pygatt; platform_system == "Linux"
bleak

View File

@@ -13,7 +13,7 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work
setup(
name="meshtastic",
version="2.2.17",
version="2.2.19",
description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description,
long_description_content_type="text/markdown",
@@ -43,7 +43,7 @@ setup(
"tabulate>=0.8.9",
"timeago>=1.0.15",
"pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'",
"bleak>=0.21.1",
],
extras_require={"tunnel": ["pytap2>=2.0.0"]},
python_requires=">=3.7",