[WIP] PoC for CLI event handling (not compatible with textual)

This commit is contained in:
jpk 2023-05-18 19:23:56 +02:00
parent 754afed50b
commit f9505c2cde
9 changed files with 229 additions and 22 deletions

View File

@ -1,24 +1,29 @@
#!/usr/bin/env python3
import click
from ..tools.ble import scanner, monitor
from ..tools import context
from ..tools.ble import monitor, scanner
@click.command(name="scan")
def scanner_cmd():
def scanner_cmd() -> None:
scanner.run()
@click.command(name="monitor")
def monitor_cmd():
def monitor_cmd() -> None:
monitor.run()
@click.group()
def main():
@click.command(name="tui")
def tui_cmd() -> None:
pass
@click.group()
def main() -> None:
context.set_environment(context.BlattedEnvironment.CLI)
main.add_command(scanner_cmd)
main.add_command(monitor_cmd)
main.add_command(tui_cmd)

View File

@ -0,0 +1,3 @@
from .event import DeviceDiscovered
__all__ = ["DeviceDiscovered"]

16
blatted/events/event.py Normal file
View File

@ -0,0 +1,16 @@
from textual.message import Message
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
class DeviceDiscovered(Message):
def __init__(self, device: BLEDevice, adverisement_data: AdvertisementData) -> None:
self.device = device
self.adverisement_data = adverisement_data
super().__init__()
def has_services(self) -> bool:
return len(self.advertising_data.service_uuids) > 0
def repr(self) -> str:
return f"{self.device.address} - {self.device.name}"

View File

@ -15,7 +15,7 @@ class DiscoveredDevices:
devices: list[DiscoveredDevice] = field(default_factory=list)
def add(self, device: DiscoveredDevice):
if not device in self.devices:
if device not in self.devices:
self.devices.append(device)
def count(self):

View File

@ -7,7 +7,7 @@ from icecream import ic
async def monitor_services(filter: list[str] = []):
await asyncio.sleep(.1)
await asyncio.sleep(0.1)
def run(uuid_filter: list[str] = []):

View File

@ -1,31 +1,55 @@
import asyncio
from typing import Dict, Any
import bleak.exc
from bleak import BleakScanner
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from pydispatch import Dispatcher
from .models import DiscoveredDevice
from ...events.event import DeviceDiscovered
from ...tools import context
def discover_callback(device: BLEDevice, advertising_data: AdvertisementData):
if len(advertising_data.service_uuids) > 0:
discovered = DiscoveredDevice(device, advertising_data)
print(
f"[{advertising_data.rssi:-4d}] {device.address} - {device.name} [{discovered}]"
)
class Scanner(Dispatcher):
_events_ = ["device_discovered"]
def __init__(self) -> None:
self.environment = context.get_environment()
self.devices: Dict[str, Any] = {}
self.bind(device_discovered=self.on_device_discovered)
async def discover_devices():
stop_event = asyncio.Event()
def discover_callback(
self, device: BLEDevice, advertising_data: AdvertisementData
) -> None:
if len(advertising_data.service_uuids) > 0:
#discovered = DiscoveredDevice(device, advertising_data)
discovered = DeviceDiscovered(device, advertising_data)
if context.get_environment() == context.BlattedEnvironment.CLI:
self.emit("device_discovered", data=discovered)
elif context.get_environment() == context.BlattedEnvironment.TUI:
pass # TODO Implement
async with BleakScanner(discover_callback):
await stop_event.wait()
def on_device_discovered(self, data: DeviceDiscovered) -> None:
if data.device.address not in self.devices:
print(f"new device discovered: {data.device}")
self.devices[data.device.address] = {"data": data, "seen": 1}
else:
self.devices[data.device.address]["seen"] += 1
print(
f"device seen {self.devices[data.device.address]['seen']} times: {data.device}"
)
async def run(self) -> None:
stop_event = asyncio.Event()
async with BleakScanner(self.discover_callback):
await stop_event.wait()
def run():
print("scanner called")
try:
asyncio.run(discover_devices())
asyncio.run(Scanner().run())
except bleak.exc.BleakDBusError as exc:
print(f"ERROR: {exc}")

18
blatted/tools/context.py Normal file
View File

@ -0,0 +1,18 @@
from contextvars import ContextVar
from enum import Enum
blatted_environment_var: ContextVar = ContextVar("blatted_environment")
class BlattedEnvironment(Enum):
CLI = "console line interface"
TUI = "terminal user interface"
def set_environment(mode: BlattedEnvironment) -> None:
blatted_environment_var.set(mode)
def get_environment() -> BlattedEnvironment:
return blatted_environment_var.get()

141
poetry.lock generated
View File

