From fe410f6444f01d56bd66c254f5f0304a09fd5d37 Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Sat, 7 Mar 2026 14:22:06 +1100 Subject: [PATCH] work on ui --- .gitignore | 3 +- desktop_app/api/node.py | 13 +++++---- desktop_app/main.py | 5 +++- desktop_app/ui/app.py | 11 ++++++++ desktop_app/ui/assets/banner.txt | 7 +++++ desktop_app/ui/assets/pair_screen.tcss | 39 ++++++++++++++++++++++++++ desktop_app/ui/screens/pair_screen.py | 32 +++++++++++++++++++++ 7 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 desktop_app/ui/app.py create mode 100644 desktop_app/ui/assets/banner.txt create mode 100644 desktop_app/ui/assets/pair_screen.tcss create mode 100644 desktop_app/ui/screens/pair_screen.py diff --git a/.gitignore b/.gitignore index f5e96db..fab8f69 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -venv \ No newline at end of file +venv +*.pyc \ No newline at end of file diff --git a/desktop_app/api/node.py b/desktop_app/api/node.py index df14588..54ab054 100644 --- a/desktop_app/api/node.py +++ b/desktop_app/api/node.py @@ -1,18 +1,21 @@ from enum import Enum -from bleak import BLEDevice, BleakScanner +from bleak import BLEDevice, BleakScanner, BleakClient NODE_BLUETOOTH_SERVICE_UUID = "E1898FF7-5063-4441-a6eb-526073B00001" class MeshNode: - def __init__(self, ble_device: BLEDevice): - self.ble_device = ble_device + def __init__(self, client: BleakClient): + self.client = client async def discover(): """Find a mesh node via Bluetooth """ - devices = await BleakScanner(service_uuids=[NODE_BLUETOOTH_SERVICE_UUID], scanning_mode="pasive") + devices = await BleakScanner.discover(service_uuids=[NODE_BLUETOOTH_SERVICE_UUID], timeout=5) # no device was found if len(devices) == 0: - return None \ No newline at end of file + return None + + device = await BleakScanner.find_device_by_address(devices[0].address, timeout=5) + return MeshNode(BleakClient(device)) \ No newline at end of file diff --git a/desktop_app/main.py b/desktop_app/main.py index 24652ad..e0c454f 100644 --- a/desktop_app/main.py +++ b/desktop_app/main.py @@ -1,4 +1,7 @@ from api.node import MeshNode +from ui.app import Hive -print(MeshNode.discover()) \ No newline at end of file +if __name__ == "__main__": + app = Hive() + app.run() \ No newline at end of file diff --git a/desktop_app/ui/app.py b/desktop_app/ui/app.py new file mode 100644 index 0000000..5fe72fb --- /dev/null +++ b/desktop_app/ui/app.py @@ -0,0 +1,11 @@ +from textual.app import App, ComposeResult +from textual.widgets import Header, Footer +from ui.screens.pair_screen import PairScreen + + +class Hive(App): + theme = "rose-pine" + ENABLE_COMMAND_PALETTE = False + + def on_ready(self): + self.push_screen(PairScreen()) \ No newline at end of file diff --git a/desktop_app/ui/assets/banner.txt b/desktop_app/ui/assets/banner.txt new file mode 100644 index 0000000..d2d02b8 --- /dev/null +++ b/desktop_app/ui/assets/banner.txt @@ -0,0 +1,7 @@ + + _ + | | + _ __ ___ ___ ___| |__ + | '_ ` _ \ / _ \/ __| '_ \ + | | | | | | __/\__ \ | | | + |_| |_| |_|\___||___/_| |_| diff --git a/desktop_app/ui/assets/pair_screen.tcss b/desktop_app/ui/assets/pair_screen.tcss new file mode 100644 index 0000000..6edb484 --- /dev/null +++ b/desktop_app/ui/assets/pair_screen.tcss @@ -0,0 +1,39 @@ +PairScreen { + align: center middle; +} + +EffectLabel { + min-width: 50; + text-align: center; + text-style: bold; +} + +#middle { + border: $success ascii; + width: 50; + height: 28; + + padding: 0 1 1 1; + + Static { + min-width: 100%; + text-align: center; + } + + DataTable { + margin-top: 1; + } + + LoadingIndicator { + height: 1; + margin-top: 1; + } + + DataTable { + height: 5; + } + + Button { + margin: 2 0 0 1; + } +} \ No newline at end of file diff --git a/desktop_app/ui/screens/pair_screen.py b/desktop_app/ui/screens/pair_screen.py new file mode 100644 index 0000000..d75bd5a --- /dev/null +++ b/desktop_app/ui/screens/pair_screen.py @@ -0,0 +1,32 @@ +from textual.screen import Screen +from textual.containers import VerticalScroll, Vertical +from textual.widgets import Static, Button, LoadingIndicator, DataTable +from textualeffects.widgets import EffectLabel + + +class PairScreen(Screen): + CSS_PATH = "../assets/pair_screen.tcss" + + def compose(self): + + with Vertical(id="middle") as center_window: + center_window.border_title = "Pair a Node" + + with open("ui/assets/banner.txt", "r") as f: + yield EffectLabel(f.read(), effect="Print") + + yield Static("Make sure your mesh network node is powered and ready to pair. When you're ready, click the \"Pair\" button to connect to the mesh!") + + table = DataTable() + table.add_columns("[b]Address", "RSSI") + yield table + + table.add_row("hi", "[green]󰢽[/] -34 dBm") + table.add_row("hi", "[green]󰢽[/] -52 dBm") + table.add_row("hi", "[yellow]󰢽[/] -64 dBm") + table.add_row("hi", "[red]󰢼[/] -98 dBm") + table.add_row("hi", "[red]󰢼[/] -101 dBm") + + yield LoadingIndicator() + + yield Button("Pair", disabled=True, variant="success", id="pair-btn") \ No newline at end of file