Przeglądaj źródła

Calibrate-Button für Magnetsensor

Janek Fabian Franz 3 lat temu
rodzic
commit
5c2a7a49f4

BIN
raspberry-pi/SourceSansPro-Semibold.otf


+ 99 - 0
raspberry-pi/graph.py

@@ -0,0 +1,99 @@
+import time, math
+
+import tkinter as tk
+from PIL import Image, ImageDraw, ImageTk, ImageFont
+
+
+class Graph(tk.Canvas):
+  def __init__(self, root, scale=(-50, 450), **kwargs):
+    self.root = root
+    tk.Canvas.__init__(self, root, **kwargs)
+    self.image = self.create_image(0, 0, image=None, anchor='nw')
+    self.height = self.winfo_reqheight()
+    self.width = self.winfo_reqwidth()
+    # store last point of plot to connect the lines
+    self.lastPoints = [(0, 0)] * 3
+    # scale contains the (min, max) values of both axes
+    self.scale = scale
+    # appearance of the plots
+    self.colors = [(100, 255, 100, 255), (255, 100, 100, 255) ,(100, 100, 255, 255)]
+    self.font = ImageFont.truetype("gui/SourceSansPro-Semibold.otf", 12)
+    self.lineWidth = 1
+    
+    # the background contains all static elements
+    self.drawBackground()
+    # the plots will be drawn on a separate canvas
+    self.canvas = self.bg.copy()
+
+    self.bind("<Configure>", self.on_resize)
+
+  def drawBackground(self):
+    self.bg = Image.new('RGB', (self.width, self.height), (0,10,0))
+    draw = ImageDraw.Draw(self.bg)
+    # draw x and y axis
+    axes = self.pointToCoord((0, 0))
+    draw.line([(0, axes[1]), (self.width, axes[1])], (60,127,127), self.lineWidth)
+    draw.line([(axes[0], 0), (axes[0], self.height)], (60,127,127), self.lineWidth)
+
+    step  = 10**int(math.log10(self.scale[1]-self.scale[0]))
+    begin = math.floor(self.scale[0] / step) * step
+    end   = math.ceil(self.scale[1] / step) * step
+    halfStep = int(step / 2)
+    # label x and y axis
+    draw.text(self.pointToCoord(((end+begin)/2+25, -30)), "x in mm", font=self.font, align='center')
+    draw.text(self.pointToCoord((-45, (end+begin)/2+25)), "y in mm", font=self.font, align='left')
+    for p in range(begin, end, halfStep):
+      tickPosX = self.pointToCoord((p, 0))
+      tickPosY = self.pointToCoord((0, p))
+      #draw grid
+      draw.line([(tickPosX[0], self.height), (tickPosX[0], 0)], (60,127,60), 1)
+      draw.line([(self.width, tickPosY[1]), (0, tickPosY[1])], (60,127,60), 1)
+    for p in range(begin, end, step):
+      tickPosX = self.pointToCoord((p, 0))
+      tickPosY = self.pointToCoord((0, p))
+      # draw ticks
+      draw.line([(tickPosX[0], tickPosX[1]+self.lineWidth*3), (tickPosX[0], tickPosX[1]-self.lineWidth*3)], (60,127,100), int(self.lineWidth/2))
+      draw.line([(tickPosY[0]+self.lineWidth*3, tickPosY[1]), (tickPosY[0]-self.lineWidth*3, tickPosY[1])], (60,127,100), int(self.lineWidth/2))
+      # draw tick labels
+      draw.text((tickPosX[0]+3, tickPosX[1]+self.lineWidth*1), str(p), font=self.font)
+      if p != 0:
+        draw.text((tickPosY[0]+self.lineWidth*2, tickPosY[1]), str(p), font=self.font, align='right')
+
+  def on_resize(self,event):
+    self.width = max(100, event.width-4)
+    self.height = max(100, event.height-4)
+    self.lineWidth = int(max(min(self.width,self.height) / 100, 1))
+    # resize the canvas 
+    self.canvas = self.canvas.resize((self.width, self.height))
+    self.drawBackground()
+    self.canvas = Image.blend(self.canvas, self.bg, 1)
+
+  # convert physical space to screen space
+  def pointToCoord(self, point):
+    return ((point[0] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.width, 
+      self.height - (point[1] - self.scale[0]) / (self.scale[1]-self.scale[0]) * self.height - 1)
+
+  def update(self, data):
+    # load first point of line
+    coord = [[self.pointToCoord(p)] for p in self.lastPoints if p]
+    newPoints = False
+    # append new points
+    for i in range(len(data)):
+      for point in data[i]:
+        coord[i].append(self.pointToCoord(point))
+        self.lastPoints[i] = point
+        newPoints = True
+
+    self.canvas = Image.blend(self.canvas, self.bg, 1/100)
+    if newPoints:
+      #fade out old lines by mixing with background
+      draw = ImageDraw.Draw(self.canvas)
+      for i in range(len(coord)):
+        draw.line(coord[i], fill=self.colors[i], width=self.lineWidth+2, joint='curve')
+
+    # draw to tk.Canvas
+    self.photo = ImageTk.PhotoImage(self.canvas)
+    self.itemconfig(self.image, image=self.photo)
+
+  def clear(self):
+    self.canvas = self.bg.copy()

+ 61 - 0
raspberry-pi/logScreen.py

@@ -0,0 +1,61 @@
+import tkinter as tk
+from tkinter.ttk import Progressbar
+
+import logHandler
+
+class LogScreen(tk.Frame):
+  def __init__(self, root):
+    self.root = root
+    tk.Frame.__init__(self, root)
+
+    self.log_handler = logHandler.get_log_handler()
+
+    self.text = tk.Frame(self,relief="sunken",borderwidth=1)
+    self.text.pack(expand=True,fill=tk.BOTH)
+
+    self.disable_refresh = False
+
+    self.y_scroll = tk.Scrollbar(self.text, width=32)
+    self.y_scroll.pack(side="right", fill="y")
+
+    self.x_scroll = tk.Scrollbar(self.text, orient='horizontal', width=32)
+    self.x_scroll.pack(side="bottom", fill="x")
+
+    self.textfield = tk.Listbox(self.text, yscrollcommand=self.y_scroll.set, xscrollcommand=self.x_scroll.set)
+    self.textfield.pack(side="left",expand=True, fill=tk.BOTH)
+
+    self.y_scroll.config(command=self.textfield.yview)
+    self.x_scroll.config(command=self.textfield.xview)
+
+    self.quit_button = tk.Button(self, text="Close", command=self.close, height=2, width = 10)
+    self.quit_button.pack(side="right", fill="both")
+
+    self.disable_refresh_button = tk.Button(self,text="disable refreshing", command=self.toggle_refreshing, height=2, width=20, relief="raised", bg="green", activebackground="#00dd00", fg="white")
+    self.disable_refresh_button.pack(side="right", fill="both")
+
+    for element in self.log_handler.get_log_list():
+      self.textfield.insert(tk.END, element)
+    self.log_handler.get_new_items()
+
+    root.bind('<Escape>', self.close)
+
+  def update(self):
+    if not self.root.winfo_exists():
+      return
+    if not self.disable_refresh:
+      lines = self.log_handler.get_new_items()
+      if lines:
+        for element in lines:
+          self.textfield.insert(tk.END, element)
+        self.textfield.see("end")
+
+  def close(self):
+    if self.root.winfo_exists():
+      self.root.destroy()
+
+  def toggle_refreshing(self):
+    self.disable_refresh = not self.disable_refresh
+    if self.disable_refresh:
+      self.disable_refresh_button.config(text="Enable refresh", relief="sunken", background="#ee0000", activebackground="#ff0000")
+    else:
+      self.disable_refresh_button.config(text="Disable refresh", relief="raised", background="green", activebackground="#00dd00")

+ 233 - 0
raspberry-pi/mainWindow.py

@@ -0,0 +1,233 @@
+import time
+import queue
+
+import tkinter as tk
+import numpy as np
+
+from gui.popup import CalibrationPopUp
+from gui.graph import Graph
+from gui.logScreen import LogScreen
+from sensors.opticalSensor import OpticalSensor
+import logHandler
+
+
+class MainWindow(tk.Frame):
+  def __init__(self, root, conf, ac_sensor, opt_sensor, mag_sensor):
+    self.root         = root
+    self.conf         = conf
+    self.ac_sensor    = ac_sensor
+    self.opt_sensor   = opt_sensor
+    self.mag_sensor   = mag_sensor
+    self.log_handler  = logHandler.get_log_handler()
+    self.popup_window = None
+    self.log_window   = None
+    self.mainWindow   = None
+
+    tk.Frame.__init__(self, root)
+    # data plot at left side
+    self.graph = Graph(self)
+    self.graph.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
+    # frame at right side
+    self.controls = tk.Frame(self, borderwidth=4)
+    self.controls.pack(fill=tk.BOTH, side=tk.RIGHT)
+    self.controlsUpdateTime = 0
+
+    self.ac_dro_val_sums = np.ndarray((4), dtype=np.float)
+    self.ac_dro_val_count = 0
+    self.ac_dro_x = tk.StringVar()
+    self.ac_dro_y = tk.StringVar()
+    self.ac_dro_t1 = tk.StringVar()
+    self.ac_dro_t2 = tk.StringVar()
+    self.ac_label = tk.Label(self.controls, text="Acoustic Sensor", anchor="c", font=("Helvatica", 10, 'bold'))
+    self.ac_label.pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.ac_dro_x, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.ac_dro_y, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.ac_dro_t1, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.ac_dro_t2, anchor="nw").pack(side="top", fill="both", expand=False)
+
+    self.opt_dro_val_sums = np.ndarray((4), dtype=np.float)
+    self.opt_dro_val_count = 0
+    self.opt_dro_x = tk.StringVar()
+    self.opt_dro_y = tk.StringVar()
+    self.opt_dro_offset = tk.StringVar()
+    self.opt_dro_size   = tk.StringVar()
+    self.opt_label = tk.Label(self.controls, text="Optical Sensor", anchor="c", font=("Helvatica", 10, 'bold'))
+    self.opt_label.pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.opt_dro_x, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.opt_dro_y, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.opt_dro_offset, anchor="nw").pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.opt_dro_size, anchor="nw").pack(side="top", fill="both", expand=False)
+
+    self.mag_label = tk.Label(self.controls, text="Magnetic Sensor", anchor="c", font=("Helvatica", 10, 'bold'))
+    self.mag_label.pack(side="top", fill="both", expand=False)
+    self.mag_dro_x = tk.StringVar()
+    self.mag_dro_y = tk.StringVar()
+    tk.Label(self.controls, textvariable=self.mag_dro_x, anchor = "nw").pack(side = "top", fill = "both", expand = False)
+    tk.Label(self.controls, textvariable=self.mag_dro_x, anchor = "nw").pack(side = "top", fill = "both", expand = False)
+
+    self.quit_button = tk.Button(self.controls, text="Quit", command=self.root.destroy, height=2, foreground="red")
+    self.quit_button.pack(side="bottom", fill="both")
+
+    self.calibrateac_button = tk.Button(self.controls, text="Calibrate AC", command=self.calibrate_ac, height=4)
+    self.calibrateac_button.pack_forget()
+
+    self.calibratemc_button = tk.Button(self.controls, text = "Calibrate MC", command = self.calibrate_mc,height = 4)
+    self.calibratemc_button.pack_forget()
+
+    self.clear_button = tk.Button(self.controls, text="Clear graph", command=self.graph.clear, height=4)
+    self.clear_button.pack_forget()
+
+    self.logscreen_button = tk.Button(self.controls, text="Log", command=self.open_log, height=4)
+    self.logscreen_button.pack_forget()
+
+    self.menu_button = tk.Button(self.controls, text="Menu", command=self.menu, height=4)
+    self.menu_button.pack(side="bottom", fill="both")
+    
+    self.menu_back_button = tk.Button(self.controls, text="Back", command=self.back, height=4)
+    self.menu_back_button.pack_forget()
+
+  def update(self):
+    if not self.root.winfo_exists():
+      return
+
+    ac_positions = []
+    # aggregate measurements
+    while self.ac_sensor.queue.qsize() > 0:
+      name, data = self.ac_sensor.queue.get()
+      if name == "data":
+        ac_positions.append(data[0:2])
+        self.ac_dro_val_sums += data
+        self.ac_dro_val_count += 1
+
+    opt_positions = []
+    while self.opt_sensor.queue.qsize() > 0:
+      name, data = self.opt_sensor.queue.get()
+      if name == "data":
+        opt_positions.append(data[0:2])
+        self.opt_dro_val_sums += data
+        self.opt_dro_val_count += 1
+    
+    mag_positions = []
+    while self.mag_sensor.queue.qsize() > 0:
+      name, data = self.mag_sensor.queue.get()
+      if name == "data":
+        mag_positions.append(data[0:2])
+        self.mag_dro_val_sums += data
+        self.mag_dro_val_count += 1
+    
+    # graph shows all values as a line
+    self.graph.update([ac_positions, opt_positions, mag_positions])
+
+    # update status color
+    if self.ac_sensor.dummyActive:
+      self.ac_label.config(fg="white", bg="red")
+    elif len(ac_positions) > 0:
+      self.ac_label.config(fg="white", bg="green")
+    else:
+      self.ac_label.config(fg="black", bg="yellow")
+
+
+    if not self.opt_sensor.success:
+      self.opt_label.config(fg="white", bg="red")
+    elif len(opt_positions) > 0:
+      self.opt_label.config(fg="white", bg="green")
+    else:
+      self.opt_label.config(fg="black", bg="yellow")
+
+    # if not self.mag_sensor.:
+    #  self.mag_label.config(fg="white", bg="red")
+    # elif len(mag_positions) > 0:
+    #  self.mag_label.config(fg="white", bg="green")
+    # else:
+    #  self.mag_label.config(fg="black", bg="yellow")
+
+
+    # readouts will only be updated so often
+    if self.controlsUpdateTime + 0.4 < time.time():
+      self.controlsUpdateTime = time.time()
+      # they display the average of all values
+      if self.ac_dro_val_count > 0:
+        self.ac_dro_val_sums /= self.ac_dro_val_count
+      else:
+        self.ac_dro_val_sums.fill(0)
+
+      self.ac_dro_x.set("X: {:.1f} mm".format(self.ac_dro_val_sums[0]))
+      self.ac_dro_y.set("Y: {:.1f} mm".format(self.ac_dro_val_sums[1]))
+      self.ac_dro_t1.set("t1: {:.3f} ms".format(self.ac_dro_val_sums[2]/1000))
+      self.ac_dro_t2.set("t2: {:.3f} ms".format(self.ac_dro_val_sums[3]/1000))
+
+      self.ac_dro_val_sums.fill(0)
+      self.ac_dro_val_count = 0
+
+      if self.opt_dro_val_count > 0:
+        self.opt_dro_val_sums /= self.opt_dro_val_count
+      else:
+        self.opt_dro_val_sums.fill(0)
+
+      self.opt_dro_x.set("X: {:.1f} mm".format(self.opt_dro_val_sums[0]))
+      self.opt_dro_y.set("Y: {:.1f} mm".format(self.opt_dro_val_sums[1]))
+      self.opt_dro_offset.set("offset: {:.1f} %".format(self.opt_dro_val_sums[2]*100))
+      self.opt_dro_size.set("size: {:.1f} %".format(self.opt_dro_val_sums[3]*100))
+
+      self.opt_dro_val_sums.fill(0)
+      self.opt_dro_val_count = 0
+
+    if self.popup_window:
+      self.popup_window.update()
+    
+    if self.log_window:
+      self.log_window.update()
+
+    self.root.after(30, self.update)
+
+  def calibrate_ac(self):
+    self.ac_sensor.start_calibration()
+    if not self.popup_window or not self.pu_root.winfo_exists():
+      # create new window
+      self.pu_root = tk.Toplevel(self.root)
+      self.pu_root.wm_transient(self.root)
+      self.pu_root.wm_title("Acoustic Sensor Calibration")
+      # make it centered
+      x = (self.pu_root.winfo_screenwidth()  - 500) / 2
+      y = (self.pu_root.winfo_screenheight() - 200) / 2
+      self.pu_root.geometry(f'500x200+{int(x)}+{int(y)}')
+      # deactivate mainWindow
+      self.pu_root.grab_set()
+      self.popup_window = CalibrationPopUp(self.pu_root, self.ac_sensor.calibration_state, self.conf)
+      self.popup_window.pack(side="top", fill="both", expand=True)
+
+  def calibrate_mc(self):
+    pass
+
+  def open_log(self):
+    #create new window
+    self.log_root = tk.Toplevel(self.root)
+    self.log_root.wm_transient(self.root)
+    self.log_root.wm_title("Logs")
+    #center
+    x = (self.log_root.winfo_screenwidth()  - 780) / 2
+    y = (self.log_root.winfo_screenheight() - 400) / 2
+    self.log_root.geometry(f'780x400+{int(x)}+{int(y)}')
+    # deactivate mainWindow
+    self.log_root.grab_set()
+    self.log_window = LogScreen(self.log_root)
+    self.log_window.pack(side="top", fill="both", expand=True)
+
+  
+  # Menu Button
+  def menu(self):
+    self.menu_back_button.pack(side="bottom", fill="both")
+    self.calibrateac_button.pack(side="bottom", fill="both")
+    self.calibratemc_button.pack(side="bottom", fill="both")
+    self.clear_button.pack(side="bottom", fill="both")
+    self.logscreen_button.pack(side="bottom", fill="both")
+    self.menu_button.pack_forget()
+
+  #Back Button
+  def back(self):
+    self.calibrateac_button.pack_forget()
+    self.calibratemc_button.pack_forget()
+    self.clear_button.pack_forget()
+    self.logscreen_button.pack_forget()
+    self.menu_button.pack(side="bottom", fill="both")
+    self.menu_back_button.pack_forget()

