Skip to content

Commit

Permalink
Use separate characteristics for read/write
Browse files Browse the repository at this point in the history
  • Loading branch information
jcapona committed Sep 26, 2023
1 parent 12557e7 commit b11d7ab
Show file tree
Hide file tree
Showing 11 changed files with 478 additions and 172 deletions.
41 changes: 31 additions & 10 deletions further_link/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@
from aiohttp import web

from further_link.endpoint.apt_version import apt_version, apt_version_bt
from further_link.endpoint.run import bt_run_handler
from further_link.endpoint.run import bluetooth_run_handler
from further_link.endpoint.run import run as run_handler
from further_link.endpoint.run_py import run_py
from further_link.endpoint.upload import bt_upload, upload
from further_link.endpoint.upload import bluetooth_upload, upload
from further_link.util import vnc
from further_link.util.bluetooth.device import BluetoothDevice, GattConfig
from further_link.util.bluetooth.gatt import (
FURTHER_GATT_CONFIG,
PT_APT_VERSION_CHARACTERISTIC_UUID,
PT_RUN_CHARACTERISTIC_UUID,
PT_RUN_SERVICE_UUID,
PT_RUN_READ_CHARACTERISTIC_UUID,
PT_RUN_WRITE_CHARACTERISTIC_UUID,
PT_SERVICE_UUID,
PT_STATUS_CHARACTERISTIC_UUID,
PT_UPLOAD_CHARACTERISTIC_UUID,
PT_UPLOAD_READ_CHARACTERISTIC_UUID,
PT_UPLOAD_WRITE_CHARACTERISTIC_UUID,
PT_VERSION_CHARACTERISTIC_UUID,
)
from further_link.util.ssl_context import ssl_context
Expand Down Expand Up @@ -56,21 +58,40 @@ async def version(_):

async def create_bluetooth_app():
try:
# Associate characteristic read/write with handlers
# 'upload' and 'run' features use 2 characteristics; one ('READ') is used by the client to read the
# characteristic's value, or get notified of a change in the characteristic's value when subscribed.
#
# The other ('WRITE') is used by the client to send a value to the server.
#
# When a value is written to the 'WRITE' characteristic, the associated callback will be executed,
# writing a value to the associated 'READ' characteristic.
#
# In both cases, the value written/read from/to the characteristic is treated as a message being sent/received.

gatt_config = GattConfig("Further", FURTHER_GATT_CONFIG)
gatt_config.register_read_handler(
PT_RUN_SERVICE_UUID, PT_STATUS_CHARACTERISTIC_UUID, _status
PT_SERVICE_UUID, PT_STATUS_CHARACTERISTIC_UUID, _status
)
gatt_config.register_read_handler(
PT_RUN_SERVICE_UUID, PT_VERSION_CHARACTERISTIC_UUID, _version
PT_SERVICE_UUID, PT_VERSION_CHARACTERISTIC_UUID, _version
)
gatt_config.register_write_handler(
PT_RUN_SERVICE_UUID, PT_APT_VERSION_CHARACTERISTIC_UUID, apt_version_bt
PT_SERVICE_UUID, PT_APT_VERSION_CHARACTERISTIC_UUID, apt_version_bt
)
gatt_config.register_write_handler(
PT_RUN_SERVICE_UUID, PT_RUN_CHARACTERISTIC_UUID, bt_run_handler
PT_SERVICE_UUID,
PT_RUN_WRITE_CHARACTERISTIC_UUID,
lambda device, uuid, message: bluetooth_run_handler(
device, uuid, message, PT_RUN_READ_CHARACTERISTIC_UUID
),
)
gatt_config.register_write_handler(
PT_RUN_SERVICE_UUID, PT_UPLOAD_CHARACTERISTIC_UUID, bt_upload
PT_SERVICE_UUID,
PT_UPLOAD_WRITE_CHARACTERISTIC_UUID,
lambda device, uuid, message: bluetooth_upload(
device, uuid, message, PT_UPLOAD_READ_CHARACTERISTIC_UUID
),
)

bt = BluetoothDevice(gatt_config)
Expand Down
2 changes: 1 addition & 1 deletion further_link/endpoint/apt_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ async def apt_version_bt(device, char_uuid, value):


async def apt_version(request):
version = await _apt_version_dict(request.match_info["pkg"])
version = _apt_version_dict(request.match_info["pkg"])
return web.Response(text=json.dumps(version))


Expand Down
62 changes: 31 additions & 31 deletions further_link/endpoint/run.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import logging
from typing import Callable, Dict

from aiohttp import web
from pt_web_vnc.connection_details import VncConnectionDetails
Expand All @@ -8,39 +9,19 @@
from ..runner.process_handler import InvalidOperation
from ..runner.py_process_handler import PyProcessHandler
from ..runner.shell_process_handler import ShellProcessHandler
from ..util.bluetooth.gatt import PT_RUN_CHARACTERISTIC_UUID
from ..util.bluetooth.utils import bytearray_to_dict
from ..util.message import BadMessage, create_message, parse_message
from ..util.user_config import default_user, get_temp_dir


class Iface:
def __init__(self, obj) -> None:
self.obj = obj

