Compare commits

...

5 commits

Author SHA1 Message Date
liamcottle
6100a3bdff show ble address 2024-12-15 21:09:53 +13:00
liamcottle
3aaebfccba add section titles to interface port list 2024-12-15 21:08:55 +13:00
liamcottle
4970d5088f discover ble rnodes when adding or editing an interface 2024-12-15 20:59:52 +13:00
liamcottle
7c5235845a add api to scan for ble devices 2024-12-15 20:51:52 +13:00
liamcottle
b1c3cc767c add bleak as python dependency for rnode ble connections 2024-12-15 20:14:26 +13:00
3 changed files with 75 additions and 1 deletions

View file

@ -13,11 +13,13 @@ import RNS
import RNS.vendor.umsgpack as msgpack
import LXMF
from LXMF import LXMRouter
from RNS.Interfaces.RNodeInterface import BLEConnection
from aiohttp import web, WSMessage, WSMsgType, WSCloseCode
import asyncio
import base64
import webbrowser
from bleak import BleakScanner
from peewee import SqliteDatabase
from serial.tools import list_ports
@ -317,6 +319,45 @@ class ReticulumMeshChat:
"comports": comports,
})
# scan for rnodes available via ble
@routes.get("/api/v1/rnodes/ble-scan")
async def index(request):
# determine how long we should scan for
scan_duration_seconds = int(request.query.get("scan_duration_seconds", 3))
# discover ble devices
ble_scan_results = await BleakScanner.discover(
timeout=scan_duration_seconds,
return_adv=True,
cb=dict(use_bdaddr=True),
service_uuids=[
BLEConnection.UART_SERVICE_UUID,
],
)
# format scan results
rnodes = []
for ble_address in ble_scan_results:
# get device and advertisement data from scan result
device, advertisement_data = ble_scan_results[ble_address]
# skip this result if advertisement data is unavailable
if advertisement_data is None:
continue
rnodes.append({
"name": device.name,
"local_name": advertisement_data.local_name,
"ble_address": device.address,
"port": "ble://" + advertisement_data.local_name,
})
return web.json_response({
"rnodes": rnodes,
})
# fetch reticulum interfaces
@routes.get("/api/v1/reticulum/interfaces")
async def index(request):

View file

@ -1,4 +1,5 @@
aiohttp>=3.9.5
bleak>=0.22.3
cx_freeze>=7.0.0
lxmf>=0.5.8
peewee>=3.17.3

View file

@ -117,8 +117,15 @@
<div v-if="newInterfaceType === 'RNodeInterface'" class="mb-2">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-zinc-100">Port</label>
<select v-model="newInterfacePort" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-zinc-900 dark:border-zinc-600 dark:text-white dark:focus:ring-blue-600 dark:focus:border-blue-600">
<option disabled>Serial Devices ({{ comports.length }})</option>
<option v-for="comport of comports" :value="comport.device">{{ comport.device }} (Product: {{ comport.product ?? '?' }}, Serial: {{ comport.serial ?? '?' }})</option>
<option disabled>Bluetooth Devices ({{ rnodes.length }})</option>
<option v-for="rnode of rnodes" :value="rnode.port">{{ rnode.port }} ({{ rnode.ble_address }})</option>
</select>
<div class="text-xs text-gray-600">
<span v-if="isLoadingRnodes" class="text-gray-500">Discovering Bluetooth RNodes...</span>
<span v-else @click="loadRnodes" class="text-blue-500 underline cursor-pointer">Discover Bluetooth RNodes</span>
</div>
</div>
<!-- interface frequency -->
@ -184,6 +191,9 @@ export default {
config: null,
comports: [],
rnodes: [],
isLoadingRnodes: false,
newInterfaceName: null,
newInterfaceType: null,
@ -243,6 +253,7 @@ export default {
this.getConfig();
this.loadComports();
this.loadRnodes();
// check if we are editing an interface
const interfaceName = this.$route.query.interface_name;
@ -276,9 +287,30 @@ export default {
const response = await window.axios.get(`/api/v1/comports`);
this.comports = response.data.comports;
} catch(e) {
// do nothing if failed to load interfaces
// do nothing if failed to load comports
}
},
async loadRnodes() {
// do nothing if already loading
if(this.isLoadingRnodes){
return;
}
// show loading
this.isLoadingRnodes = true;
try {
const response = await window.axios.get(`/api/v1/rnodes/ble-scan`);
this.rnodes = response.data.rnodes;
} catch(e) {
// do nothing if failed to load rnodes
} finally {
// no longer loading
this.isLoadingRnodes = false;
}
},
async loadInterfaceToEdit(interfaceName) {
try {