+ 46 - 0
raspberry-pi/popup.py

@@ -0,0 +1,46 @@
+import tkinter as tk
+from tkinter.ttk import Progressbar
+import pyglet
+
+class CalibrationPopUp(tk.Frame):
+  def __init__(self, root, calibration_state, conf):
+    self.root = root
+    self.font = pyglet.font.add_file("gui/SourceSansPro-Semibold.otf")
+    self.calibration_state = calibration_state
+    tk.Frame.__init__(self, root)
+    self.pendingClose = False
+    self.conf = conf
+
+
+    self.instruction = tk.Label(self,text="Start Calibration", anchor="c",font=("SourceSansPro-Semibold", 18))
+    self.instruction.pack(side="top", fill="both", expand=True)
+    button = tk.Button(self,text="OK", command=self.calibration_state.next_state_gui, anchor="c",height=1,width=5)
+    button.pack(side="top", fill="both", expand=True)
+    self.cs = Progressbar(self, orient='horizontal', mode='determinate')
+    self.cs.pack(side="top", fill="both", expand=True)
+
+    root.bind('<Escape>', self.close)
+  
+  def update(self):
+    if not self.root.winfo_exists():
+      return
+    # display captured value count as progress
+    self.cs['value'] = self.calibration_state.progress
+    # read state from state machine
+    if self.calibration_state.get_state() == self.calibration_state.WAITING_POS_1:
+      text = "Move gondola to [" + self.conf["ac_sensor"]["calibration_x_offset"] + " , " + self.conf["ac_sensor"]["calibration_y_offset_1"] + "]!"
+      self.instruction["text"] = text
+    elif self.calibration_state.get_state() == self.calibration_state.WAITING_POS_2:
+      text = "Move gondola to [" + self.conf["ac_sensor"]["calibration_x_offset"] + " , " + self.conf["ac_sensor"]["calibration_y_offset_2"] + "]!"
+      self.instruction["text"] = text
+    elif self.calibration_state.get_state() == self.calibration_state.CALIBRATION_DONE:
+      self.instruction["text"] = "Calibration Done!"
+      if not self.pendingClose:
+        self.pendingClose = True
+        self.root.after(1500, self.close)
+    else:
+      self.instruction["text"] = "Processing..."
+
+  def close(self):
+    if self.root.winfo_exists():
+      self.root.destroy()