mirror of
https://github.com/janishutz/BiogasControllerApp.git
synced 2025-11-25 05:44:23 +00:00
Redesign app, prepare for 3.1.0 release
This commit is contained in:
@@ -4,11 +4,12 @@
|
||||
|
||||
Currently only the newest versions get security updates as security updates are also part of a release.
|
||||
|
||||
Only Version 3 is supported due to the poor code quality of V2.3.0 and below.
|
||||
Only Version 3.1 and later are supported due to the poor code quality of V2.3.0 and different UI before.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.0.0 | ✅ |
|
||||
| 3.1.X | ✅ |
|
||||
| 3.0.X | ✅ |
|
||||
| 2.3.0 | ❎ |
|
||||
| 2.2.0 | ❎ |
|
||||
| 2.1.0 | ❎ |
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# Load the config file
|
||||
import configparser
|
||||
import time
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read("./config.ini")
|
||||
|
||||
@@ -27,13 +28,17 @@ if config["Tariffs"]["impose_tariffs"] == "True":
|
||||
try:
|
||||
import tariff
|
||||
|
||||
tariff.set({
|
||||
tariff.set(
|
||||
{
|
||||
"kivy": int(config["Tariffs"]["kivy_rate"]),
|
||||
"serial": int(config["Tariffs"]["pyserial_rate"]),
|
||||
})
|
||||
}
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("You cannot evade the tariffs. I will impose impose a tariff of 1000000% on the launch of this app!")
|
||||
print(
|
||||
"You cannot evade the tariffs. I will impose impose a tariff of 1000000% on the launch of this app!"
|
||||
)
|
||||
time.sleep(2000000)
|
||||
|
||||
import os
|
||||
@@ -43,7 +48,6 @@ from lib.com import Com, ComSuperClass
|
||||
import lib.test.com
|
||||
|
||||
|
||||
|
||||
# Load config and disable kivy log if necessary
|
||||
if config["Dev"]["verbose"] == "True":
|
||||
pass
|
||||
@@ -52,13 +56,13 @@ else:
|
||||
|
||||
|
||||
# Load kivy modules. Kivy is the UI framework used. See https://kivy.org
|
||||
# from kivy.core.window import Window, Config
|
||||
from kivy.core.window import Window
|
||||
from kivy.uix.screenmanager import ScreenManager
|
||||
from kivymd.app import MDApp
|
||||
|
||||
|
||||
# Store the current app version
|
||||
app_version = f"{config['Info']['version']}{config['Info']['subVersion']}"
|
||||
# Set Window size
|
||||
Window.size = (int(config["UI"]["width"]), int(config["UI"]["height"]))
|
||||
|
||||
|
||||
# ╭────────────────────────────────────────────────╮
|
||||
@@ -66,13 +70,11 @@ app_version = f"{config['Info']['version']}{config['Info']['subVersion']}"
|
||||
# ╰────────────────────────────────────────────────╯
|
||||
# Import all the screens (= pages) used in the app
|
||||
from gui.home.home import HomeScreen
|
||||
from gui.credits.credits import CreditsScreen
|
||||
from gui.program.program import ProgramScreen
|
||||
from gui.about.about import AboutScreen
|
||||
from gui.main.main import MainScreen
|
||||
|
||||
|
||||
|
||||
# ╭────────────────────────────────────────────────╮
|
||||
# │ Screen Manager │
|
||||
# ╰────────────────────────────────────────────────╯
|
||||
@@ -84,29 +86,54 @@ class BiogasControllerApp(MDApp):
|
||||
|
||||
@override
|
||||
def build(self):
|
||||
com: ComSuperClass = Com()
|
||||
# Configure com
|
||||
conn = config["Connection"]
|
||||
filters = [x for x in conn["filters"].split(",")]
|
||||
com: ComSuperClass = Com(
|
||||
int(conn["baudrate"]) if conn["baudrate"] != None else 19200, filters
|
||||
)
|
||||
if config["Dev"]["use_test_library"] == "True":
|
||||
com = lib.test.com.Com()
|
||||
com = lib.test.com.Com(
|
||||
int(config["Dev"]["fail_sim"]),
|
||||
int(conn["baudrate"]) if conn["baudrate"] != None else 19200,
|
||||
filters,
|
||||
)
|
||||
com.set_port_override(conn["baudrate"])
|
||||
|
||||
self.theme_cls.theme_style = "Dark"
|
||||
self.theme_cls.primary_palette = "Green"
|
||||
self.theme_cls.accent_palette = "Lime"
|
||||
self.theme_cls.theme_style_switch_animation = True
|
||||
self.theme_cls.theme_style_switch_animation_duration = 0.8
|
||||
self.theme_cls.theme_style = (
|
||||
"Dark" if config["UI"]["theme"] == None else config["UI"]["theme"]
|
||||
)
|
||||
self.theme_cls.material_style = "M3"
|
||||
self.theme_cls.primary_palette = (
|
||||
"Green"
|
||||
if config["UI"]["primary_color"] == None
|
||||
else config["UI"]["primary_color"]
|
||||
)
|
||||
self.theme_cls.accent_palette = (
|
||||
"Lime"
|
||||
if config["UI"]["accent_color"] == None
|
||||
else config["UI"]["accent_color"]
|
||||
)
|
||||
self.theme_cls.theme_style_switch_animation = False
|
||||
|
||||
self.icon = "./BiogasControllerAppLogo.png"
|
||||
self.title = "BiogasControllerApp-" + app_version
|
||||
self.title = "BiogasControllerApp-V3.1.0"
|
||||
self.screen_manager.add_widget(HomeScreen(com, name="home"))
|
||||
self.screen_manager.add_widget(MainScreen(com, name="main"))
|
||||
self.screen_manager.add_widget(ProgramScreen(com, name="program"))
|
||||
self.screen_manager.add_widget(CreditsScreen(name="credits"))
|
||||
self.screen_manager.add_widget(AboutScreen(name="about"))
|
||||
return self.screen_manager
|
||||
|
||||
def change_theme(self):
|
||||
self.theme_cls.theme_style = (
|
||||
"Dark" if self.theme_cls.theme_style == "Light" else "Light"
|
||||
)
|
||||
|
||||
|
||||
# Disallow this file to be imported
|
||||
if __name__ == "__main__":
|
||||
print("""
|
||||
print(
|
||||
"""
|
||||
┏━━┓━━━━━━━━━━━━━━━━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━┏┓━┏┓━━━━━━━━┏━━━┓━━━━━━━━
|
||||
┃┏┓┃━━━━━━━━━━━━━━━━━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━┃┃━┃┃━━━━━━━━┃┏━┓┃━━━━━━━━
|
||||
┃┗┛┗┓┏┓┏━━┓┏━━┓┏━━┓━┏━━┓┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓┃┃━┃┃━┏━━┓┏━┓┃┃━┃┃┏━━┓┏━━┓
|
||||
@@ -116,9 +143,10 @@ if __name__ == "__main__":
|
||||
━━━━━━━━━━━┏━┛┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━┃┃━━
|
||||
━━━━━━━━━━━┗━━┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━┗┛━━
|
||||
|
||||
Version 3.0.0
|
||||
Version 3.1.0
|
||||
|
||||
=> Initializing....
|
||||
""")
|
||||
"""
|
||||
)
|
||||
BiogasControllerApp().run()
|
||||
print("\n => Exiting!")
|
||||
|
||||
20
changelog
20
changelog
@@ -1,11 +1,27 @@
|
||||
***CHANGELOG***
|
||||
V3.1.0
|
||||
- Completely redesigned User Interface using KivyMD
|
||||
- Added config option for themes
|
||||
|
||||
V3.0-beta
|
||||
- Redesigned GUI
|
||||
V3.0.1
|
||||
- Install script fixes
|
||||
- Packaging fixes
|
||||
|
||||
|
||||
V3.0.0
|
||||
- Small UI fixes
|
||||
- Consolidated multiple previously separate screens
|
||||
- Completely rewritten backend
|
||||
- Improved stability
|
||||
- Cleaned, documented code
|
||||
- Reduced overhead of connecting
|
||||
- Improved hooking reliability
|
||||
- Removed installer, simpler setup now possible
|
||||
- Removed official MacOS support as it didn't really work before anyway
|
||||
- Added additional config options
|
||||
- Improved linguistics
|
||||
- Bugfixes
|
||||
|
||||
|
||||
OLD VERSIONS
|
||||
------------
|
||||
|
||||
27
config.ini
27
config.ini
@@ -1,21 +1,24 @@
|
||||
[Ports]
|
||||
specificport = None
|
||||
[Connection]
|
||||
override_port = None
|
||||
baudrate = 19200
|
||||
# List the names as which the adapter cable will show up separated by commas below
|
||||
# For ENATECH, the below is likely correct.
|
||||
filters = USB-Serial Controller, Prolific USB-Serial Controller
|
||||
|
||||
[UI]
|
||||
sizeh = 600
|
||||
sizew = 800
|
||||
height = 600
|
||||
width = 800
|
||||
# Can be Dark or Light
|
||||
theme = Dark
|
||||
primary_color = Green
|
||||
accent_color = Lime
|
||||
|
||||
[Dev]
|
||||
verbose = True
|
||||
log_level = DEBUG
|
||||
use_test_library = True
|
||||
verbose = False
|
||||
use_test_library = False
|
||||
fail_sim = 10
|
||||
|
||||
[Tariffs]
|
||||
impose_tariffs = False
|
||||
kivy_rate = 50
|
||||
pyserial_rate = 500
|
||||
|
||||
[Info]
|
||||
version = V2.3.0
|
||||
subversion =
|
||||
|
||||
|
||||
@@ -1,37 +1,52 @@
|
||||
<AboutScreen>:
|
||||
name: "about"
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: (10,10,10,0.1)
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
GridLayout:
|
||||
MDFloatLayout:
|
||||
Image:
|
||||
source: "BiogasControllerAppLogo.png"
|
||||
pos_hint: {"top": 0.9}
|
||||
size_hint_y: .3
|
||||
radius: 36, 36, 0, 0
|
||||
allow_stretch: True
|
||||
keep_ratio: True
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
Label:
|
||||
MDLabel:
|
||||
text: "About"
|
||||
font_size: 40
|
||||
color: (0, 113, 0, 1)
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
bold: True
|
||||
FloatLayout:
|
||||
GridLayout:
|
||||
pos_hint: {"x":0.05, "y":0.05}
|
||||
size_hint: 0.9, 0.9
|
||||
cols: 3
|
||||
Button:
|
||||
italic: True
|
||||
theme_text_color: 'Secondary'
|
||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
pos_hint: {'x': 0.1, 'y': 0.05}
|
||||
text: "Back"
|
||||
background_color: (255,0,0,0.6)
|
||||
on_release:
|
||||
app.root.current = "home"
|
||||
root.manager.transition.direction = "up"
|
||||
Button:
|
||||
text: "Report a\nBug"
|
||||
background_color: (255,0,0,0.6)
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
pos_hint: {'right': 0.9, 'y': 0.05}
|
||||
text: "Report a Bug"
|
||||
on_release:
|
||||
root.report_issue()
|
||||
Button:
|
||||
text: "Credits"
|
||||
background_color: (255,0,0,0.6)
|
||||
root.goto("issues")
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
pos_hint: {'right': 0.48, 'y': 0.05}
|
||||
text: "Wiki"
|
||||
on_release:
|
||||
app.root.current = "credits"
|
||||
root.manager.transition.direction = "left"
|
||||
root.goto("wiki")
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
pos_hint: {'x': 0.52, 'y': 0.05}
|
||||
text: "Repo"
|
||||
on_release:
|
||||
root.goto("repo")
|
||||
Label:
|
||||
text: "This is a simple controller application that allows you to read data from and configure the microcontroller used in ENATECH at KSWO. It is written in Python using KivyMD as its UI framework.\n\nThis software is free Software licensed under the GNU General Public License Version 3 and as such comes with absolutely no warranty."
|
||||
pos_hint: {'x': 0.05, 'top': 0.42}
|
||||
text_size: self.width, None
|
||||
size_hint: 0.9, None
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
from kivy.uix.screenmanager import Screen
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivy.lang import Builder
|
||||
import webbrowser
|
||||
|
||||
from gui.popups.popups import SingleRowPopup
|
||||
|
||||
|
||||
class AboutScreen(Screen):
|
||||
def report_issue(self):
|
||||
SingleRowPopup().open("Opened your web-browser")
|
||||
def __init__(self, **kw):
|
||||
self.opened_web_browser_dialog = MDDialog(
|
||||
title="Open Link",
|
||||
text="Your webbrowser has been opened. Continue there",
|
||||
buttons=[
|
||||
MDFlatButton(text="Ok", on_release=lambda _: self.opened_web_browser_dialog.dismiss()),
|
||||
],
|
||||
)
|
||||
super().__init__(**kw)
|
||||
|
||||
def goto(self, loc: str):
|
||||
if loc == "wiki":
|
||||
webbrowser.open('https://github.com/janishutz/BiogasControllerApp/wiki', new=2)
|
||||
elif loc == "issues":
|
||||
webbrowser.open('https://github.com/janishutz/BiogasControllerApp/issues', new=2)
|
||||
elif loc == "repo":
|
||||
webbrowser.open('https://github.com/janishutz/BiogasControllerApp', new=2)
|
||||
self.opened_web_browser_dialog.open()
|
||||
|
||||
Builder.load_file('./gui/about/about.kv')
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<CreditsScreen>:
|
||||
name: "credits"
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: (20,20,20,0.2)
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
FloatLayout:
|
||||
Button:
|
||||
text: "back"
|
||||
size_hint: 0.4, 0.2
|
||||
pos_hint: {"x":0.3, "y":0.1}
|
||||
on_release:
|
||||
app.root.current = "about"
|
||||
root.manager.transition.direction = "right"
|
||||
GridLayout:
|
||||
cols:1
|
||||
pos_hint:{"x":0.05, "y":0.35}
|
||||
size_hint: 0.9, 0.5
|
||||
Label:
|
||||
text: "This is a controller sofware that helps you reprogram and monitor the micro-controller used in ENATECH at KSWO"
|
||||
Label:
|
||||
text: "Written by: Janis Hutz\nDesigned by: Janis Hutz\nDesign language: Kivy"
|
||||
Label:
|
||||
text: "This software is free Software licensed under the GPL V3 (GNU General Public License) and as such comes with absolutely no warranty. In return, you can use, modify, distribute or use any of the code of this software in your own project, if you reuse the same license. For more infos, you can find a copy of this license in the project folder."
|
||||
text_size: self.width, None
|
||||
@@ -1,8 +0,0 @@
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivy.lang import Builder
|
||||
|
||||
|
||||
class CreditsScreen(MDScreen):
|
||||
pass
|
||||
|
||||
Builder.load_file('./gui/credits/credits.kv')
|
||||
@@ -1,16 +1,10 @@
|
||||
<HomeScreen>:
|
||||
name: "home"
|
||||
MDGridLayout:
|
||||
cols:1
|
||||
MDFloatLayout:
|
||||
MDCard:
|
||||
radius: 36
|
||||
pos_hint: {"center_x": .5, "center_y": .5}
|
||||
size_hint: .6, .8
|
||||
|
||||
Image:
|
||||
source: "BiogasControllerAppLogo.png"
|
||||
pos_hint: {"top": 1}
|
||||
pos_hint: {"top": 0.9}
|
||||
size_hint_y: .3
|
||||
radius: 36, 36, 0, 0
|
||||
allow_stretch: True
|
||||
keep_ratio: True
|
||||
@@ -28,28 +22,29 @@
|
||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||
|
||||
|
||||
MDFloatLayout:
|
||||
MDGridLayout:
|
||||
spacing: 20
|
||||
size_hint: None, None
|
||||
size: self.minimum_size
|
||||
cols: 2
|
||||
size_hint: 0.2, 0.2
|
||||
pos_hint: {"x": 0.4, "y": 0.3}
|
||||
|
||||
MDRaisedButton:
|
||||
on_release: root.start()
|
||||
pos_hint: {'center_x': 0.5, 'center_y': 0.3 }
|
||||
MDFillRoundFlatButton:
|
||||
font_size: 30
|
||||
text: "Start"
|
||||
radius: [25]
|
||||
MDRaisedButton:
|
||||
on_release: root.start()
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
text: "Quit"
|
||||
font_size: 30
|
||||
on_release:
|
||||
root.quit()
|
||||
pos_hint: {"x": 0.7, "center_y": 0}
|
||||
on_release: root.quit()
|
||||
|
||||
MDLabel:
|
||||
text: "You are running version V3.0.1"
|
||||
text: "You are running version V3.1.0"
|
||||
font_size: 13
|
||||
pos_hint: {"y": -0.45, "x":0}
|
||||
halign: 'center'
|
||||
|
||||
MDFlatButton:
|
||||
text: "About"
|
||||
font_size: 13
|
||||
@@ -57,3 +52,11 @@
|
||||
pos_hint: {"x":0.01, "y":0.01}
|
||||
on_release:
|
||||
root.to_about()
|
||||
|
||||
# MDFlatButton:
|
||||
# text: "Change Theme"
|
||||
# font_size: 13
|
||||
# size_hint: 0.07, 0.06
|
||||
# pos_hint: {"right":0.99, "y":0.01}
|
||||
# on_release:
|
||||
# app.change_theme()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivy.lang import Builder
|
||||
from gui.popups.popups import DualRowPopup, QuitPopup, TwoActionPopup
|
||||
from lib.com import ComSuperClass
|
||||
import platform
|
||||
|
||||
@@ -10,62 +12,113 @@ information = {
|
||||
"Windows": {
|
||||
"2": "Un- and replug the cable and ensure you have the required driver(s) installed",
|
||||
"13": "You are probably missing a required driver or your cable doesn't work. Consult the wiki for more information",
|
||||
"NO_COM": "Could not find a microcontroller. Please ensure you have one connected and the required driver(s) installed"
|
||||
"NO_COM": "Could not find a microcontroller. Please ensure you have one connected and the required driver(s) installed",
|
||||
},
|
||||
"Linux": {
|
||||
"2": "Un- and replug the cable, or if you haven't plugged a controller in yet, do that",
|
||||
"13": "Incorrect permissions at /dev/ttyUSB0. Open a terminal and type: sudo chmod 777 /dev/ttyUSB0",
|
||||
"NO_COM": "Could not find a microcontroller. Please ensure you have one connected"
|
||||
}
|
||||
"NO_COM": "Could not find a microcontroller. Please ensure you have one connected",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# This is the launch screen, i.e. what you see when you start up the app
|
||||
class HomeScreen(MDScreen):
|
||||
def __init__(self, com: ComSuperClass, **kw):
|
||||
self._com = com;
|
||||
self._com = com
|
||||
self.connection_error_dialog = MDDialog(
|
||||
title="Connection",
|
||||
text="Failed to connect. See Details for more information and troubleshooting guide",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Cancel",
|
||||
on_release=lambda _: self.connection_error_dialog.dismiss(),
|
||||
),
|
||||
MDFlatButton(
|
||||
text="Details", on_release=lambda _: self.open_details_popup()
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
self.quit_dialog = MDDialog(
|
||||
title="Exit BiogasControllerApp",
|
||||
text="Do you really want to exit BiogasControllerApp?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Cancel",
|
||||
on_release=lambda _: self.quit_dialog.dismiss(),
|
||||
),
|
||||
MDFlatButton(
|
||||
text="Quit", on_release=lambda _: self._quit()
|
||||
),
|
||||
],
|
||||
)
|
||||
super().__init__(**kw)
|
||||
|
||||
def _quit(self):
|
||||
self._com.close()
|
||||
MDApp.get_running_app().stop()
|
||||
|
||||
# Go to the main screen if we can establish connection or the check was disabled
|
||||
# in the configs
|
||||
def start(self):
|
||||
if self._com.connect():
|
||||
self.manager.current = 'main'
|
||||
self.manager.transition.direction = 'right'
|
||||
self.manager.current = "main"
|
||||
self.manager.transition.direction = "right"
|
||||
else:
|
||||
TwoActionPopup().open('Failed to connect', 'Details', self.open_details_popup)
|
||||
print('ERROR connecting')
|
||||
self.connection_error_dialog.open()
|
||||
print("[ COM ] Connection failed!")
|
||||
|
||||
# Open popup for details as to why the connection failed
|
||||
def open_details_popup(self):
|
||||
DualRowPopup().open("Troubleshooting tips", self._generate_help())
|
||||
self.connection_error_dialog.dismiss()
|
||||
self.details_dialog = MDDialog(
|
||||
title="Troubleshooting",
|
||||
text=self._generate_help(),
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Ok", on_release=lambda _: self.details_dialog.dismiss()
|
||||
)
|
||||
],
|
||||
)
|
||||
self.details_dialog.open()
|
||||
|
||||
def _generate_help(self) -> str:
|
||||
operating_system = platform.system()
|
||||
if operating_system == "Windows" or operating_system == "Linux":
|
||||
port = self._com.get_comport();
|
||||
information["Linux"]["13"] = f"Incorrect permissions at {port}. Resolve by running 'sudo chmod 777 {port}'"
|
||||
port = self._com.get_comport()
|
||||
if port == "Sim":
|
||||
return "Running in simulator, so this error is just simulated"
|
||||
|
||||
information["Linux"][
|
||||
"13"
|
||||
] = f"Incorrect permissions at {port}. Resolve by running 'sudo chmod 777 {port}'"
|
||||
|
||||
|
||||
if port == "":
|
||||
return information[operating_system]["NO_COM"]
|
||||
|
||||
err = self._com.get_error()
|
||||
if err != None:
|
||||
return information[operating_system][str(err.errno)]
|
||||
else:
|
||||
return "No error message available"
|
||||
else:
|
||||
return "You are running on an unsupported Operating System. No help available"
|
||||
return (
|
||||
"You are running on an unsupported Operating System. No help available"
|
||||
)
|
||||
|
||||
# Helper to open a Popup to ask user whether to quit or not
|
||||
def quit(self):
|
||||
QuitPopup(self._com).open()
|
||||
self.quit_dialog.open()
|
||||
|
||||
# Switch to about screen
|
||||
def to_about(self):
|
||||
self.manager.current = 'about'
|
||||
self.manager.transition.direction = 'down'
|
||||
self.manager.current = "about"
|
||||
self.manager.transition.direction = "down"
|
||||
|
||||
|
||||
# Load the design file for this screen (.kv files)
|
||||
# The path has to be relative to root of the app, i.e. where the biogascontrollerapp.py
|
||||
# file is located
|
||||
Builder.load_file('./gui/home/home.kv')
|
||||
Builder.load_file("./gui/home/home.kv")
|
||||
|
||||
111
gui/main/main.kv
111
gui/main/main.kv
@@ -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}
|
||||
MDFloatLayout:
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
pos_hint: {'x': 0, 'y': 0.4}
|
||||
MDLabel:
|
||||
text: "READOUT"
|
||||
font_size: 40
|
||||
color: (0, 113, 0, 1)
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||
bold: True
|
||||
GridLayout:
|
||||
|
||||
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:
|
||||
|
||||
MDGridLayout:
|
||||
cols: 2
|
||||
size_hint: 0.15, 0.1
|
||||
pos_hint: {"x":0.1, "y":0.15}
|
||||
MDLabel:
|
||||
text: "Fast Mode"
|
||||
valign: "center"
|
||||
MDSwitch:
|
||||
id: mode_selector
|
||||
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:
|
||||
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:
|
||||
|
||||
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
|
||||
pos_hint: {"x":0.4, "y": 0.3}
|
||||
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'
|
||||
|
||||
@@ -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,6 +71,7 @@ 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
|
||||
try:
|
||||
data.append(
|
||||
f"Tadc: {
|
||||
self._decoder.decode_int(received[12 * i:12 * i + 4])
|
||||
@@ -79,8 +81,12 @@ class ReaderThread(threading.Thread):
|
||||
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"
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<QuitPopup>:
|
||||
title: "BiogasControllerApp"
|
||||
font_size: 50
|
||||
size_hint: 0.5, 0.4
|
||||
auto_dismiss: False
|
||||
GridLayout:
|
||||
cols: 1
|
||||
Label:
|
||||
text: "Are you sure you want to leave?"
|
||||
font_size: 20
|
||||
GridLayout:
|
||||
cols:2
|
||||
Button:
|
||||
text: "Yes"
|
||||
font_size: 15
|
||||
on_release:
|
||||
root.quit()
|
||||
app.stop()
|
||||
Button:
|
||||
text: "No"
|
||||
font_size: 15
|
||||
on_press:
|
||||
root.dismiss()
|
||||
|
||||
<SingleRowPopup>:
|
||||
title: "INFORMATION"
|
||||
size_hint: 0.7, 0.5
|
||||
auto_dismiss: True
|
||||
GridLayout:
|
||||
cols: 1
|
||||
Label:
|
||||
id: msg
|
||||
text: "Message"
|
||||
text_size: self.width, None
|
||||
halign: 'center'
|
||||
GridLayout:
|
||||
cols: 1
|
||||
Button:
|
||||
text: "Ok"
|
||||
on_release:
|
||||
root.dismiss()
|
||||
|
||||
<TwoActionPopup>:
|
||||
title: "WARNING!"
|
||||
font_size: 50
|
||||
size_hint: 0.5, 0.4
|
||||
auto_dismiss: False
|
||||
GridLayout:
|
||||
cols:1
|
||||
Label:
|
||||
id: msg
|
||||
text: "Message"
|
||||
font_size: 20
|
||||
halign: 'center'
|
||||
GridLayout:
|
||||
cols:2
|
||||
Button:
|
||||
id: btn1
|
||||
text: "Details"
|
||||
on_release:
|
||||
root.action_one()
|
||||
root.dismiss()
|
||||
Button:
|
||||
id: btn2
|
||||
text:"Ok"
|
||||
on_release:
|
||||
root.action_two()
|
||||
root.dismiss()
|
||||
|
||||
<DualRowPopup>:
|
||||
title: "Details"
|
||||
font_size: 50
|
||||
size_hint: 0.7, 0.6
|
||||
auto_dismiss: False
|
||||
GridLayout:
|
||||
cols:1
|
||||
Label:
|
||||
id: msg_title
|
||||
text: "Message title"
|
||||
font_size: 20
|
||||
Label:
|
||||
id: msg_body
|
||||
text: "Message body"
|
||||
font_size: 14
|
||||
Button:
|
||||
text:"Ok"
|
||||
on_release:
|
||||
root.dismiss()
|
||||
|
||||
<LargeTrippleRowPopUp>:
|
||||
title: "DETAILS"
|
||||
font_size: 50
|
||||
size_hint: 1, 0.7
|
||||
auto_dismiss: False
|
||||
GridLayout:
|
||||
cols: 1
|
||||
Label:
|
||||
id: msg_title
|
||||
text: "title"
|
||||
font_size: 20
|
||||
Label:
|
||||
id: msg_body
|
||||
text: "Message"
|
||||
font_size: 13
|
||||
Label:
|
||||
text: msg_extra
|
||||
font_size: 13
|
||||
Button:
|
||||
text:"Ok"
|
||||
on_release:
|
||||
root.dismiss()
|
||||
@@ -1,63 +0,0 @@
|
||||
from typing import Callable
|
||||
from kivy.uix.popup import Popup
|
||||
from kivy.lang import Builder
|
||||
|
||||
from lib.com import ComSuperClass
|
||||
|
||||
|
||||
# Just an empty function
|
||||
def empty_func():
|
||||
pass
|
||||
|
||||
# ╭────────────────────────────────────────────────╮
|
||||
# │ Popups │
|
||||
# ╰────────────────────────────────────────────────╯
|
||||
# Below, you can find various popups with various designs that can be used in the app
|
||||
class QuitPopup(Popup):
|
||||
def __init__(self, com: ComSuperClass, **kw):
|
||||
self._com = com;
|
||||
super().__init__(**kw)
|
||||
|
||||
def quit(self):
|
||||
self._com.close()
|
||||
|
||||
class SingleRowPopup(Popup):
|
||||
def open(self, message, *_args, **kwargs):
|
||||
self.ids.msg.text = message
|
||||
return super().open(*_args, **kwargs)
|
||||
|
||||
class DualRowPopup(Popup):
|
||||
def open(self, title: str, message: str, *_args, **kwargs):
|
||||
self.ids.msg_title.text = title
|
||||
self.ids.msg_body.text = message
|
||||
return super().open(*_args, **kwargs)
|
||||
|
||||
class LargeTrippleRowPopup(Popup):
|
||||
def open(self, title: str, message: str, details: str, *_args, **kwargs):
|
||||
self.ids.msg_title.text = title
|
||||
self.ids.msg_body.text = message
|
||||
self.ids.msg_extra.text = details
|
||||
return super().open(*_args, **kwargs)
|
||||
|
||||
class TwoActionPopup(Popup):
|
||||
def open(self,
|
||||
message: str,
|
||||
button_one: str,
|
||||
action_one: Callable[[], None],
|
||||
button_two: str = 'Ok',
|
||||
action_two: Callable[[], None] = empty_func,
|
||||
*_args,
|
||||
**kwargs
|
||||
):
|
||||
self.ids.msg.text = message
|
||||
self.ids.btn1.text = button_one
|
||||
self.ids.btn2.text = button_two
|
||||
self.action_one = action_one
|
||||
self.action_two = action_two
|
||||
return super().open(*_args, **kwargs)
|
||||
|
||||
|
||||
# Load the design file for this screen (.kv files)
|
||||
# The path has to be relative to root of the app, i.e. where the biogascontrollerapp.py
|
||||
# file is located
|
||||
Builder.load_file('./gui/popups/popups.kv')
|
||||
@@ -1,126 +1,123 @@
|
||||
<ProgramScreen>:
|
||||
name: "program"
|
||||
on_enter: self.config_loader = root.load_config()
|
||||
md_bg_color: app.theme_cls.primary_color
|
||||
FloatLayout:
|
||||
Label:
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
pos_hint: {'x': 0, 'y': 0.4}
|
||||
MDLabel:
|
||||
text: "Configuration"
|
||||
font_size: 40
|
||||
color: (0, 113, 0, 1)
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||
bold: True
|
||||
pos_hint: {"y":0.4}
|
||||
GridLayout:
|
||||
size_hint: 0.8, 0.5
|
||||
pos_hint: {"x":0.1, "y":0.2}
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
pos_hint: {'x': 0, 'y': 0.33}
|
||||
MDLabel:
|
||||
text: "Change the configuration of the microcontroller"
|
||||
font_size: 18
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||
italic: True
|
||||
|
||||
MDGridLayout:
|
||||
cols: 1
|
||||
pos_hint: {'x': 0, 'y': 0.25}
|
||||
MDLabel:
|
||||
id: status
|
||||
text: "Loading..."
|
||||
font_size: 17
|
||||
halign: 'center'
|
||||
bold: True
|
||||
|
||||
MDGridLayout:
|
||||
size_hint: 0.9, 0.5
|
||||
spacing: 10
|
||||
pos_hint: {"x":0.05, "y":0.2}
|
||||
cols: 4
|
||||
Label:
|
||||
text: "Sensor 1, a:"
|
||||
TextInput:
|
||||
MDTextField:
|
||||
id: s1_a
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 1, b:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 1 a'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s1_b
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 1, c:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 1 b'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s1_c
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 1, Temp:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 1 c'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s1_t
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 2, a:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 1 Temperature'
|
||||
on_text: root.validate_float(self)
|
||||
|
||||
MDTextField:
|
||||
id: s2_a
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 2, b:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 2 a'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s2_b
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 2, c:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 2 b'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s2_c
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 2, Temp:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 2 c'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s2_t
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 3, a:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 2 Temperature'
|
||||
on_text: root.validate_float(self)
|
||||
|
||||
MDTextField:
|
||||
id: s3_a
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 3, b:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 3 a'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s3_b
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 3, c:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 3 b'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s3_c
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 3, Temp:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 3 c'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s3_t
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 4, a:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 3 Temperature'
|
||||
on_text: root.validate_float(self)
|
||||
|
||||
MDTextField:
|
||||
id: s4_a
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 4, b:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 4 a'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s4_b
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 4, c:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 4 b'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s4_c
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Label:
|
||||
text: "Sensor 4, Temp:"
|
||||
TextInput:
|
||||
hint_text: 'Sensor 4 c'
|
||||
on_text: root.validate_float(self)
|
||||
MDTextField:
|
||||
id: s4_t
|
||||
multiline: False
|
||||
input_filter: "float"
|
||||
Button:
|
||||
hint_text: 'Sensor 4 Temperature'
|
||||
on_text: root.validate_float(self)
|
||||
|
||||
MDFillRoundFlatButton:
|
||||
size_hint: 0.1, 0.07
|
||||
text: "Back"
|
||||
size_hint: 0.1, 0.1
|
||||
pos_hint: {"x":0.1, "y":0.1}
|
||||
background_color: (255, 0, 0, 0.6)
|
||||
on_release:
|
||||
app.root.current = "main"
|
||||
root.manager.transition.direction = "up"
|
||||
Button:
|
||||
MDFillRoundFlatButton:
|
||||
size_hint: 0.15, 0.09
|
||||
text: "Save"
|
||||
size_hint: 0.2, 0.1
|
||||
pos_hint: {"x":0.6, "y":0.1}
|
||||
background_color: (255, 0, 0, 0.6)
|
||||
on_release:
|
||||
root.save()
|
||||
|
||||
@@ -3,7 +3,8 @@ from kivymd.uix.screen import MDScreen
|
||||
from kivy.lang import Builder
|
||||
from lib.decoder import Decoder
|
||||
from lib.instructions import Instructions
|
||||
from gui.popups.popups import SingleRowPopup, TwoActionPopup, empty_func
|
||||
from kivymd.uix.button import MDFlatButton
|
||||
from kivymd.uix.dialog import MDDialog
|
||||
from lib.com import ComSuperClass
|
||||
from kivy.clock import Clock
|
||||
|
||||
@@ -18,13 +19,60 @@ class ProgramScreen(MDScreen):
|
||||
self._com = com
|
||||
self._instructions = Instructions(com)
|
||||
self._decoder = Decoder()
|
||||
|
||||
self.connection_error_dialog = MDDialog(
|
||||
title="Connection",
|
||||
text="Failed to connect. Do you wish to retry?",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Cancel",
|
||||
on_release=lambda dt: self.connection_error_dialog.dismiss(),
|
||||
),
|
||||
MDFlatButton(text="Retry", on_release=lambda dt: self._load()),
|
||||
],
|
||||
)
|
||||
|
||||
self.missing_fields_error_dialog = MDDialog(
|
||||
title="Save",
|
||||
text="Some fields are missing entries. Please fill them out and try again",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Ok",
|
||||
on_release=lambda dt: self.missing_fields_error_dialog.dismiss(),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
self.save_error_dialog = MDDialog(
|
||||
title="Save",
|
||||
text="Failed to save data. Please try again",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Ok",
|
||||
on_release=lambda dt: self.save_error_dialog.dismiss(),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
self.save_success_dialog = MDDialog(
|
||||
title="Save",
|
||||
text="Data saved successfully!",
|
||||
buttons=[
|
||||
MDFlatButton(
|
||||
text="Ok",
|
||||
on_release=lambda dt: self.save_success_dialog.dismiss(),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
super().__init__(**kw)
|
||||
|
||||
def load_config(self):
|
||||
Clock.schedule_once(self._load)
|
||||
Clock.schedule_once(lambda dt: self._load())
|
||||
|
||||
# Load the current configuration from the micro-controller
|
||||
def _load(self, dt: float):
|
||||
def _load(self):
|
||||
self.ids.status.text = "Loading..."
|
||||
# Hook to the microcontroller's data stream (i.e. sync up with it)
|
||||
if self._instructions.hook("RD", ["\n", "R", "D", "\n"]):
|
||||
config: List[List[str]] = []
|
||||
@@ -37,13 +85,7 @@ class ProgramScreen(MDScreen):
|
||||
received = self._com.receive(28)
|
||||
except:
|
||||
# Open error popup
|
||||
TwoActionPopup().open(
|
||||
"Failed to connect to micro-controller, retry?",
|
||||
"Cancel",
|
||||
empty_func,
|
||||
"Retry",
|
||||
lambda: self._load(0),
|
||||
)
|
||||
self.connection_error_dialog.open()
|
||||
return
|
||||
|
||||
# Create a list of strings to store the config for the sensor
|
||||
@@ -58,16 +100,11 @@ class ProgramScreen(MDScreen):
|
||||
|
||||
# Add it to the config
|
||||
config.append(config_sensor_i)
|
||||
self.ids.status.text = ""
|
||||
|
||||
self._set_ui(config)
|
||||
else:
|
||||
TwoActionPopup().open(
|
||||
"Failed to connect to micro-controller, retry?",
|
||||
"Cancel",
|
||||
empty_func,
|
||||
"Retry",
|
||||
lambda: self._load(0),
|
||||
)
|
||||
self.connection_error_dialog.open()
|
||||
|
||||
# Set the elements of the UI to the values of the config
|
||||
def _set_ui(self, config: List[List[str]]):
|
||||
@@ -96,16 +133,37 @@ class ProgramScreen(MDScreen):
|
||||
|
||||
# Transmit the changed data to the micro-controller to reconfigure it
|
||||
def save(self):
|
||||
self.ids.status.text = "Saving..."
|
||||
data = self._read_ui()
|
||||
if data == None:
|
||||
SingleRowPopup().open("Some fields are missing values!")
|
||||
self.missing_fields_error_dialog()
|
||||
else:
|
||||
try:
|
||||
self._instructions.change_config(data)
|
||||
except Exception as e:
|
||||
SingleRowPopup().open("Could not save data!")
|
||||
self.save_error_dialog.open()
|
||||
return
|
||||
SingleRowPopup().open("Data saved successfully")
|
||||
self.save_success_dialog.open()
|
||||
self.ids.status.text = "Saved!"
|
||||
Clock.schedule_once(self.reset_update, 5)
|
||||
|
||||
def reset_update(self, dt):
|
||||
self.ids.status.text = ""
|
||||
|
||||
def validate_float(self, instance):
|
||||
text = instance.text
|
||||
|
||||
# Allow only digits and one dot
|
||||
if text.count(".") > 1 or any(c not in "0123456789." for c in text):
|
||||
# Remove invalid characters
|
||||
clean_text = "".join(c for c in text if c in "0123456789.")
|
||||
# Remove extra dots
|
||||
if clean_text.count(".") > 1:
|
||||
first_dot = clean_text.find(".")
|
||||
clean_text = clean_text[: first_dot + 1] + clean_text[
|
||||
first_dot + 1 :
|
||||
].replace(".", "")
|
||||
instance.text = clean_text
|
||||
|
||||
|
||||
# Load the design file for this screen (.kv files)
|
||||
|
||||
25
lib/com.py
25
lib/com.py
@@ -6,15 +6,22 @@ import serial.tools.list_ports
|
||||
|
||||
|
||||
class ComSuperClass(ABC):
|
||||
def __init__(self, baudrate: Optional[int] = 19200, filters: Optional[list[str]] = None) -> None:
|
||||
def __init__(
|
||||
self, baudrate: Optional[int] = 19200, filters: Optional[list[str]] = None
|
||||
) -> None:
|
||||
self._serial: Optional[serial.Serial] = None
|
||||
self._filters = filters if filters != None else [ 'USB-Serial Controller', 'Prolific USB-Serial Controller' ]
|
||||
self._port_override = ''
|
||||
self._filters = (
|
||||
filters
|
||||
if filters != None
|
||||
else ["USB-Serial Controller", "Prolific USB-Serial Controller"]
|
||||
)
|
||||
self._port_override = ""
|
||||
self._baudrate = baudrate if baudrate != None else 19200
|
||||
self._err = None
|
||||
|
||||
def set_port_override(self, override: str) -> None:
|
||||
"""Set the port override, to disable port search"""
|
||||
if override != "" and override != "None":
|
||||
self._port_override = override
|
||||
|
||||
def get_error(self) -> serial.SerialException | None:
|
||||
@@ -58,7 +65,7 @@ class Com(ComSuperClass):
|
||||
|
||||
def get_comport(self) -> str:
|
||||
"""Find the comport the microcontroller has attached to"""
|
||||
if self._port_override != '':
|
||||
if self._port_override != "":
|
||||
return self._port_override
|
||||
|
||||
# Catch all errors and simply return an empty string if search unsuccessful
|
||||
@@ -80,7 +87,7 @@ class Com(ComSuperClass):
|
||||
comport = self.get_comport()
|
||||
|
||||
# Comport search returns empty string if search unsuccessful
|
||||
if comport == '':
|
||||
if comport == "":
|
||||
try:
|
||||
self._serial = serial.Serial(comport, self._baudrate, timeout=5)
|
||||
except serial.SerialException as e:
|
||||
@@ -108,7 +115,7 @@ class Com(ComSuperClass):
|
||||
if self._serial != None:
|
||||
return self._serial.read(byte_count)
|
||||
else:
|
||||
raise Exception('ERR_CONNECTING')
|
||||
raise Exception("ERR_CONNECTING")
|
||||
|
||||
def send(self, msg: str) -> None:
|
||||
"""Send a string over serial connection. Will open a connection if none is available"""
|
||||
@@ -116,12 +123,12 @@ class Com(ComSuperClass):
|
||||
if self._serial != None:
|
||||
self._serial.write(msg.encode())
|
||||
else:
|
||||
raise Exception('ERR_CONNECTING')
|
||||
raise Exception("ERR_CONNECTING")
|
||||
|
||||
def send_float(self, msg: float) -> None:
|
||||
"""Send a float number over serial connection"""
|
||||
self._connection_check()
|
||||
if self._serial != None:
|
||||
self._serial.write(bytearray(struct.pack('>f', msg))[0:3])
|
||||
self._serial.write(bytearray(struct.pack(">f", msg))[0:3])
|
||||
else:
|
||||
raise Exception('ERR_CONNECTING')
|
||||
raise Exception("ERR_CONNECTING")
|
||||
|
||||
@@ -12,10 +12,6 @@ class Instructions:
|
||||
def __init__(self, com: ComSuperClass) -> None:
|
||||
self._com = com
|
||||
|
||||
# Set a port override (to use a specific COM port)
|
||||
def set_port_override(self, override: str) -> None:
|
||||
self._com.set_port_override(override)
|
||||
|
||||
# Helper method to hook to the data stream according to protocol.
|
||||
# You can specify the sequence that the program listens to to sync up,
|
||||
# as an array of strings, that should each be of length one and only contain
|
||||
|
||||
@@ -50,7 +50,7 @@ class SensorConfig:
|
||||
|
||||
class Com(ComSuperClass):
|
||||
def __init__(
|
||||
self, baudrate: int = 19200, filters: Optional[list[str]] = None
|
||||
self, fail_sim: int, baudrate: int = 19200, filters: Optional[list[str]] = None
|
||||
) -> None:
|
||||
# Calling the constructor of the super class to assign defaults
|
||||
print("\n\nWARNING: Using testing library for communication!\n\n")
|
||||
@@ -62,6 +62,7 @@ class Com(ComSuperClass):
|
||||
|
||||
self.__reconf_sensor = 0
|
||||
self.__reconf_step = 0
|
||||
self.__fail_sim = fail_sim
|
||||
|
||||
self.__config: List[SensorConfig] = [
|
||||
SensorConfig(),
|
||||
@@ -78,11 +79,11 @@ class Com(ComSuperClass):
|
||||
self._port_override = override
|
||||
|
||||
def get_comport(self) -> str:
|
||||
return "test" if self._port_override != "" else self._port_override
|
||||
return "Sim" if self._port_override == "" else self._port_override
|
||||
|
||||
def connect(self) -> bool:
|
||||
# Randomly return false in 1 in 20 ish cases
|
||||
if random.randint(0, 20) == 1:
|
||||
# Randomly return false in 1 in fail_sim ish cases
|
||||
if random.randint(0, self.__fail_sim) == 0:
|
||||
print("Simulating error to connect")
|
||||
return False
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user