async def send(self, message):
logging.warning(f"Iface send: {message}")
if isinstance(self.obj, web.WebSocketResponse):
try:
await self.obj.send_str(message)
except ConnectionResetError:
pass # already disconnected
else:
try:
self.obj.write_value(message, PT_RUN_CHARACTERISTIC_UUID)
except Exception as e:
logging.error(f"Error sending message: {e}")
pass


class RunManager:
def __init__(self, interface, user=None, pty=False):
self.iface = Iface(interface)
def __init__(self, send_func: Callable, user=None, pty=False):
self.send_func = send_func
self.user = default_user() if user is None else user
self.pty = pty

self.id = str(id(self))
self.process_handlers = {}
self.process_handlers: Dict = {}
self.handler_classes = {
"exec": ExecProcessHandler,
"python3": PyProcessHandler,
Expand All @@ -56,7 +37,7 @@ async def stop(self):
pass

async def send(self, type, data=None, process_id=None):
await self.iface.send(create_message(type, data, process_id))
self.send_func(create_message(type, data, process_id))

async def handle_message(self, message):
try:
Expand Down Expand Up @@ -171,17 +152,29 @@ async def on_display_activity(connection_details: VncConnectionDetails):
bt_run_manager = None


async def bt_run_handler(interface, uuid, message):
message_dict = bytearray_to_dict(message)
async def bluetooth_run_handler(device, uuid, message, characteristic_to_report_on):
try:
message_dict = bytearray_to_dict(message)
except Exception as e:
logging.exception(f"Error: {e}")
raise Exception("Error: invalid format")

user = message_dict.get("user", None)
pty = message_dict.get("pty", "").lower() in ["1", "true"]
try:
user = message_dict.get("user", None)
pty = message_dict.get("pty", "").lower() in ["1", "true"]
except Exception as e:
logging.exception(f"Error: {e}")
raise

# TODO: handle multiple 'run' connections
global bt_run_manager
if bt_run_manager is None:
bt_run_manager = RunManager(interface, user=user, pty=pty)
logging.info(f"{bt_run_manager.id} New connection")

def send_func(message):
device.write_value(message, characteristic_to_report_on)

bt_run_manager = RunManager(send_func, user=user, pty=pty)
logging.debug(f"{bt_run_manager.id} New connection")

logging.debug(f"{bt_run_manager.id} Received Message {message_dict}")
try:
Expand All @@ -190,6 +183,7 @@ async def bt_run_handler(interface, uuid, message):
logging.exception(f"{bt_run_manager.id} Message Exception: {e}")
await bt_run_manager.stop()
bt_run_manager = None
raise


async def run(request):
Expand All @@ -200,7 +194,13 @@ async def run(request):
socket = web.WebSocketResponse()
await socket.prepare(request)

run_manager = RunManager(socket, user=user, pty=pty)
async def send_func(message):
try:
await socket.send_str(message)
except ConnectionResetError:
pass # already disconnected

run_manager = RunManager(send_func, user=user, pty=pty)
logging.info(f"{run_manager.id} New connection")

try:
Expand Down
40 changes: 21 additions & 19 deletions further_link/endpoint/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,29 @@ async def upload(request):
)


async def bt_upload(interface, uuid, message: bytearray):
async def bluetooth_upload(
device, uuid: str, message: bytearray, characteristic_to_report_on: str
):
device.write_value("IN_PROGRESS", characteristic_to_report_on)
try:
message_dict = bytearray_to_dict(message)
except json.decoder.JSONDecodeError as e:
final_message = await _bt_upload(device, characteristic_to_report_on, message)
device.write_value(f"{final_message}", characteristic_to_report_on)
except Exception as e:
logging.exception(f"Error: {e}")
interface.write_value(b"Error: invalid format", uuid)
return
device.write_value(f"Error: {e}", characteristic_to_report_on)


async def _bt_upload(device, uuid: str, message: bytearray):
try:
user = message_dict.get("user", None)
work_dir = get_working_directory(user)

if not directory_is_valid(message_dict):
msg = f"Invalid upload directory: {message_dict}"
interface.write_value(msg, uuid)
return
fetched_urls = await handle_upload(message_dict, work_dir, user)
except Exception as e:
logging.exception(f"{e}")
interface.write_value(f"Error: {e}", uuid)
message_dict = bytearray_to_dict(message)
except json.decoder.JSONDecodeError:
raise Exception("Invalid format")

interface.write_value(
json.dumps({"success": True, "fetched_urls": fetched_urls}), uuid
)
user = message_dict.get("user", None)
work_dir = get_working_directory(user)

if not directory_is_valid(message_dict):
raise Exception(f"Invalid upload directory: {message_dict}")
fetched_urls = await handle_upload(message_dict, work_dir, user)

return json.dumps({"success": True, "fetched_urls": fetched_urls})
20 changes: 13 additions & 7 deletions further_link/util/bluetooth/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def start(self) -> None:

async def stop(self) -> None:
if self.server:
logging.info("Stopping bluetooth server")
logging.debug("Stopping bluetooth server")
await self.server.stop()