@ -238,6 +238,47 @@ files = [
[package.extras]
license = ["ukkonen"]
[[package]]
name = "importlib-metadata"
version = "6.6.0"
description = "Read metadata from Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
{file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
]
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]]
name = "linkify-it-py"
version = "2.0.2"
description = "Links recognition library with FULL unicode support."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "linkify-it-py-2.0.2.tar.gz", hash = "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2"},
{file = "linkify_it_py-2.0.2-py3-none-any.whl", hash = "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541"},
]
[package.dependencies]
uc-micro-py = "*"
[package.extras]
benchmark = ["pytest", "pytest-benchmark"]
dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"]
doc = ["myst-parser", "sphinx", "sphinx-book-theme"]
test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "markdown-it-py"
version = "2.2.0"
@ -251,6 +292,8 @@ files = [
]
[package.dependencies]
linkify-it-py = {version = ">=1,<3", optional = true, markers = "extra == \"linkify\""}
mdit-py-plugins = {version = "*", optional = true, markers = "extra == \"plugins\""}
mdurl = ">=0.1,<1.0"
[package.extras]
@ -263,6 +306,26 @@ profiling = ["gprof2dot"]
rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdit-py-plugins"
version = "0.3.5"
description = "Collection of plugins for markdown-it-py"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "mdit-py-plugins-0.3.5.tar.gz", hash = "sha256:eee0adc7195e5827e17e02d2a258a2ba159944a0748f59c5099a4a27f78fcf6a"},
{file = "mdit_py_plugins-0.3.5-py3-none-any.whl", hash = "sha256:ca9a0714ea59a24b2b044a1831f48d817dd0c817e84339f20e7889f392d77c4e"},
]
[package.dependencies]
markdown-it-py = ">=1.0.0,<3.0.0"
[package.extras]
code-style = ["pre-commit"]
rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "mdurl"
version = "0.1.2"
@ -415,6 +478,18 @@ files = [
[package.dependencies]
pyobjc-core = ">=9.1.1"
[[package]]
name = "python-dispatch"
version = "0.2.1"
description = "Lightweight Event Handling"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "python-dispatch-0.2.1.tar.gz", hash = "sha256:ca166addfdedd11fef80a004b930503b30c5c2a6f23cec0395986fd7cc8a5f1c"},
{file = "python_dispatch-0.2.1-py3-none-any.whl", hash = "sha256:43fb413a87404b212281bfc5733ef23d4b4ed3b7452faddb3c82517ea58634f1"},
]
[[package]]
name = "pyyaml"
version = "6.0"
@ -513,6 +588,54 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "textual"
version = "0.25.0"
description = "Modern Text User Interface framework"
category = "main"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "textual-0.25.0-py3-none-any.whl", hash = "sha256:8258499a09793696e13a1d1e1391810916828015a91770abd7ce3d9ab64cfd9e"},
{file = "textual-0.25.0.tar.gz", hash = "sha256:5e2d6320026dd8ff86e0d023fc4988071e80ec2e34de913bd63263a8301d664b"},
]
[package.dependencies]
importlib-metadata = ">=4.11.3"
markdown-it-py = {version = ">=2.1.0,<3.0.0", extras = ["linkify", "plugins"]}
rich = ">=13.3.3"
typing-extensions = ">=4.4.0,<5.0.0"
[package.extras]
dev = ["aiohttp (>=3.8.1)", "click (>=8.1.2)", "msgpack (>=1.0.3)"]
[[package]]
name = "typing-extensions"
version = "4.5.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
]
[[package]]
name = "uc-micro-py"
version = "1.0.2"
description = "Micro subset of unicode data files for linkify-it-py projects."
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "uc-micro-py-1.0.2.tar.gz", hash = "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54"},
{file = "uc_micro_py-1.0.2-py3-none-any.whl", hash = "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0"},
]
[package.extras]
test = ["coverage", "pytest", "pytest-cov"]
[[package]]
name = "virtualenv"
version = "20.23.0"
@ -534,7 +657,23 @@ platformdirs = ">=3.2,<4"
docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.7.1)", "time-machine (>=2.9)"]
[[package]]
name = "zipp"
version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"},
{file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "1a257f20c24d028b39114e970d7e3156f4cac3daeae8e443f1381b168c7eeca5"
content-hash = "a8b87d0f21a27962db7d2999e0e30657c5ee0705abe98325a0a65466421d1349"

View File

@ -10,6 +10,8 @@ python = "^3.10"
bleak = "^0.20.2"
click = "^8.1.3"
rich = "^13.3.5"
textual = "^0.25.0"
python-dispatch = "^0.2.1"
[tool.poetry.scripts]
blatted = "blatted.cli:main"