204 lines
7.4 KiB
Python
204 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 CombinedView
|
|
|
|
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:
|
|
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 not None:
|
|
self.view.update_views(selected_types)
|
|
else:
|
|
self.view = CombinedView(self,
|
|
tare_command=self.weight_reader.tare,
|
|
calibrate_command=self.calibrate)
|
|
|
|
self.update_weight_display()
|
|
|
|
|
|
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()
|