Redesign app, prepare for 3.1.0 release

This commit is contained in:
2025-06-16 12:21:45 +02:00
parent d6a5e90b3c
commit 3a6cd6af3d
19 changed files with 599 additions and 562 deletions

View File

@@ -1,101 +1,124 @@
<MainScreen>:
on_pre_enter: root.reset()
name: "main"
canvas.before:
Color:
rgba: (10,10,10,0.1)
Rectangle:
size: self.size
pos: self.pos
GridLayout:
FloatLayout:
Label:
pos_hint: {"y":0.4}
text: "READOUT"
font_size: 40
color: (0, 113, 0, 1)
bold: True
GridLayout:
MDFloatLayout:
MDGridLayout:
cols: 1
pos_hint: {'x': 0, 'y': 0.4}
MDLabel:
text: "READOUT"
font_size: 40
halign: 'center'
valign: 'center'
pos_hint: {'center_x': 0, 'center_y': 0}
bold: True
MDGridLayout:
cols:4
size_hint: 0.8, 0.3
pos_hint: {"x":0.1, "y":0.4}
Label:
MDLabel:
text: "Sensor 1: "
font_size: 20
Label:
MDLabel:
id: sensor1
text: ""
size_hint: 1, 1
halign: 'left'
text_size: self.size
Label:
MDLabel:
text: "Sensor 2: "
font_size: 20
Label:
MDLabel:
id: sensor2
text: ""
size_hint: 1, 1
halign: 'left'
text_size: self.size
Label:
MDLabel:
text: "Sensor 3: "
font_size: 20
Label:
MDLabel:
id: sensor3
text: ""
size_hint: 1, 1
halign: 'left'
text_size: self.size
Label:
MDLabel:
text: "Sensor 4: "
font_size: 20
Label:
MDLabel:
id: sensor4
text: ""
size_hint: 1, 1
halign: 'left'
text_size: self.size
Button:
MDFillRoundFlatButton:
text: "Connect"
size_hint: 0.2, 0.1
pos_hint: {"x": 0.5, "y": 0.05}
background_color: (255, 0, 0, 0.6)
size_hint: 0.15, 0.09
pos_hint: {"x": 0.03, "y": 0.05}
on_release:
root.start()
Button:
MDFillRoundFlatButton:
text: "Disconnect"
size_hint: 0.2, 0.1
pos_hint: {"x": 0.7, "y": 0.05}
background_color: (255, 0, 0, 0.6)
size_hint: 0.15, 0.09
pos_hint: {"x": 0.2, "y": 0.05}
on_release:
root.end()
Button:
MDFillRoundFlatButton:
text: "Back"
size_hint: 0.3, 0.1
pos_hint: {"x":0.05, "y":0.05}
background_color: (255, 0, 0, 0.6)
size_hint: 0.15, 0.09
pos_hint: {"right": 0.95, "y":0.05}
md_bg_color: app.theme_cls.primary_dark
on_release:
root.end()
app.root.current = "home"
root.manager.transition.direction = "left"
ToggleButton:
id: mode_selector
MDGridLayout:
cols: 2
size_hint: 0.15, 0.1
pos_hint: {"x":0.1, "y":0.2}
text: "Normal Mode" if self.state == "normal" else "Fast Mode"
on_text: root.switch_mode(mode_selector.text)
background_color: (255,0,0,0.6) if self.state == "normal" else (0,0,255,0.6)
Button:
pos_hint: {"x":0.1, "y":0.15}
MDLabel:
text: "Fast Mode"
valign: "center"
MDSwitch:
id: mode_selector
on_active: root.switch_mode()
icon_active: "check"
MDFillRoundFlatButton:
text: "Configuration"
size_hint: 0.15, 0.1
pos_hint: {"x":0.7, "y":0.2}
background_color: (255, 0, 0, 0.6)
size_hint: 0.1, 0.07
pos_hint: {"x":0.45, "y":0.06}
md_bg_color: app.theme_cls.accent_dark
on_release:
root.end()
app.root.current = "program"
root.manager.transition.direction = "down"
Label:
id: status
text: "Status will appear here"
font_size: 10
pos_hint: {"x":0.4, "y": 0.3}
MDGridLayout:
size_hint: 0.2, None
spacing: 0
padding: 0
cols: 1
pos_hint: {'right': 0.95, 'top': 0.95}
MDLabel:
id: status
text: "Status will appear here"
font_size: 10
halign: 'right'
MDGridLayout:
size_hint: None, None
spacing: 0
padding: 0
cols: 1
pos_hint: {'right': 0.95, 'top': 0.925}
MDLabel:
id: port
text: "Port: Not connected"
font_size: 10
halign: 'right'

View File

