Files
frontend-dev/frontend/__main__.py
2025-10-14 13:59:42 +02:00

200 lines
7.5 KiB
Python

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:
self.show_device_components()
else:
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):
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)
elif selected_view == DISPLAY_TYPES.NUMBER_CIRCLE.value:
self.view = NumberCircleView(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.refresh(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))
self.update_calib()
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()