Files
frontend-dev/frontend/__main__.py
2025-10-17 20:19:13 +02:00

205 lines
7.4 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.serial_mock import SerialMock
from python_toolkit.gui.connect import ConnectFrame
from .config import DEFAULT_CALIB_WEIGHT, DEFAULT_CALIB, DISPLAY_TYPES, MOV_AVG_DEFAULTS
from .views import MainView
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()
# Checkboxes for multiple view selection
self.view_vars = {}
for display_type in DISPLAY_TYPES:
if display_type == DISPLAY_TYPES.TIMER:
continue
var = tk.BooleanVar()
if display_type == DISPLAY_TYPES.NUMBER:
var.set(True) # Default to NUMBER view
checkbox = ttk.Checkbutton(self.view_type, text=display_type.name, variable=var)
checkbox.pack()
self.view_vars[display_type] = var
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:
if isinstance(self.weight_reader.serial, SerialMock):
self.weight_reader.add_mock_ui(self.reader_settings)
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):
# Get selected display types using logical AND operator
selected_types = DISPLAY_TYPES(0) # Start with empty flags
for display_type, var in self.view_vars.items():
if var.get(): # If checkbox is checked
selected_types |= display_type # Combine using bitwise OR
# Remove existing views
if self.view is None:
self.view = MainView(self,
tare_command=self.weight_reader.tare,
calibrate_command=self.calibrate)
self.view.update_views(selected_types)
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()