def _get_service_uuid(self, characteristic_uuid) -> str:
Expand All @@ -66,7 +66,7 @@ def write_value(self, value: Union[str, bytearray], uuid: str) -> None:
if isinstance(value, str):
value = to_byte_array(value)

logging.warning(f"Writing '{value}' to characteristic {uuid}")
logging.debug(f"Writing '{value}' to characteristic {uuid}")
characteristic = self.server.get_characteristic(uuid)
characteristic.value = value

Expand All @@ -84,7 +84,9 @@ def read_value(self, uuid: str):
return self._read_request(char)

def _read_request(self, characteristic: BlessGATTCharacteristic, **kwargs):
logging.warning(f"Read request for characteristc {characteristic}")
"""method required by bless to handle read requests"""

logging.debug(f"Read request for characteristc {characteristic}")
value = decode_value(characteristic.value)

# callback on read requests will update the value of the characteristic before returning
Expand All @@ -94,7 +96,7 @@ def _read_request(self, characteristic: BlessGATTCharacteristic, **kwargs):
.get("ReadHandler")
)
if callback and callable(callback):
logging.warning(f"Executing ReadHandler callback for {characteristic.uuid}")
logging.debug(f"Executing ReadHandler callback for {characteristic.uuid}")
try:
value = str(callback())
# TODO: handle long messages using ChunkedMessage
Expand All @@ -103,7 +105,7 @@ def _read_request(self, characteristic: BlessGATTCharacteristic, **kwargs):
logging.exception(f"Error executing callback: '{e}' - returning ''")
value = ""

logging.warning(
logging.debug(
f"Read request for characteristc {characteristic}; returning '{value}'"
)

Expand All @@ -112,7 +114,9 @@ def _read_request(self, characteristic: BlessGATTCharacteristic, **kwargs):
def _write_request(
self, characteristic: BlessGATTCharacteristic, value: bytearray, **kwargs
):
logging.warning(
"""method required by bless to handle write requests"""

logging.debug(
f"Write request for characteristic {characteristic.uuid} with value '{value}'"
)
# if handling a 'ChunkedMessage', callback is executed only when the message is complete
Expand Down Expand Up @@ -143,7 +147,7 @@ def _write_request(
.get("WriteHandler")
)
if callable(callback):
logging.warning(
logging.debug(
f"Executing WriteHandler callback for {characteristic.uuid}: {callback}"
)

Expand All @@ -152,5 +156,7 @@ async def run_callback():
await callback(self, characteristic.uuid, value)
except Exception as e:
logging.exception(f"Error executing callback: {e}")
raise

# '_write_request' method is synchronous; callback is executed as a task
asyncio.create_task(run_callback())
51 changes: 32 additions & 19 deletions further_link/util/bluetooth/gatt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@

from bless import GATTAttributePermissions, GATTCharacteristicProperties

PT_RUN_SERVICE_UUID = "12341000-1234-1234-1234-123456789abc"
PT_RUN_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abd"
PT_STATUS_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abe"
PT_VERSION_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abf"
PT_APT_VERSION_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789adf"
PT_UPLOAD_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789acf"
PT_SERVICE_UUID = "12341000-1234-1234-1234-123456789aaa"
PT_STATUS_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abb"
PT_VERSION_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abc"
PT_APT_VERSION_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789abd"

# TODO: upload & run characteristics should run in a separate service (not supported by bless apparently)
PT_UPLOAD_READ_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789bba"
PT_UPLOAD_WRITE_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789bca"
PT_RUN_READ_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789cba"
PT_RUN_WRITE_CHARACTERISTIC_UUID = "12341000-1234-1234-1234-123456789cca"


FURTHER_GATT_CONFIG = {
PT_RUN_SERVICE_UUID: {
PT_RUN_CHARACTERISTIC_UUID: {
"Properties": (
GATTCharacteristicProperties.write
| GATTCharacteristicProperties.read
| GATTCharacteristicProperties.notify
),
"Permissions": (
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
),
"Value": None,
},
PT_SERVICE_UUID: {
PT_STATUS_CHARACTERISTIC_UUID: {
"Properties": (
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify
Expand All @@ -48,7 +42,7 @@
),
"Value": None,
},
PT_UPLOAD_CHARACTERISTIC_UUID: {
PT_UPLOAD_WRITE_CHARACTERISTIC_UUID: {
"Properties": (
GATTCharacteristicProperties.write
| GATTCharacteristicProperties.read
Expand All @@ -59,6 +53,25 @@
),
"Value": None,
},
PT_UPLOAD_READ_CHARACTERISTIC_UUID: {
"Properties": (
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify
),
"Permissions": (GATTAttributePermissions.readable),
"Value": None,
},
PT_RUN_READ_CHARACTERISTIC_UUID: {
"Properties": (
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify
),
"Permissions": (GATTAttributePermissions.readable),
"Value": None,
},
PT_RUN_WRITE_CHARACTERISTIC_UUID: {
"Properties": (GATTCharacteristicProperties.write),
"Permissions": (GATTAttributePermissions.writeable),
"Value": None,
},
},
}

Expand Down
Loading

0 comments on commit b11d7ab

Please sign in to comment.