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() var.set(True) # Default to all enabled 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()