@@ -3,8 +3,9 @@ from time import time
from typing import List, override
from kivymd.uix.screen import MDScreen
from kivy.lang import Builder
from gui.popups.popups import SingleRowPopup, TwoActionPopup, empty_func
from kivy.clock import Clock, ClockEvent
from kivymd.uix.button import MDFlatButton
from kivymd.uix.dialog import MDDialog
import queue
import threading
@@ -53,7 +54,7 @@ class ReaderThread(threading.Thread):
# Hook to output stream
if self._instructions.hook_main():
# We are now hooked to the stream (i.e. data is synced)
synced_queue.put(["HOOK"])
synced_queue.put(["HOOK", self._com.get_comport()])
# making it exit using the stop function
while self._run:
@@ -70,17 +71,22 @@ class ReaderThread(threading.Thread):
for i in range(4):
# The slicing that happens here uses offsets automatically calculated from the sensor id
# This allows for short code
data.append(
f"Tadc: {
self._decoder.decode_int(received[12 * i:12 * i + 4])
}\nTemp: {
round(self._decoder.decode_float(received[12 * i + 5:12 * i + 11]) * 1000) / 1000
}°C\nDC: {
round((self._decoder.decode_float_long(received[48 + 5 * i: 52 + 5 * i]) / 65535.0 * 100) * 1000) / 1000
}%"
)
try:
data.append(
f"Tadc: {
self._decoder.decode_int(received[12 * i:12 * i + 4])
}\nTemp: {
round(self._decoder.decode_float(received[12 * i + 5:12 * i + 11]) * 1000) / 1000
}°C\nDC: {
round((self._decoder.decode_float_long(received[48 + 5 * i: 52 + 5 * i]) / 65535.0 * 100) * 1000) / 1000
}%"
)
except:
data.append("Bad data")
# Calculate the frequency of updates
data.append(str(round((1 / (time() - start_time)) * 1000) / 1000) + " Hz")
data.append(
str(round((1 / (time() - start_time)) * 1000) / 1000) + " Hz"
)
synced_queue.put(data)
else:
# Send error message to the UI updater
@@ -104,6 +110,30 @@ class MainScreen(MDScreen):
# Set some variables
self._com = com
self._event = None
self._fast_mode = False
self.connection_error_dialog = MDDialog(
title="Connection",
text="Failed to connect. Do you wish to retry?",
buttons=[
MDFlatButton(
text="Cancel",
on_release=lambda _: self.connection_error_dialog.dismiss(),
),
MDFlatButton(text="Retry", on_release=lambda _: self.start()),
],
)
self.mode_switch_error_dialog = MDDialog(
title="Mode Switch",
text="Failed to change mode. Please try again",
buttons=[
MDFlatButton(
text="Ok",
on_release=lambda _: self.mode_switch_error_dialog.dismiss(),
),
],
)
# Prepare the reader thread
self._prepare_reader()
@@ -115,36 +145,31 @@ class MainScreen(MDScreen):
def _prepare_reader(self):
self._reader = ReaderThread()
self._reader.setDaemon(True)
self._reader.daemon = True
self._reader.set_com(self._com)
# Start the connection to the micro-controller to read data from it.
# This also now starts the reader thread to continuously read out data
def start(self):
# Prevent running multiple times
self.connection_error_dialog.dismiss()
if self._has_connected:
return
self.ids.status.text = "Connecting..."
if self._com.connect():
print("Acquired connection")
print("[ COM ] Connection Acquired")
self._has_connected = True
self._has_run = True
if self._has_run:
self._prepare_reader()
# Start communication
self._reader.start()
print("Reader has started")
print("[ COM ] Reader has started")
self._event = Clock.schedule_interval(self._update_screen, 0.5)
else:
self.ids.status.text = "Connection failed"
TwoActionPopup().open(
"Failed to connect. Do you want to retry?",
"Cancel",
empty_func,
"Retry",
self.start,
)
self.connection_error_dialog.open()
# End connection to micro-controller and set it back to normal mode
def end(self, set_msg: bool = True):
@@ -166,10 +191,12 @@ class MainScreen(MDScreen):
self._com.close()
if set_msg:
self.ids.status.text = "Connection terminated"
self.ids.port.text = "Port: Not connected"
self._has_connected = False
print("Connection terminated")
# A helper function to update the screen. Is called on an interval
def _update_screen(self, dt):
def _update_screen(self, _):
update = []
try:
update = synced_queue.get_nowait()
@@ -182,8 +209,10 @@ class MainScreen(MDScreen):
if update[0] == "ERR_HOOK":
self.ids.status.text = "Hook failed"
self.end(False)
elif update[0] == "HOOK":
if len(update) == 2:
if update[0] == "HOOK":
self.ids.status.text = "Connected to controller"
self.ids.port.text = "Port: " + update[1]
else:
self.ids.sensor1.text = update[0]
self.ids.sensor2.text = update[1]
@@ -198,9 +227,10 @@ class MainScreen(MDScreen):
self.ids.sensor3.text = ""
self.ids.sensor4.text = ""
self.ids.status.text = "Status will appear here"
self.ids.port.text = "Port: Not connected"
# Switch the mode for the micro-controller
def switch_mode(self, new_mode: str):
def switch_mode(self):
# Store if we have been connected to the micro-controller before mode was switched
was_connected = self._has_connected
@@ -210,12 +240,12 @@ class MainScreen(MDScreen):
# Try to set the new mode
try:
if new_mode == "Normal Mode":
if self._fast_mode:
self._com.send("NM")
else:
self._com.send("FM")
except:
SingleRowPopup().open("Failed to switch modes")
self.mode_switch_error_dialog.open()
return
self.ids.status.text = "Mode set"