From 05abcca6f572d1487585da4ba8bef32782f34b84 Mon Sep 17 00:00:00 2001 From: Jannes Date: Sat, 19 Apr 2025 23:06:54 +0200 Subject: [PATCH] init app --- filter_dev/app.py | 124 ++++++++++++++++++++++++++------------ filter_dev/ble.py | 13 ++++ filter_dev/filter/base.py | 10 ++- filter_dev/gui/device.py | 36 +++++++---- filter_dev/gui/slider.py | 19 +++--- 5 files changed, 143 insertions(+), 59 deletions(-) diff --git a/filter_dev/app.py b/filter_dev/app.py index 0b68050..3d2801b 100644 --- a/filter_dev/app.py +++ b/filter_dev/app.py @@ -3,30 +3,42 @@ from tkinter import ttk import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import asyncio + from .filter import * from .gui.device import Device +from .gui.slider import Slider + +class FilterDevApp(tk.Tk): + def __init__(self, loop: asyncio.EventLoop): + super().__init__() + self.loop = loop + self.protocol("WM_DELETE_WINDOW", self.close) + self.tasks = [] + self.tasks.append(loop.create_task(self.updater(1./2))) + self.tasks.append(loop.create_task(self.update_plot(1./20))) + self.tasks.append(loop.create_task(self.read_values(1./100))) -class FilterDevApp: - def __init__(self, root): self.filter = None - self.root = root - self.root.title("JannTers Filter Evaluation Tool") + self.title("JannTers Filter Evaluation Tool") # Create a frame for the plot and sliders - self.frame = tk.Frame(self.root) - self.frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + self.frame = tk.Frame(self) + self.frame.pack(side=tk.LEFT, fill=tk.BOTH) # Create a figure for plotting self.fig, self.ax = plt.subplots() self.canvas = FigureCanvasTkAgg(self.fig, master=self.frame) - self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) + self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH) # Create a frame for sliders - self.toolbar = tk.Frame(self.root) + self.toolbar = tk.Frame(self, width=200, padx=10) self.toolbar.pack(side=tk.RIGHT, fill=tk.Y) # Device Settings + self.connect_button = ttk.Button(self.toolbar, text="Connect", command=self.connect_disconnect) + self.connect_button.pack(pady=10) self.device_label = ttk.Label(self.toolbar, text="Device Name:") self.device_label.pack(pady=10) self.device_name = ttk.Entry(self.toolbar) @@ -34,9 +46,6 @@ class FilterDevApp: self.device_name.pack(pady=10) self.device = Device(self.device_name) - - self.connect_button = ttk.Button(self.toolbar, text="Connect Device", command=self.device.connect) - self.connect_button.pack(pady=10) # Filter Settings self.filter_type_label = ttk.Label(self.toolbar, text="Filter:") @@ -46,37 +55,36 @@ class FilterDevApp: self.filter_type_combobox.pack(pady=10) self.change_filter = ttk.Button(self.toolbar, text="Change Filter", command=self.update_filter) self.change_filter.pack(pady=10) - + # Objects - self.filter = MovAvg(self.device, self.toolbar, self.update_plot) + self.filter = MovAvg(self.device, self.toolbar, lambda: None) + self.filter.pack() - # Initial plot - self.update_plot() + async def update_plot(self, interval): + while await asyncio.sleep(interval, True): + if self.filter is None: + continue + + # Clear the current plot + self.ax.clear() - def update_plot(self, *args): - if self.filter is None: - return + # Get current values from sliders + df = self.filter() - # Clear the current plot - self.ax.clear() + # Generate data + x = df['timestamps'] + y1 = df['weights'] + y2 = df['filtered'] - # Get current values from sliders - df = self.filter() + # Plot the data + self.ax.plot(x, y1) + self.ax.plot(x, y2) + self.ax.set_xlabel("Time in ms") + self.ax.set_ylabel("Weight") + self.ax.grid() - # Generate data - x = df['timestamps'] - y1 = df['weights'] - y2 = df['filtered'] - - # Plot the data - self.ax.plot(x, y1) - self.ax.plot(x, y2) - self.ax.set_xlabel("Time in ms") - self.ax.set_ylabel("Weight") - self.ax.grid() - - # Draw the updated plot - self.canvas.draw() + # Draw the updated plot + self.canvas.draw() def update_filter(self): option = self.filter_type_combobox.get() @@ -85,7 +93,45 @@ class FilterDevApp: self.update_plot() + def connect_disconnect(self): + if self.device.is_connected: + self.device_label.pack(pady=10) + self.device_name.pack(pady=10) + self.connect_button.config(text="Connect") + self.connect_button.pack(pady=10) + + self.device.disconnect() + + else: + task = self.loop.create_task(self.device.connect()) + task.add_done_callback(self.connected) + + + def connected(self, *args): + if self.device.is_connected: + self.device_label.pack_forget() + self.device_name.pack_forget() + self.connect_button.config(text="Disconnect") + + self.filter.pack() + + async def read_values(self, interval): + await self.device.read_values(interval) + + async def updater(self, interval): + while await asyncio.sleep(interval, True): + self.update() + + + def close(self): + for task in self.tasks: + task.cancel() + self.loop.stop() + self.destroy() + + if __name__ == "__main__": - root = tk.Tk() - app = FilterDevApp(root) - root.mainloop() + loop = asyncio.new_event_loop() + app = FilterDevApp(loop) + loop.run_forever() + loop.close() diff --git a/filter_dev/ble.py b/filter_dev/ble.py index 91c813a..796c14d 100644 --- a/filter_dev/ble.py +++ b/filter_dev/ble.py @@ -6,8 +6,13 @@ import pandas as pd from argparse import ArgumentParser from tqdm.auto import tqdm +from config import MILLIS_UUID, WEIGHT_UUID + +READING_TIME = 30 + async def read_ble(reading_time=READING_TIME): device = await BleakScanner.find_device_by_name("Smaage") + assert device is not None, "No device found" timestamps = [] raw_weights = [] @@ -45,3 +50,11 @@ async def read_ble(reading_time=READING_TIME): print(df['weights'].describe()) plt.plot(timestamps, raw_weights) plt.show() + +if __name__ == '__main__': + parser = ArgumentParser() + parser.add_argument('-t', default=READING_TIME, type=int, help="reading time in sec") + + args = parser.parse_args() + + asyncio.run(read_ble(args.t)) \ No newline at end of file diff --git a/filter_dev/filter/base.py b/filter_dev/filter/base.py index a5a0222..1eeb469 100644 --- a/filter_dev/filter/base.py +++ b/filter_dev/filter/base.py @@ -33,4 +33,12 @@ class Filter: return df def filter(self, df: pd.DataFrame) -> pd.Series: - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() + + def pack(self): + for k, v in self.param_map.items(): + v.pack() + + def pack_forget(self): + for k, v in self.param_map.items(): + v.pack_forget() \ No newline at end of file diff --git a/filter_dev/gui/device.py b/filter_dev/gui/device.py index 56ba385..d969bf5 100644 --- a/filter_dev/gui/device.py +++ b/filter_dev/gui/device.py @@ -1,6 +1,7 @@ from bleak import BleakClient, BleakScanner import pandas as pd from tkinter.ttk import Entry +import asyncio from ..config import MILLIS_UUID, WEIGHT_UUID @@ -26,15 +27,28 @@ class Device: async def connect(self): self.device = await BleakScanner.find_device_by_name(self.device_name.get()) - assert self.device is not None, "No Device found!" - - async def read_values(self): - assert self.is_connected, "Not connected" - async with BleakClient(self.device.address) as client: - millis = await client.read_gatt_char(MILLIS_UUID) - millis = int.from_bytes(millis, byteorder='little') # Adjust based on your data format - weight = await client.read_gatt_char(WEIGHT_UUID) - weight = int.from_bytes(weight, byteorder='little') # Adjust based on your data format + return self.device is not None - self.timestamps.append(millis) - self.weights.append(weight) \ No newline at end of file + def disconnect(self): + self.device = None + + async def read_values(self, interval): + while await asyncio.sleep(interval, True): + if self.is_connected: + async with BleakClient(self.device.address) as client: + while await asyncio.sleep(interval, True): + millis = await client.read_gatt_char(MILLIS_UUID) + millis = int.from_bytes(millis, byteorder='little') # Adjust based on your data format + weight = await client.read_gatt_char(WEIGHT_UUID) + weight = int.from_bytes(weight, byteorder='little') # Adjust based on your data format + + self.timestamps.append(millis) + self.weights.append(weight) + + if not self.is_connected: + break + + + def clear_data(self): + self.timestamps = [] + self.weights = [] \ No newline at end of file diff --git a/filter_dev/gui/slider.py b/filter_dev/gui/slider.py index 6693c65..498797b 100644 --- a/filter_dev/gui/slider.py +++ b/filter_dev/gui/slider.py @@ -9,15 +9,10 @@ class Slider: command): self.command = command - self.frame = ttk.Frame(parent) - self.frame.pack(pady=10) + self.label = ttk.Label(parent, text=f"{label_text}: {int(initial_value)}") - self.label = ttk.Label(self.frame, text=f"{label_text}: {int(initial_value)}") - self.label.pack() - - self.slider = ttk.Scale(self.frame, from_=from_, to=to, orient='horizontal', command=self.update) + self.slider = ttk.Scale(parent, from_=from_, to=to, orient='horizontal', command=self.update) self.slider.set(initial_value) - self.slider.pack() def update(self, event=None): value = self.slider.get() @@ -25,4 +20,12 @@ class Slider: self.command() def get_value(self): - return self.slider.get() \ No newline at end of file + return self.slider.get() + + def pack(self, **kwargs): + self.label.pack(**kwargs) + self.slider.pack(**kwargs) + + def pack_forget(self): + self.label.pack_forget() + self.slider.pack_forget() \ No newline at end of file