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.
|
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 |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 3.0.0 | ✅ |
|
| 3.1.X | ✅ |
|
||||||
|
| 3.0.X | ✅ |
|
||||||
| 2.3.0 | ❎ |
|
| 2.3.0 | ❎ |
|
||||||
| 2.2.0 | ❎ |
|
| 2.2.0 | ❎ |
|
||||||
| 2.1.0 | ❎ |
|
| 2.1.0 | ❎ |
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# Load the config file
|
# Load the config file
|
||||||
import configparser
|
import configparser
|
||||||
import time
|
import time
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read("./config.ini")
|
config.read("./config.ini")
|
||||||
|
|
||||||
@@ -27,13 +28,17 @@ if config["Tariffs"]["impose_tariffs"] == "True":
|
|||||||
try:
|
try:
|
||||||
import tariff
|
import tariff
|
||||||
|
|
||||||
tariff.set({
|
tariff.set(
|
||||||
"kivy": int(config["Tariffs"]["kivy_rate"]),
|
{
|
||||||
"serial": int(config["Tariffs"]["pyserial_rate"]),
|
"kivy": int(config["Tariffs"]["kivy_rate"]),
|
||||||
})
|
"serial": int(config["Tariffs"]["pyserial_rate"]),
|
||||||
|
}
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(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)
|
time.sleep(2000000)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -43,7 +48,6 @@ from lib.com import Com, ComSuperClass
|
|||||||
import lib.test.com
|
import lib.test.com
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Load config and disable kivy log if necessary
|
# Load config and disable kivy log if necessary
|
||||||
if config["Dev"]["verbose"] == "True":
|
if config["Dev"]["verbose"] == "True":
|
||||||
pass
|
pass
|
||||||
@@ -52,13 +56,13 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
# Load kivy modules. Kivy is the UI framework used. See https://kivy.org
|
# 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 kivy.uix.screenmanager import ScreenManager
|
||||||
from kivymd.app import MDApp
|
from kivymd.app import MDApp
|
||||||
|
|
||||||
|
|
||||||
# Store the current app version
|
# Set Window size
|
||||||
app_version = f"{config['Info']['version']}{config['Info']['subVersion']}"
|
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
|
# Import all the screens (= pages) used in the app
|
||||||
from gui.home.home import HomeScreen
|
from gui.home.home import HomeScreen
|
||||||
from gui.credits.credits import CreditsScreen
|
|
||||||
from gui.program.program import ProgramScreen
|
from gui.program.program import ProgramScreen
|
||||||
from gui.about.about import AboutScreen
|
from gui.about.about import AboutScreen
|
||||||
from gui.main.main import MainScreen
|
from gui.main.main import MainScreen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ╭────────────────────────────────────────────────╮
|
# ╭────────────────────────────────────────────────╮
|
||||||
# │ Screen Manager │
|
# │ Screen Manager │
|
||||||
# ╰────────────────────────────────────────────────╯
|
# ╰────────────────────────────────────────────────╯
|
||||||
@@ -84,29 +86,54 @@ class BiogasControllerApp(MDApp):
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
def build(self):
|
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":
|
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.theme_style = (
|
||||||
self.theme_cls.primary_palette = "Green"
|
"Dark" if config["UI"]["theme"] == None else config["UI"]["theme"]
|
||||||
self.theme_cls.accent_palette = "Lime"
|
)
|
||||||
self.theme_cls.theme_style_switch_animation = True
|
self.theme_cls.material_style = "M3"
|
||||||
self.theme_cls.theme_style_switch_animation_duration = 0.8
|
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.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(HomeScreen(com, name="home"))
|
||||||
self.screen_manager.add_widget(MainScreen(com, name="main"))
|
self.screen_manager.add_widget(MainScreen(com, name="main"))
|
||||||
self.screen_manager.add_widget(ProgramScreen(com, name="program"))
|
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"))
|
self.screen_manager.add_widget(AboutScreen(name="about"))
|
||||||
return self.screen_manager
|
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
|
# Disallow this file to be imported
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("""
|
print(
|
||||||
|
"""
|
||||||
┏━━┓━━━━━━━━━━━━━━━━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━┏┓━┏┓━━━━━━━━┏━━━┓━━━━━━━━
|
┏━━┓━━━━━━━━━━━━━━━━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━┏┓━┏┓━━━━━━━━┏━━━┓━━━━━━━━
|
||||||
┃┏┓┃━━━━━━━━━━━━━━━━━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━┃┃━┃┃━━━━━━━━┃┏━┓┃━━━━━━━━
|
┃┏┓┃━━━━━━━━━━━━━━━━━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━┃┃━┃┃━━━━━━━━┃┏━┓┃━━━━━━━━
|
||||||
┃┗┛┗┓┏┓┏━━┓┏━━┓┏━━┓━┏━━┓┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓┃┃━┃┃━┏━━┓┏━┓┃┃━┃┃┏━━┓┏━━┓
|
┃┗┛┗┓┏┓┏━━┓┏━━┓┏━━┓━┏━━┓┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓┃┃━┃┃━┏━━┓┏━┓┃┃━┃┃┏━━┓┏━━┓
|
||||||
@@ -116,9 +143,10 @@ if __name__ == "__main__":
|
|||||||
━━━━━━━━━━━┏━┛┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━┃┃━━
|
━━━━━━━━━━━┏━┛┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━┃┃━━
|
||||||
━━━━━━━━━━━┗━━┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━┗┛━━
|
━━━━━━━━━━━┗━━┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━┗┛━━
|
||||||
|
|
||||||
Version 3.0.0
|
Version 3.1.0
|
||||||
|
|
||||||
=> Initializing....
|
=> Initializing....
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
BiogasControllerApp().run()
|
BiogasControllerApp().run()
|
||||||
print("\n => Exiting!")
|
print("\n => Exiting!")
|
||||||
|
|||||||
20
changelog
20
changelog
@@ -1,11 +1,27 @@
|
|||||||
***CHANGELOG***
|
***CHANGELOG***
|
||||||
|
V3.1.0
|
||||||
|
- Completely redesigned User Interface using KivyMD
|
||||||
|
- Added config option for themes
|
||||||
|
|
||||||
V3.0-beta
|
V3.0.1
|
||||||
- Redesigned GUI
|
- Install script fixes
|
||||||
|
- Packaging fixes
|
||||||
|
|
||||||
|
|
||||||
|
V3.0.0
|
||||||
|
- Small UI fixes
|
||||||
- Consolidated multiple previously separate screens
|
- Consolidated multiple previously separate screens
|
||||||
- Completely rewritten backend
|
- Completely rewritten backend
|
||||||
- Improved stability
|
- Improved stability
|
||||||
- Cleaned, documented code
|
- 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
|
OLD VERSIONS
|
||||||
------------
|
------------
|
||||||
|
|||||||
27
config.ini
27
config.ini
@@ -1,21 +1,24 @@
|
|||||||
[Ports]
|
[Connection]
|
||||||
specificport = None
|
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]
|
[UI]
|
||||||
sizeh = 600
|
height = 600
|
||||||
sizew = 800
|
width = 800
|
||||||
|
# Can be Dark or Light
|
||||||
|
theme = Dark
|
||||||
|
primary_color = Green
|
||||||
|
accent_color = Lime
|
||||||
|
|
||||||
[Dev]
|
[Dev]
|
||||||
verbose = True
|
verbose = False
|
||||||
log_level = DEBUG
|
use_test_library = False
|
||||||
use_test_library = True
|
fail_sim = 10
|
||||||
|
|
||||||
[Tariffs]
|
[Tariffs]
|
||||||
impose_tariffs = False
|
impose_tariffs = False
|
||||||
kivy_rate = 50
|
kivy_rate = 50
|
||||||
pyserial_rate = 500
|
pyserial_rate = 500
|
||||||
|
|
||||||
[Info]
|
|
||||||
version = V2.3.0
|
|
||||||
subversion =
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,52 @@
|
|||||||
<AboutScreen>:
|
<AboutScreen>:
|
||||||
name: "about"
|
name: "about"
|
||||||
canvas.before:
|
MDFloatLayout:
|
||||||
Color:
|
Image:
|
||||||
rgba: (10,10,10,0.1)
|
source: "BiogasControllerAppLogo.png"
|
||||||
Rectangle:
|
pos_hint: {"top": 0.9}
|
||||||
size: self.size
|
size_hint_y: .3
|
||||||
pos: self.pos
|
radius: 36, 36, 0, 0
|
||||||
GridLayout:
|
allow_stretch: True
|
||||||
cols: 1
|
keep_ratio: True
|
||||||
|
|
||||||
|
MDGridLayout:
|
||||||
|
cols: 1
|
||||||
|
MDLabel:
|
||||||
|
text: "About"
|
||||||
|
font_size: 40
|
||||||
|
halign: 'center'
|
||||||
|
valign: 'center'
|
||||||
|
bold: True
|
||||||
|
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"
|
||||||
|
on_release:
|
||||||
|
app.root.current = "home"
|
||||||
|
root.manager.transition.direction = "up"
|
||||||
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
|
pos_hint: {'right': 0.9, 'y': 0.05}
|
||||||
|
text: "Report a Bug"
|
||||||
|
on_release:
|
||||||
|
root.goto("issues")
|
||||||
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
|
pos_hint: {'right': 0.48, 'y': 0.05}
|
||||||
|
text: "Wiki"
|
||||||
|
on_release:
|
||||||
|
root.goto("wiki")
|
||||||
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
|
pos_hint: {'x': 0.52, 'y': 0.05}
|
||||||
|
text: "Repo"
|
||||||
|
on_release:
|
||||||
|
root.goto("repo")
|
||||||
Label:
|
Label:
|
||||||
text: "About"
|
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."
|
||||||
font_size: 40
|
pos_hint: {'x': 0.05, 'top': 0.42}
|
||||||
color: (0, 113, 0, 1)
|
text_size: self.width, None
|
||||||
bold: True
|
size_hint: 0.9, None
|
||||||
FloatLayout:
|
|
||||||
GridLayout:
|
|
||||||
pos_hint: {"x":0.05, "y":0.05}
|
|
||||||
size_hint: 0.9, 0.9
|
|
||||||
cols: 3
|
|
||||||
Button:
|
|
||||||
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)
|
|
||||||
on_release:
|
|
||||||
root.report_issue()
|
|
||||||
Button:
|
|
||||||
text: "Credits"
|
|
||||||
background_color: (255,0,0,0.6)
|
|
||||||
on_release:
|
|
||||||
app.root.current = "credits"
|
|
||||||
root.manager.transition.direction = "left"
|
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
from kivy.uix.screenmanager import Screen
|
from kivy.uix.screenmanager import Screen
|
||||||
|
from kivymd.uix.dialog import MDDialog
|
||||||
|
from kivymd.uix.button import MDFlatButton
|
||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
from gui.popups.popups import SingleRowPopup
|
|
||||||
|
|
||||||
|
|
||||||
class AboutScreen(Screen):
|
class AboutScreen(Screen):
|
||||||
def report_issue(self):
|
def __init__(self, **kw):
|
||||||
SingleRowPopup().open("Opened your web-browser")
|
self.opened_web_browser_dialog = MDDialog(
|
||||||
webbrowser.open('https://github.com/janishutz/BiogasControllerApp/issues', new=2)
|
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')
|
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,19 +1,13 @@
|
|||||||
<HomeScreen>:
|
<HomeScreen>:
|
||||||
name: "home"
|
name: "home"
|
||||||
MDGridLayout:
|
MDFloatLayout:
|
||||||
cols:1
|
Image:
|
||||||
MDFloatLayout:
|
source: "BiogasControllerAppLogo.png"
|
||||||
MDCard:
|
pos_hint: {"top": 0.9}
|
||||||
radius: 36
|
size_hint_y: .3
|
||||||
pos_hint: {"center_x": .5, "center_y": .5}
|
radius: 36, 36, 0, 0
|
||||||
size_hint: .6, .8
|
allow_stretch: True
|
||||||
|
keep_ratio: True
|
||||||
Image:
|
|
||||||
source: "BiogasControllerAppLogo.png"
|
|
||||||
pos_hint: {"top": 1}
|
|
||||||
radius: 36, 36, 0, 0
|
|
||||||
allow_stretch: True
|
|
||||||
keep_ratio: True
|
|
||||||
|
|
||||||
MDGridLayout:
|
MDGridLayout:
|
||||||
cols: 1
|
cols: 1
|
||||||
@@ -28,32 +22,41 @@
|
|||||||
pos_hint: {'center_x': 0, 'center_y': 0}
|
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||||
|
|
||||||
|
|
||||||
MDFloatLayout:
|
MDGridLayout:
|
||||||
MDGridLayout:
|
spacing: 20
|
||||||
cols: 2
|
size_hint: None, None
|
||||||
size_hint: 0.2, 0.2
|
size: self.minimum_size
|
||||||
pos_hint: {"x": 0.4, "y": 0.3}
|
cols: 2
|
||||||
|
pos_hint: {'center_x': 0.5, 'center_y': 0.3 }
|
||||||
|
MDFillRoundFlatButton:
|
||||||
|
font_size: 30
|
||||||
|
text: "Start"
|
||||||
|
on_release: root.start()
|
||||||
|
|
||||||
MDRaisedButton:
|
MDFillRoundFlatButton:
|
||||||
on_release: root.start()
|
text: "Quit"
|
||||||
font_size: 30
|
font_size: 30
|
||||||
text: "Start"
|
pos_hint: {"x": 0.7, "center_y": 0}
|
||||||
radius: [25]
|
on_release: root.quit()
|
||||||
MDRaisedButton:
|
|
||||||
text: "Quit"
|
|
||||||
font_size: 30
|
|
||||||
on_release:
|
|
||||||
root.quit()
|
|
||||||
|
|
||||||
MDLabel:
|
MDLabel:
|
||||||
text: "You are running version V3.0.1"
|
text: "You are running version V3.1.0"
|
||||||
font_size: 13
|
font_size: 13
|
||||||
pos_hint: {"y": -0.45, "x":0}
|
pos_hint: {"y": -0.45, "x":0}
|
||||||
halign: 'center'
|
halign: 'center'
|
||||||
MDFlatButton:
|
|
||||||
text: "About"
|
MDFlatButton:
|
||||||
font_size: 13
|
text: "About"
|
||||||
size_hint: 0.07, 0.06
|
font_size: 13
|
||||||
pos_hint: {"x":0.01, "y":0.01}
|
size_hint: 0.07, 0.06
|
||||||
on_release:
|
pos_hint: {"x":0.01, "y":0.01}
|
||||||
root.to_about()
|
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 kivymd.uix.screen import MDScreen
|
||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
from gui.popups.popups import DualRowPopup, QuitPopup, TwoActionPopup
|
|
||||||
from lib.com import ComSuperClass
|
from lib.com import ComSuperClass
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
@@ -10,62 +12,113 @@ information = {
|
|||||||
"Windows": {
|
"Windows": {
|
||||||
"2": "Un- and replug the cable and ensure you have the required driver(s) installed",
|
"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",
|
"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": {
|
"Linux": {
|
||||||
"2": "Un- and replug the cable, or if you haven't plugged a controller in yet, do that",
|
"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",
|
"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
|
# This is the launch screen, i.e. what you see when you start up the app
|
||||||
class HomeScreen(MDScreen):
|
class HomeScreen(MDScreen):
|
||||||
def __init__(self, com: ComSuperClass, **kw):
|
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)
|
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
|
# Go to the main screen if we can establish connection or the check was disabled
|
||||||
# in the configs
|
# in the configs
|
||||||
def start(self):
|
def start(self):
|
||||||
if self._com.connect():
|
if self._com.connect():
|
||||||
self.manager.current = 'main'
|
self.manager.current = "main"
|
||||||
self.manager.transition.direction = 'right'
|
self.manager.transition.direction = "right"
|
||||||
else:
|
else:
|
||||||
TwoActionPopup().open('Failed to connect', 'Details', self.open_details_popup)
|
self.connection_error_dialog.open()
|
||||||
print('ERROR connecting')
|
print("[ COM ] Connection failed!")
|
||||||
|
|
||||||
# Open popup for details as to why the connection failed
|
# Open popup for details as to why the connection failed
|
||||||
def open_details_popup(self):
|
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:
|
def _generate_help(self) -> str:
|
||||||
operating_system = platform.system()
|
operating_system = platform.system()
|
||||||
if operating_system == "Windows" or operating_system == "Linux":
|
if operating_system == "Windows" or operating_system == "Linux":
|
||||||
port = self._com.get_comport();
|
port = self._com.get_comport()
|
||||||
information["Linux"]["13"] = f"Incorrect permissions at {port}. Resolve by running 'sudo chmod 777 {port}'"
|
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 == "":
|
if port == "":
|
||||||
return information[operating_system]["NO_COM"]
|
return information[operating_system]["NO_COM"]
|
||||||
|
|
||||||
err = self._com.get_error()
|
err = self._com.get_error()
|
||||||
if err != None:
|
if err != None:
|
||||||
return information[operating_system][str(err.errno)]
|
return information[operating_system][str(err.errno)]
|
||||||
else:
|
else:
|
||||||
return "No error message available"
|
return "No error message available"
|
||||||
else:
|
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
|
# Helper to open a Popup to ask user whether to quit or not
|
||||||
def quit(self):
|
def quit(self):
|
||||||
QuitPopup(self._com).open()
|
self.quit_dialog.open()
|
||||||
|
|
||||||
# Switch to about screen
|
# Switch to about screen
|
||||||
def to_about(self):
|
def to_about(self):
|
||||||
self.manager.current = 'about'
|
self.manager.current = "about"
|
||||||
self.manager.transition.direction = 'down'
|
self.manager.transition.direction = "down"
|
||||||
|
|
||||||
|
|
||||||
# Load the design file for this screen (.kv files)
|
# 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
|
# The path has to be relative to root of the app, i.e. where the biogascontrollerapp.py
|
||||||
# file is located
|
# file is located
|
||||||
Builder.load_file('./gui/home/home.kv')
|
Builder.load_file("./gui/home/home.kv")
|
||||||
|
|||||||
123
gui/main/main.kv
123
gui/main/main.kv
@@ -1,101 +1,124 @@
|
|||||||
<MainScreen>:
|
<MainScreen>:
|
||||||
on_pre_enter: root.reset()
|
on_pre_enter: root.reset()
|
||||||
name: "main"
|
name: "main"
|
||||||
canvas.before:
|
MDFloatLayout:
|
||||||
Color:
|
MDGridLayout:
|
||||||
rgba: (10,10,10,0.1)
|
cols: 1
|
||||||
Rectangle:
|
pos_hint: {'x': 0, 'y': 0.4}
|
||||||
size: self.size
|
MDLabel:
|
||||||
pos: self.pos
|
text: "READOUT"
|
||||||
GridLayout:
|
font_size: 40
|
||||||
FloatLayout:
|
halign: 'center'
|
||||||
Label:
|
valign: 'center'
|
||||||
pos_hint: {"y":0.4}
|
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||||
text: "READOUT"
|
bold: True
|
||||||
font_size: 40
|
|
||||||
color: (0, 113, 0, 1)
|
MDGridLayout:
|
||||||
bold: True
|
|
||||||
GridLayout:
|
|
||||||
cols:4
|
cols:4
|
||||||
size_hint: 0.8, 0.3
|
size_hint: 0.8, 0.3
|
||||||
pos_hint: {"x":0.1, "y":0.4}
|
pos_hint: {"x":0.1, "y":0.4}
|
||||||
Label:
|
MDLabel:
|
||||||
text: "Sensor 1: "
|
text: "Sensor 1: "
|
||||||
font_size: 20
|
font_size: 20
|
||||||
Label:
|
MDLabel:
|
||||||
id: sensor1
|
id: sensor1
|
||||||
text: ""
|
text: ""
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
text_size: self.size
|
text_size: self.size
|
||||||
Label:
|
MDLabel:
|
||||||
text: "Sensor 2: "
|
text: "Sensor 2: "
|
||||||
font_size: 20
|
font_size: 20
|
||||||
Label:
|
MDLabel:
|
||||||
id: sensor2
|
id: sensor2
|
||||||
text: ""
|
text: ""
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
text_size: self.size
|
text_size: self.size
|
||||||
Label:
|
MDLabel:
|
||||||
text: "Sensor 3: "
|
text: "Sensor 3: "
|
||||||
font_size: 20
|
font_size: 20
|
||||||
Label:
|
MDLabel:
|
||||||
id: sensor3
|
id: sensor3
|
||||||
text: ""
|
text: ""
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
text_size: self.size
|
text_size: self.size
|
||||||
Label:
|
MDLabel:
|
||||||
text: "Sensor 4: "
|
text: "Sensor 4: "
|
||||||
font_size: 20
|
font_size: 20
|
||||||
Label:
|
MDLabel:
|
||||||
id: sensor4
|
id: sensor4
|
||||||
text: ""
|
text: ""
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
halign: 'left'
|
halign: 'left'
|
||||||
text_size: self.size
|
text_size: self.size
|
||||||
Button:
|
MDFillRoundFlatButton:
|
||||||
text: "Connect"
|
text: "Connect"
|
||||||
size_hint: 0.2, 0.1
|
size_hint: 0.15, 0.09
|
||||||
pos_hint: {"x": 0.5, "y": 0.05}
|
pos_hint: {"x": 0.03, "y": 0.05}
|
||||||
background_color: (255, 0, 0, 0.6)
|
|
||||||
on_release:
|
on_release:
|
||||||
root.start()
|
root.start()
|
||||||
Button:
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
text: "Disconnect"
|
text: "Disconnect"
|
||||||
size_hint: 0.2, 0.1
|
size_hint: 0.15, 0.09
|
||||||
pos_hint: {"x": 0.7, "y": 0.05}
|
pos_hint: {"x": 0.2, "y": 0.05}
|
||||||
background_color: (255, 0, 0, 0.6)
|
|
||||||
on_release:
|
on_release:
|
||||||
root.end()
|
root.end()
|
||||||
Button:
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
text: "Back"
|
text: "Back"
|
||||||
size_hint: 0.3, 0.1
|
size_hint: 0.15, 0.09
|
||||||
pos_hint: {"x":0.05, "y":0.05}
|
pos_hint: {"right": 0.95, "y":0.05}
|
||||||
background_color: (255, 0, 0, 0.6)
|
md_bg_color: app.theme_cls.primary_dark
|
||||||
on_release:
|
on_release:
|
||||||
root.end()
|
root.end()
|
||||||
app.root.current = "home"
|
app.root.current = "home"
|
||||||
root.manager.transition.direction = "left"
|
root.manager.transition.direction = "left"
|
||||||
ToggleButton:
|
|
||||||
id: mode_selector
|
MDGridLayout:
|
||||||
|
cols: 2
|
||||||
size_hint: 0.15, 0.1
|
size_hint: 0.15, 0.1
|
||||||
pos_hint: {"x":0.1, "y":0.2}
|
pos_hint: {"x":0.1, "y":0.15}
|
||||||
text: "Normal Mode" if self.state == "normal" else "Fast Mode"
|
MDLabel:
|
||||||
on_text: root.switch_mode(mode_selector.text)
|
text: "Fast Mode"
|
||||||
background_color: (255,0,0,0.6) if self.state == "normal" else (0,0,255,0.6)
|
valign: "center"
|
||||||
Button:
|
MDSwitch:
|
||||||
|
id: mode_selector
|
||||||
|
on_active: root.switch_mode()
|
||||||
|
icon_active: "check"
|
||||||
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
text: "Configuration"
|
text: "Configuration"
|
||||||
size_hint: 0.15, 0.1
|
size_hint: 0.1, 0.07
|
||||||
pos_hint: {"x":0.7, "y":0.2}
|
pos_hint: {"x":0.45, "y":0.06}
|
||||||
background_color: (255, 0, 0, 0.6)
|
md_bg_color: app.theme_cls.accent_dark
|
||||||
on_release:
|
on_release:
|
||||||
root.end()
|
root.end()
|
||||||
app.root.current = "program"
|
app.root.current = "program"
|
||||||
root.manager.transition.direction = "down"
|
root.manager.transition.direction = "down"
|
||||||
Label:
|
|
||||||
id: status
|
MDGridLayout:
|
||||||
text: "Status will appear here"
|
size_hint: 0.2, None
|
||||||
font_size: 10
|
spacing: 0
|
||||||
pos_hint: {"x":0.4, "y": 0.3}
|
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'
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ from time import time
|
|||||||
from typing import List, override
|
from typing import List, override
|
||||||
from kivymd.uix.screen import MDScreen
|
from kivymd.uix.screen import MDScreen
|
||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
from gui.popups.popups import SingleRowPopup, TwoActionPopup, empty_func
|
|
||||||
from kivy.clock import Clock, ClockEvent
|
from kivy.clock import Clock, ClockEvent
|
||||||
|
from kivymd.uix.button import MDFlatButton
|
||||||
|
from kivymd.uix.dialog import MDDialog
|
||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ class ReaderThread(threading.Thread):
|
|||||||
# Hook to output stream
|
# Hook to output stream
|
||||||
if self._instructions.hook_main():
|
if self._instructions.hook_main():
|
||||||
# We are now hooked to the stream (i.e. data is synced)
|
# 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
|
# making it exit using the stop function
|
||||||
while self._run:
|
while self._run:
|
||||||
@@ -70,17 +71,22 @@ class ReaderThread(threading.Thread):
|
|||||||
for i in range(4):
|
for i in range(4):
|
||||||
# The slicing that happens here uses offsets automatically calculated from the sensor id
|
# The slicing that happens here uses offsets automatically calculated from the sensor id
|
||||||
# This allows for short code
|
# This allows for short code
|
||||||
data.append(
|
try:
|
||||||
f"Tadc: {
|
data.append(
|
||||||
self._decoder.decode_int(received[12 * i:12 * i + 4])
|
f"Tadc: {
|
||||||
}\nTemp: {
|
self._decoder.decode_int(received[12 * i:12 * i + 4])
|
||||||
round(self._decoder.decode_float(received[12 * i + 5:12 * i + 11]) * 1000) / 1000
|
}\nTemp: {
|
||||||
}°C\nDC: {
|
round(self._decoder.decode_float(received[12 * i + 5:12 * i + 11]) * 1000) / 1000
|
||||||
round((self._decoder.decode_float_long(received[48 + 5 * i: 52 + 5 * i]) / 65535.0 * 100) * 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
|
# 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)
|
synced_queue.put(data)
|
||||||
else:
|
else:
|
||||||
# Send error message to the UI updater
|
# Send error message to the UI updater
|
||||||
@@ -104,6 +110,30 @@ class MainScreen(MDScreen):
|
|||||||
# Set some variables
|
# Set some variables
|
||||||
self._com = com
|
self._com = com
|
||||||
self._event = None
|
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
|
# Prepare the reader thread
|
||||||
self._prepare_reader()
|
self._prepare_reader()
|
||||||
@@ -115,36 +145,31 @@ class MainScreen(MDScreen):
|
|||||||
|
|
||||||
def _prepare_reader(self):
|
def _prepare_reader(self):
|
||||||
self._reader = ReaderThread()
|
self._reader = ReaderThread()
|
||||||
self._reader.setDaemon(True)
|
self._reader.daemon = True
|
||||||
self._reader.set_com(self._com)
|
self._reader.set_com(self._com)
|
||||||
|
|
||||||
# Start the connection to the micro-controller to read data from it.
|
# Start the connection to the micro-controller to read data from it.
|
||||||
# This also now starts the reader thread to continuously read out data
|
# This also now starts the reader thread to continuously read out data
|
||||||
def start(self):
|
def start(self):
|
||||||
# Prevent running multiple times
|
# Prevent running multiple times
|
||||||
|
self.connection_error_dialog.dismiss()
|
||||||
if self._has_connected:
|
if self._has_connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.ids.status.text = "Connecting..."
|
self.ids.status.text = "Connecting..."
|
||||||
if self._com.connect():
|
if self._com.connect():
|
||||||
print("Acquired connection")
|
print("[ COM ] Connection Acquired")
|
||||||
self._has_connected = True
|
self._has_connected = True
|
||||||
self._has_run = True
|
self._has_run = True
|
||||||
if self._has_run:
|
if self._has_run:
|
||||||
self._prepare_reader()
|
self._prepare_reader()
|
||||||
# Start communication
|
# Start communication
|
||||||
self._reader.start()
|
self._reader.start()
|
||||||
print("Reader has started")
|
print("[ COM ] Reader has started")
|
||||||
self._event = Clock.schedule_interval(self._update_screen, 0.5)
|
self._event = Clock.schedule_interval(self._update_screen, 0.5)
|
||||||
else:
|
else:
|
||||||
self.ids.status.text = "Connection failed"
|
self.ids.status.text = "Connection failed"
|
||||||
TwoActionPopup().open(
|
self.connection_error_dialog.open()
|
||||||
"Failed to connect. Do you want to retry?",
|
|
||||||
"Cancel",
|
|
||||||
empty_func,
|
|
||||||
"Retry",
|
|
||||||
self.start,
|
|
||||||
)
|
|
||||||
|
|
||||||
# End connection to micro-controller and set it back to normal mode
|
# End connection to micro-controller and set it back to normal mode
|
||||||
def end(self, set_msg: bool = True):
|
def end(self, set_msg: bool = True):
|
||||||
@@ -166,10 +191,12 @@ class MainScreen(MDScreen):
|
|||||||
self._com.close()
|
self._com.close()
|
||||||
if set_msg:
|
if set_msg:
|
||||||
self.ids.status.text = "Connection terminated"
|
self.ids.status.text = "Connection terminated"
|
||||||
|
self.ids.port.text = "Port: Not connected"
|
||||||
|
self._has_connected = False
|
||||||
print("Connection terminated")
|
print("Connection terminated")
|
||||||
|
|
||||||
# A helper function to update the screen. Is called on an interval
|
# A helper function to update the screen. Is called on an interval
|
||||||
def _update_screen(self, dt):
|
def _update_screen(self, _):
|
||||||
update = []
|
update = []
|
||||||
try:
|
try:
|
||||||
update = synced_queue.get_nowait()
|
update = synced_queue.get_nowait()
|
||||||
@@ -182,8 +209,10 @@ class MainScreen(MDScreen):
|
|||||||
if update[0] == "ERR_HOOK":
|
if update[0] == "ERR_HOOK":
|
||||||
self.ids.status.text = "Hook failed"
|
self.ids.status.text = "Hook failed"
|
||||||
self.end(False)
|
self.end(False)
|
||||||
elif update[0] == "HOOK":
|
if len(update) == 2:
|
||||||
|
if update[0] == "HOOK":
|
||||||
self.ids.status.text = "Connected to controller"
|
self.ids.status.text = "Connected to controller"
|
||||||
|
self.ids.port.text = "Port: " + update[1]
|
||||||
else:
|
else:
|
||||||
self.ids.sensor1.text = update[0]
|
self.ids.sensor1.text = update[0]
|
||||||
self.ids.sensor2.text = update[1]
|
self.ids.sensor2.text = update[1]
|
||||||
@@ -198,9 +227,10 @@ class MainScreen(MDScreen):
|
|||||||
self.ids.sensor3.text = ""
|
self.ids.sensor3.text = ""
|
||||||
self.ids.sensor4.text = ""
|
self.ids.sensor4.text = ""
|
||||||
self.ids.status.text = "Status will appear here"
|
self.ids.status.text = "Status will appear here"
|
||||||
|
self.ids.port.text = "Port: Not connected"
|
||||||
|
|
||||||
# Switch the mode for the micro-controller
|
# 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
|
# Store if we have been connected to the micro-controller before mode was switched
|
||||||
was_connected = self._has_connected
|
was_connected = self._has_connected
|
||||||
|
|
||||||
@@ -210,12 +240,12 @@ class MainScreen(MDScreen):
|
|||||||
|
|
||||||
# Try to set the new mode
|
# Try to set the new mode
|
||||||
try:
|
try:
|
||||||
if new_mode == "Normal Mode":
|
if self._fast_mode:
|
||||||
self._com.send("NM")
|
self._com.send("NM")
|
||||||
else:
|
else:
|
||||||
self._com.send("FM")
|
self._com.send("FM")
|
||||||
except:
|
except:
|
||||||
SingleRowPopup().open("Failed to switch modes")
|
self.mode_switch_error_dialog.open()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.ids.status.text = "Mode set"
|
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>:
|
<ProgramScreen>:
|
||||||
name: "program"
|
name: "program"
|
||||||
on_enter: self.config_loader = root.load_config()
|
on_enter: self.config_loader = root.load_config()
|
||||||
md_bg_color: app.theme_cls.primary_color
|
|
||||||
FloatLayout:
|
FloatLayout:
|
||||||
Label:
|
MDGridLayout:
|
||||||
text: "Configuration"
|
cols: 1
|
||||||
font_size: 40
|
pos_hint: {'x': 0, 'y': 0.4}
|
||||||
color: (0, 113, 0, 1)
|
MDLabel:
|
||||||
bold: True
|
text: "Configuration"
|
||||||
pos_hint: {"y":0.4}
|
font_size: 40
|
||||||
GridLayout:
|
halign: 'center'
|
||||||
size_hint: 0.8, 0.5
|
valign: 'center'
|
||||||
pos_hint: {"x":0.1, "y":0.2}
|
pos_hint: {'center_x': 0, 'center_y': 0}
|
||||||
|
bold: True
|
||||||
|
|
||||||
|
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
|
cols: 4
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 1, a:"
|
|
||||||
TextInput:
|
|
||||||
id: s1_a
|
id: s1_a
|
||||||
multiline: False
|
hint_text: 'Sensor 1 a'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 1, b:"
|
|
||||||
TextInput:
|
|
||||||
id: s1_b
|
id: s1_b
|
||||||
multiline: False
|
hint_text: 'Sensor 1 b'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 1, c:"
|
|
||||||
TextInput:
|
|
||||||
id: s1_c
|
id: s1_c
|
||||||
multiline: False
|
hint_text: 'Sensor 1 c'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 1, Temp:"
|
|
||||||
TextInput:
|
|
||||||
id: s1_t
|
id: s1_t
|
||||||
multiline: False
|
hint_text: 'Sensor 1 Temperature'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
|
||||||
text: "Sensor 2, a:"
|
MDTextField:
|
||||||
TextInput:
|
|
||||||
id: s2_a
|
id: s2_a
|
||||||
multiline: False
|
hint_text: 'Sensor 2 a'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 2, b:"
|
|
||||||
TextInput:
|
|
||||||
id: s2_b
|
id: s2_b
|
||||||
multiline: False
|
hint_text: 'Sensor 2 b'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 2, c:"
|
|
||||||
TextInput:
|
|
||||||
id: s2_c
|
id: s2_c
|
||||||
multiline: False
|
hint_text: 'Sensor 2 c'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 2, Temp:"
|
|
||||||
TextInput:
|
|
||||||
id: s2_t
|
id: s2_t
|
||||||
multiline: False
|
hint_text: 'Sensor 2 Temperature'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
|
||||||
text: "Sensor 3, a:"
|
MDTextField:
|
||||||
TextInput:
|
|
||||||
id: s3_a
|
id: s3_a
|
||||||
multiline: False
|
hint_text: 'Sensor 3 a'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 3, b:"
|
|
||||||
TextInput:
|
|
||||||
id: s3_b
|
id: s3_b
|
||||||
multiline: False
|
hint_text: 'Sensor 3 b'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 3, c:"
|
|
||||||
TextInput:
|
|
||||||
id: s3_c
|
id: s3_c
|
||||||
multiline: False
|
hint_text: 'Sensor 3 c'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 3, Temp:"
|
|
||||||
TextInput:
|
|
||||||
id: s3_t
|
id: s3_t
|
||||||
multiline: False
|
hint_text: 'Sensor 3 Temperature'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
|
||||||
text: "Sensor 4, a:"
|
MDTextField:
|
||||||
TextInput:
|
|
||||||
id: s4_a
|
id: s4_a
|
||||||
multiline: False
|
hint_text: 'Sensor 4 a'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 4, b:"
|
|
||||||
TextInput:
|
|
||||||
id: s4_b
|
id: s4_b
|
||||||
multiline: False
|
hint_text: 'Sensor 4 b'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 4, c:"
|
|
||||||
TextInput:
|
|
||||||
id: s4_c
|
id: s4_c
|
||||||
multiline: False
|
hint_text: 'Sensor 4 c'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Label:
|
MDTextField:
|
||||||
text: "Sensor 4, Temp:"
|
|
||||||
TextInput:
|
|
||||||
id: s4_t
|
id: s4_t
|
||||||
multiline: False
|
hint_text: 'Sensor 4 Temperature'
|
||||||
input_filter: "float"
|
on_text: root.validate_float(self)
|
||||||
Button:
|
|
||||||
|
MDFillRoundFlatButton:
|
||||||
|
size_hint: 0.1, 0.07
|
||||||
text: "Back"
|
text: "Back"
|
||||||
size_hint: 0.1, 0.1
|
|
||||||
pos_hint: {"x":0.1, "y":0.1}
|
pos_hint: {"x":0.1, "y":0.1}
|
||||||
background_color: (255, 0, 0, 0.6)
|
background_color: (255, 0, 0, 0.6)
|
||||||
on_release:
|
on_release:
|
||||||
app.root.current = "main"
|
app.root.current = "main"
|
||||||
root.manager.transition.direction = "up"
|
root.manager.transition.direction = "up"
|
||||||
Button:
|
MDFillRoundFlatButton:
|
||||||
|
size_hint: 0.15, 0.09
|
||||||
text: "Save"
|
text: "Save"
|
||||||
size_hint: 0.2, 0.1
|
|
||||||
pos_hint: {"x":0.6, "y":0.1}
|
pos_hint: {"x":0.6, "y":0.1}
|
||||||
background_color: (255, 0, 0, 0.6)
|
|
||||||
on_release:
|
on_release:
|
||||||
root.save()
|
root.save()
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from kivymd.uix.screen import MDScreen
|
|||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
from lib.decoder import Decoder
|
from lib.decoder import Decoder
|
||||||
from lib.instructions import Instructions
|
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 lib.com import ComSuperClass
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
|
|
||||||
@@ -18,13 +19,60 @@ class ProgramScreen(MDScreen):
|
|||||||
self._com = com
|
self._com = com
|
||||||
self._instructions = Instructions(com)
|
self._instructions = Instructions(com)
|
||||||
self._decoder = Decoder()
|
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)
|
super().__init__(**kw)
|
||||||
|
|
||||||
def load_config(self):
|
def load_config(self):
|
||||||
Clock.schedule_once(self._load)
|
Clock.schedule_once(lambda dt: self._load())
|
||||||
|
|
||||||
# Load the current configuration from the micro-controller
|
# 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)
|
# Hook to the microcontroller's data stream (i.e. sync up with it)
|
||||||
if self._instructions.hook("RD", ["\n", "R", "D", "\n"]):
|
if self._instructions.hook("RD", ["\n", "R", "D", "\n"]):
|
||||||
config: List[List[str]] = []
|
config: List[List[str]] = []
|
||||||
@@ -37,13 +85,7 @@ class ProgramScreen(MDScreen):
|
|||||||
received = self._com.receive(28)
|
received = self._com.receive(28)
|
||||||
except:
|
except:
|
||||||
# Open error popup
|
# Open error popup
|
||||||
TwoActionPopup().open(
|
self.connection_error_dialog.open()
|
||||||
"Failed to connect to micro-controller, retry?",
|
|
||||||
"Cancel",
|
|
||||||
empty_func,
|
|
||||||
"Retry",
|
|
||||||
lambda: self._load(0),
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create a list of strings to store the config for the sensor
|
# Create a list of strings to store the config for the sensor
|
||||||
@@ -58,16 +100,11 @@ class ProgramScreen(MDScreen):
|
|||||||
|
|
||||||
# Add it to the config
|
# Add it to the config
|
||||||
config.append(config_sensor_i)
|
config.append(config_sensor_i)
|
||||||
|
self.ids.status.text = ""
|
||||||
|
|
||||||
self._set_ui(config)
|
self._set_ui(config)
|
||||||
else:
|
else:
|
||||||
TwoActionPopup().open(
|
self.connection_error_dialog.open()
|
||||||
"Failed to connect to micro-controller, retry?",
|
|
||||||
"Cancel",
|
|
||||||
empty_func,
|
|
||||||
"Retry",
|
|
||||||
lambda: self._load(0),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set the elements of the UI to the values of the config
|
# Set the elements of the UI to the values of the config
|
||||||
def _set_ui(self, config: List[List[str]]):
|
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
|
# Transmit the changed data to the micro-controller to reconfigure it
|
||||||
def save(self):
|
def save(self):
|
||||||
|
self.ids.status.text = "Saving..."
|
||||||
data = self._read_ui()
|
data = self._read_ui()
|
||||||
if data == None:
|
if data == None:
|
||||||
SingleRowPopup().open("Some fields are missing values!")
|
self.missing_fields_error_dialog()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self._instructions.change_config(data)
|
self._instructions.change_config(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
SingleRowPopup().open("Could not save data!")
|
self.save_error_dialog.open()
|
||||||
return
|
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)
|
# Load the design file for this screen (.kv files)
|
||||||
|
|||||||
27
lib/com.py
27
lib/com.py
@@ -6,16 +6,23 @@ import serial.tools.list_ports
|
|||||||
|
|
||||||
|
|
||||||
class ComSuperClass(ABC):
|
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._serial: Optional[serial.Serial] = None
|
||||||
self._filters = filters if filters != None else [ 'USB-Serial Controller', 'Prolific USB-Serial Controller' ]
|
self._filters = (
|
||||||
self._port_override = ''
|
filters
|
||||||
|
if filters != None
|
||||||
|
else ["USB-Serial Controller", "Prolific USB-Serial Controller"]
|
||||||
|
)
|
||||||
|
self._port_override = ""
|
||||||
self._baudrate = baudrate if baudrate != None else 19200
|
self._baudrate = baudrate if baudrate != None else 19200
|
||||||
self._err = None
|
self._err = None
|
||||||
|
|
||||||
def set_port_override(self, override: str) -> None:
|
def set_port_override(self, override: str) -> None:
|
||||||
"""Set the port override, to disable port search"""
|
"""Set the port override, to disable port search"""
|
||||||
self._port_override = override
|
if override != "" and override != "None":
|
||||||
|
self._port_override = override
|
||||||
|
|
||||||
def get_error(self) -> serial.SerialException | None:
|
def get_error(self) -> serial.SerialException | None:
|
||||||
return self._err
|
return self._err
|
||||||
@@ -58,7 +65,7 @@ class Com(ComSuperClass):
|
|||||||
|
|
||||||
def get_comport(self) -> str:
|
def get_comport(self) -> str:
|
||||||
"""Find the comport the microcontroller has attached to"""
|
"""Find the comport the microcontroller has attached to"""
|
||||||
if self._port_override != '':
|
if self._port_override != "":
|
||||||
return self._port_override
|
return self._port_override
|
||||||
|
|
||||||
# Catch all errors and simply return an empty string if search unsuccessful
|
# Catch all errors and simply return an empty string if search unsuccessful
|
||||||
@@ -80,7 +87,7 @@ class Com(ComSuperClass):
|
|||||||
comport = self.get_comport()
|
comport = self.get_comport()
|
||||||
|
|
||||||
# Comport search returns empty string if search unsuccessful
|
# Comport search returns empty string if search unsuccessful
|
||||||
if comport == '':
|
if comport == "":
|
||||||
try:
|
try:
|
||||||
self._serial = serial.Serial(comport, self._baudrate, timeout=5)
|
self._serial = serial.Serial(comport, self._baudrate, timeout=5)
|
||||||
except serial.SerialException as e:
|
except serial.SerialException as e:
|
||||||
@@ -108,7 +115,7 @@ class Com(ComSuperClass):
|
|||||||
if self._serial != None:
|
if self._serial != None:
|
||||||
return self._serial.read(byte_count)
|
return self._serial.read(byte_count)
|
||||||
else:
|
else:
|
||||||
raise Exception('ERR_CONNECTING')
|
raise Exception("ERR_CONNECTING")
|
||||||
|
|
||||||
def send(self, msg: str) -> None:
|
def send(self, msg: str) -> None:
|
||||||
"""Send a string over serial connection. Will open a connection if none is available"""
|
"""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:
|
if self._serial != None:
|
||||||
self._serial.write(msg.encode())
|
self._serial.write(msg.encode())
|
||||||
else:
|
else:
|
||||||
raise Exception('ERR_CONNECTING')
|
raise Exception("ERR_CONNECTING")
|
||||||
|
|
||||||
def send_float(self, msg: float) -> None:
|
def send_float(self, msg: float) -> None:
|
||||||
"""Send a float number over serial connection"""
|
"""Send a float number over serial connection"""
|
||||||
self._connection_check()
|
self._connection_check()
|
||||||
if self._serial != None:
|
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:
|
else:
|
||||||
raise Exception('ERR_CONNECTING')
|
raise Exception("ERR_CONNECTING")
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ class Instructions:
|
|||||||
def __init__(self, com: ComSuperClass) -> None:
|
def __init__(self, com: ComSuperClass) -> None:
|
||||||
self._com = com
|
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.
|
# Helper method to hook to the data stream according to protocol.
|
||||||
# You can specify the sequence that the program listens to to sync up,
|
# 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
|
# as an array of strings, that should each be of length one and only contain
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class SensorConfig:
|
|||||||
|
|
||||||
class Com(ComSuperClass):
|
class Com(ComSuperClass):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, baudrate: int = 19200, filters: Optional[list[str]] = None
|
self, fail_sim: int, baudrate: int = 19200, filters: Optional[list[str]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
# Calling the constructor of the super class to assign defaults
|
# Calling the constructor of the super class to assign defaults
|
||||||
print("\n\nWARNING: Using testing library for communication!\n\n")
|
print("\n\nWARNING: Using testing library for communication!\n\n")
|
||||||
@@ -62,6 +62,7 @@ class Com(ComSuperClass):
|
|||||||
|
|
||||||
self.__reconf_sensor = 0
|
self.__reconf_sensor = 0
|
||||||
self.__reconf_step = 0
|
self.__reconf_step = 0
|
||||||
|
self.__fail_sim = fail_sim
|
||||||
|
|
||||||
self.__config: List[SensorConfig] = [
|
self.__config: List[SensorConfig] = [
|
||||||
SensorConfig(),
|
SensorConfig(),
|
||||||
@@ -78,11 +79,11 @@ class Com(ComSuperClass):
|
|||||||
self._port_override = override
|
self._port_override = override
|
||||||
|
|
||||||
def get_comport(self) -> str:
|
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:
|
def connect(self) -> bool:
|
||||||
# Randomly return false in 1 in 20 ish cases
|
# Randomly return false in 1 in fail_sim ish cases
|
||||||
if random.randint(0, 20) == 1:
|
if random.randint(0, self.__fail_sim) == 0:
|
||||||
print("Simulating error to connect")
|
print("Simulating error to connect")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user