mirror of
https://github.com/meshtastic/python.git
synced 2025-12-24 08:27:55 -05:00
begin BLE support
This commit is contained in:
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal 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"]
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Meshtastic"
|
||||
"Meshtastic",
|
||||
"TORADIO"
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
2
TODO.md
2
TODO.md
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
12
meshtastic/ble.py
Normal 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())
|
||||
4
setup.py
4
setup.py
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user