begin BLE support

This commit is contained in:
geeksville
2020-05-12 13:54:11 -07:00
parent bfa2d97e43
commit 8af99477bb
8 changed files with 102 additions and 18 deletions

15
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Module",
"type": "python",
"request": "launch",
"module": "meshtastic",
"args": ["--debug", "--ble", "--device", "24:62:AB:DD:DF:3A"]
}
]
}

View File

@@ -1,5 +1,6 @@
{
"cSpell.words": [
"Meshtastic"
"Meshtastic",
"TORADIO"
]
}

View File

@@ -24,6 +24,12 @@ For the rough notes/implementation plan see [TODO](https://github.com/meshtastic
This pip package will also install a "meshtastic" commandline executable, which displays packets sent over the network as JSON and lets you see serial debugging information from the meshtastic devices. The source code for this tool is also a good [example](https://github.com/meshtastic/Meshtastic-python/blob/master/meshtastic/__main__.py) of a 'complete' application that uses the meshtastic python API.
## Bluetooth support
(Alpha level feature)
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 Bleak library, but a very particular version (due to a bleak [bug](https://github.com/hbldh/bleak/issues/139)): python3 -m pip install git+https://github.com/pliniofpa/bleak.git@cbad754205b8dbbe1def448b18d04c65cf5a75e7
## Required device software version
This API and tool both require that the device is running Meshtastic 0.6.0 or later.

View File

@@ -2,6 +2,8 @@
## Before beta
- ./bin/run.sh --debug --ble --device 24:62:AB:DD:DF:3A
- merge my local fixes to bleak: /home/kevinh/.local/lib/python3.8/site-packages/bleak/backends/bluezdbus/
- update nodedb as nodes change
- radioConfig - getter/setter syntax: https://www.python-course.eu/python3_properties.php
- let user change radio params via commandline options

View File

@@ -1,5 +1,5 @@
"""
## an API for Meshtastic devices
# an API for Meshtastic devices
Primary class: StreamInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
@@ -7,26 +7,26 @@ Source code on [github](https://github.com/meshtastic/Meshtastic-python)
properties of StreamInterface:
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device.
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure.
- myNodeInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
## Published PubSub topics
# Published PubSub topics
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics:
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB
- meshtastic.connection.lost - published once we've lost our link to the radio
- meshtastic.receive.position(packet) - delivers a received packet as a dictionary, if you only care about a particular
- meshtastic.receive.position(packet) - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
- meshtastic.receive.user(packet)
- meshtastic.receive.data(packet)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...)
## Example Usage
# Example Usage
```
import meshtastic
from pubsub import pub
@@ -35,16 +35,20 @@ def onReceive(packet): # called when a packet arrives
print(f"Received: {packet}")
def onConnection(): # called when we (re)connect to the radio
interface.sendText("hello mesh") # defaults to broadcast, specify a destination ID if you wish
# defaults to broadcast, specify a destination ID if you wish
interface.sendText("hello mesh")
pub.subscribe(onReceive, "meshtastic.receive")
pub.subscribe(onConnection, "meshtastic.connection.established")
interface = meshtastic.StreamInterface() # By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
interface = meshtastic.StreamInterface()
```
"""
import asyncio
from bleak import BleakClient
import google.protobuf.json_format
import serial
import threading
@@ -82,7 +86,6 @@ class MeshInterface:
self.debugOut = debugOut
self.nodes = None # FIXME
self.isConnected = False
self._startConfig()
def sendText(self, text, destinationId=BROADCAST_ADDR):
"""Send a utf8 string to some other node, if the node has a display it will also be shown on the device.
@@ -104,7 +107,7 @@ class MeshInterface:
self.sendPacket(meshPacket, destinationId)
def sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead."""
toRadio = mesh_pb2.ToRadio()
# FIXME add support for non broadcast addresses
@@ -195,8 +198,15 @@ class MeshInterface:
if "longitudeI" in position:
position["longitude"] = position["longitudeI"] * 1e-7
def _nodeNumToId(self, num):
"""Map a node node number to a node ID
Arguments:
num {int} -- Node number
Returns:
string -- Node ID
"""
if num == BROADCAST_NUM:
return BROADCAST_ADDR
@@ -221,7 +231,7 @@ class MeshInterface:
asDict["toId"] = self._nodeNumToId(asDict["to"])
# We could provide our objects as DotMaps - which work with . notation or as dictionaries
#asObj = DotMap(asDict)
# asObj = DotMap(asDict)
topic = None
if meshPacket.decoded.HasField("position"):
topic = "meshtastic.receive.position"
@@ -240,11 +250,39 @@ class MeshInterface:
pub.sendMessage(topic, packet=asDict, interface=self)
# Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
FROMNUM_UUID = "ed9da18c-a800-4f66-a670-aa7547e34453"
class BLEInterface(MeshInterface):
def __init__(self, address, debugOut=None):
self.address = address
MeshInterface.__init__(self, debugOut=debugOut)
async def close(self):
await self.client.disconnect()
async def run(self, loop):
self.client = BleakClient(self.address, loop=loop)
try:
logging.debug(f"Connecting to {self.address}")
await self.client.connect()
logging.debug("Connected to device")
fromradio = await self.client.read_gatt_char(FROMRADIO_UUID)
print(f"****** fromradio {fromradio}")
except Exception as e:
logging.error(e)
finally:
await self.close()
class StreamInterface(MeshInterface):
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
def __init__(self, devPath=None, debugOut=None):
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
find one Meshtastic device by probing
Keyword Arguments:
@@ -275,6 +313,7 @@ class StreamInterface(MeshInterface):
self._rxThread = threading.Thread(target=self.__reader, args=())
self._rxThread.start()
MeshInterface.__init__(self, debugOut=debugOut)
self._startConfig()
def _sendToRadio(self, toRadio):
"""Send a ToRadio protobuf to the device"""

View File

@@ -1,7 +1,8 @@
#!python3
import asyncio
import argparse
from . import StreamInterface, test
from . import StreamInterface, BLEInterface, test
import logging
import sys
from pubsub import pub
@@ -95,6 +96,9 @@ def main():
parser.add_argument("--test", help="Run stress test against all connected Meshtastic devices",
action="store_true")
parser.add_argument("--ble", help="hack for testing BLE code",
action="store_true")
global args
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
@@ -115,7 +119,12 @@ def main():
logfile = open(args.seriallog, 'w+', buffering=1) # line buffering
subscribe()
client = StreamInterface(args.device, debugOut=logfile)
if args.ble:
client = BLEInterface(args.device, debugOut=logfile)
loop = asyncio.get_event_loop()
loop.run_until_complete(client.run(loop))
else:
client = StreamInterface(args.device, debugOut=logfile)
if __name__ == "__main__":

12
meshtastic/ble.py Normal file
View File

@@ -0,0 +1,12 @@
import asyncio
from bleak import discover
async def run():
devices = await discover()
for d in devices:
print(d)
def bleScan():
loop = asyncio.get_event_loop()
loop.run_until_complete(run())

View File

@@ -26,8 +26,8 @@ setup(
packages=["meshtastic"],
include_package_data=True,
install_requires=["pyserial>=3.4", "protobuf>=3.6.1",
"pypubsub>=4.0.3", "dotmap>=1.3.14"],
python_requires='>=3',
"pypubsub>=4.0.3", "dotmap>=1.3.14", "bleak>=0.6.1"],
python_requires='>=3.4',
entry_points={
"console_scripts": [
"meshtastic=meshtastic.__main__:main"