add python_toolkit

This commit is contained in:
2025-10-14 12:03:03 +02:00
parent 3e168d2074
commit 98794a36ed
5 changed files with 242 additions and 140 deletions

198
frontend/__main__.py Normal file
View File

@@ -0,0 +1,198 @@
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
import threading
from time import sleep, time
from statistics import mean
import pandas as pd
from python_toolkit.serial_reader import SerialReader
from python_toolkit.gui.connect import ConnectFrame
from .config import DEFAULT_CALIB_WEIGHT, DEFAULT_CALIB, DISPLAY_TYPES, MOV_AVG_DEFAULTS
from .views import *
class WeightApp(tk.Tk):
def __init__(self, weight_reader: SerialReader):
super().__init__()
self.recording = False
self.record_start = None
self.record_window = []
self.weight_reader = weight_reader
self.view = None
self.toolbar = tk.Frame(self, padx=10)
self.toolbar.pack(side=tk.LEFT)
#### Connection Settings ####
self.connection_settings = ConnectFrame(self.toolbar,
self.weight_reader,
self._on_connect,
pady=30)
#### Weight Reader Settings ####
self.reader_settings = tk.Frame(self.toolbar, pady=30)
self.reader_settings.pack()
self.calib_factor = tk.Frame(self.reader_settings)
self.calib_factor.pack()
self.calib_label = ttk.Label(self.calib_factor, text="Calibration Factor:")
self.calib_label.pack()
self.calib_weight = ttk.Combobox(self.calib_factor, values=[100], width=5)
self.calib_weight.set(100)
self.calib_weight.pack(side=tk.LEFT)
self.calib_measurements = ttk.Entry(self.calib_factor)
self.calib_measurements.insert(0, DEFAULT_CALIB)
self.calib_measurements.pack(side=tk.LEFT)
self.update_calib_button = ttk.Button(self.reader_settings, text="Update Calibration", command=self.update_calib)
self.update_calib_button.pack()
self.view_type = tk.Frame(self.toolbar, pady=20)
self.view_type.pack()
self.view_type_label = ttk.Label(self.view_type, text="Visual:")
self.view_type_label.pack(side=tk.LEFT)
self.view_type_select = ttk.Combobox(self.view_type, values=[t.value for t in DISPLAY_TYPES])
self.view_type_select.set(DISPLAY_TYPES.NUMBER.value)
self.view_type_select.pack(side=tk.LEFT)
self.view_type_update = ttk.Button(self.view_type, text="Refresh", command=self.update_view)
self.view_type_update.pack()
self.recording_frame = tk.Frame(self.toolbar, pady=20)
self.recording_frame.pack()
self.record_button = ttk.Button(self.recording_frame, text="Record", command=self.trigger_record)
self.record_button.pack()
#### Display ####
self.update_view()
self.update_weight_display()
self.hide_device_components()
self.focus_force()
def show_device_components(self):
self.connection_settings.pack_forget()
self.reader_settings.pack_forget()
self.view_type.pack_forget()
self.recording_frame.pack_forget()
self.view.pack_forget()
self.reader_settings.pack()
self.view_type.pack()
self.recording_frame.pack()
self.view.pack()
def hide_device_components(self):
self.connection_settings.pack_forget()
self.reader_settings.pack_forget()
self.view_type.pack_forget()
self.recording_frame.pack_forget()
self.view.pack_forget()
self.connection_settings.pack()
def _on_connect(self, connected):
if connected:
# port = self.port.get()
# self.weight_reader.connect(port)
# self.connect_button.config(text="Disconnect")
self.show_device_components()
else:
# self.weight_reader.disconnect()
# self.connect_button.config(text="Connect")
self.hide_device_components()
def update_devices(self):
self.weight_reader.scan_devices()
self.port.config(values=self.weight_reader.ports)
if len(self.weight_reader.ports) > 0:
self.port.set(self.weight_reader.ports[0])
def update_calib(self):
print(self.calib_weight.get(), self.calib_measurements.get())
self.weight_reader.calib_factor = float(self.calib_weight.get()) / float(self.calib_measurements.get())
def update_view(self):
selected_view = self.view_type_select.get()
if self.view is not None:
self.view.pack_forget()
if selected_view == DISPLAY_TYPES.NUMBER.value:
self.view = NumberView(self,
tare_command=self.weight_reader.tare,
calibrate_command=self.calibrate,
padx=50)
self.view.pack(side=tk.RIGHT)
elif selected_view == DISPLAY_TYPES.CIRCLE.value:
self.view = CircleView(self,
tare_command=self.weight_reader.tare,
calibrate_command=self.calibrate,
padx=50)
self.view.pack(side=tk.RIGHT)
else:
raise Exception(f"View {selected_view} not found.")
def update_weight_display(self):
weight = self.weight_reader.value
if self.recording:
self.record_window.append((time() - self.record_start, weight))
self.view.update_weight(weight)
self.after(20, self.update_weight_display)
def calibrate(self):
showinfo("Calibration", "Remove all weights.")
self.weight_reader.reset()
sleep(2)
showinfo("Calibration", "Place Weight")
self.weight_reader.calibrating = True
sleep(30)
self.weight_reader.calibrating = False
print(self.weight_reader.calib_window)
self.calib_measurements.delete(0, 'end')
self.calib_measurements.insert(0, mean(self.weight_reader.calib_window))
def trigger_record(self):
if self.recording:
self.record_button.config(text="Record")
self.recording = False
file_path = tk.filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
title="Save Recorded Weights"
)
if file_path:
pd.DataFrame({
'Timestamp': [t for t, _ in self.record_window],
'Weight': [w for _, w in self.record_window],
}).to_csv(file_path, index=False)
self.record_window = []
else:
self.record_button.config(text="Stop")
self.recording = True
self.record_window = []
self.record_start = time()
def main():
weight_reader = SerialReader(DEFAULT_CALIB_WEIGHT,
DEFAULT_CALIB,
window_size=MOV_AVG_DEFAULTS['window_size'],
reset_threshold=MOV_AVG_DEFAULTS['reset_threshold'],
ignore_samples=MOV_AVG_DEFAULTS['ignore_samples'])
threading.Thread(target=weight_reader.read_weights, daemon=True).start()
app = WeightApp(weight_reader)
app.protocol("WM_DELETE_WINDOW", lambda: on_closing(app, weight_reader))
app.mainloop()
def on_closing(app, weight_reader):
weight_reader.stop()
app.destroy()
if __name__ == "__main__":
main()