App launching, some porting work complete

This commit is contained in:
2025-03-05 14:07:58 +01:00
parent ffd75d94dc
commit 92fcc4a6e7
20 changed files with 428 additions and 17 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,52 @@
import os
import configparser
from typing import override
config = configparser.ConfigParser()
config.read('./config.ini')
# Load config and disable kivy log if necessary
if config['Dev Settings']['verbose'] == "True":
pass
else:
os.environ["KIVY_NO_CONSOLELOG"] = "1"
# Load kivy modules
from kivy.core.window import Window, Config
from kivy.uix.screenmanager import ScreenManager
from kivy.app import App
# Load other libraries
import threading
# Store the current app version
app_version = f"{config['Info']['version']}{config['Info']['subVersion']}"
#---------#
# Screens #
#---------#
from gui.home.home import HomeScreen
from gui.credits.credits import CreditsScreen
from gui.settings.settings import SettingsScreen
#----------------#
# Screen Manager #
#----------------#
class BiogasControllerApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.screen_manager = ScreenManager()
@override
def build(self):
self.icon = './BiogasControllerAppLogo.png'
self.title = 'BiogasControllerApp-' + app_version
self.screen_manager.add_widget(HomeScreen(name='home'))
self.screen_manager.add_widget(CreditsScreen(name='credits'))
self.screen_manager.add_widget(SettingsScreen(name='settings'))
return self.screen_manager
if __name__ == "__main__":
BiogasControllerApp().run()

View File

@@ -0,0 +1,19 @@
[Port Settings]
specificport = None
[UI Config]
sizeh = 600
sizew = 800
[Dev Settings]
verbose = True
log_level = DEBUG
disableconnectioncheck = False
[License]
show = 1
[Info]
version = V2.3.0
subversion =

View File

@@ -0,0 +1,3 @@
from gui.popups import *

View File

@@ -0,0 +1,27 @@
<CreditsScreen>:
name: "credits"
canvas.before:
Color:
rgba: (50,50,50,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 = "settings"
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 rework of the BiogasControllerApp V1, that was originally programmed by S. Reichmuth."
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

View File

@@ -0,0 +1,8 @@
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
class CreditsScreen(Screen):
pass
Builder.load_file('./gui/credits/credits.kv')

View File

@@ -0,0 +1,46 @@
<HomeScreen>:
name: "home"
canvas.before:
Color:
rgba: (50,50,50,0.2)
Rectangle:
size: self.size
pos: self.pos
GridLayout:
cols:1
Label:
text: "BiogasanlageControllerApp"
font_size: 50
color: (0, 113, 0, 1)
bold:True
italic:True
FloatLayout:
GridLayout:
cols: 2
size_hint: 0.8, 0.8
pos_hint: {"x": 0.1, "y": 0.1}
Button:
text: "Start"
background_color: (255, 0, 0, 0.6)
font_size: 30
on_release:
root.start()
Button:
text: "Quit"
background_color: (255, 0, 0, 0.6)
font_size: 30
on_release:
root.quit()
Label:
text: "App version"
id: app_version
font_size: 13
pos_hint: {"y": -0.45, "x":0.05}
Button:
text: "Settings"
font_size: 13
size_hint: 0.07, 0.06
pos_hint: {"x":0.01, "y":0.01}
background_color: (50, 0, 0, 0.2)
on_release:
root.to_settings()

View File

@@ -0,0 +1,17 @@
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
class HomeScreen(Screen):
def start(self):
pass
def quit(self):
pass
def to_settings(self):
self.manager.current = 'settings'
self.manager.transition.direction = 'down'
Builder.load_file('./gui/home/home.kv')

View File

@@ -0,0 +1,105 @@
<SingleRowPopUp>:
title: "INFORMATION"
size_hint: 0.7, 0.5
auto_dismiss: True
GridLayout:
cols: 1
Label:
id: msg
text_size: self.width, None
GridLayout:
cols: 1
Button:
text: "Ok"
on_release:
root.dismiss()
<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()
<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
GridLayout:
cols:2
Button:
id: btn1
text: "Details"
on_release:
root.action()
Button:
text:"Ok"
on_release:
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:
Label:
cols:1
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()

View File

@@ -0,0 +1,16 @@
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
class HomeScreen(Screen):
def start(self):
pass
def quit(self):
pass
def to_settings(self):
pass
Builder.load_file('./gui/home/home.kv')

View File

@@ -0,0 +1,16 @@
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
class HomeScreen(Screen):
def start(self):
pass
def quit(self):
pass
def to_settings(self):
pass
Builder.load_file('./gui/home/home.kv')

View File

@@ -0,0 +1,37 @@
<SettingsScreen>:
name: "settings"
canvas.before:
Color:
rgba: (50,50,50,0.2)
Rectangle:
size: self.size
pos: self.pos
GridLayout:
cols: 1
Label:
text: "Settings"
font_size: 40
color: (0, 113, 0, 1)
bold: True
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"

View File

@@ -0,0 +1,10 @@
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
import webbrowser
class SettingsScreen(Screen):
def report_issue(self):
webbrowser.open('https://github.com/janishutz/BiogasControllerApp/issues', new=2)
Builder.load_file('./gui/settings/settings.kv')

View File

@@ -9,11 +9,22 @@ class Com:
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 = filters if filters != None else [ 'USB-Serial Controller', 'Prolific USB-Serial Controller' ]
self._port_override = '' self._port_override = ''
self._baudrate = 19200
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 self._port_override = override
def _connection_check(self) -> bool:
if self._serial == None:
return self._open()
if self._serial != None:
if not self._serial.is_open:
self._serial.open()
return True
else:
return False
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 != '':
@@ -34,20 +45,24 @@ class Com:
return '' return ''
def connect(self, baud_rate: int, port_override: Optional[str] = None) -> bool: def _open(self) -> bool:
"""Try to find a comport and connect to the microcontroller. Returns the success as a boolean"""
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, baud_rate, timeout=5) self._serial = serial.Serial(comport, self._baudrate, timeout=5)
except: except:
return False return False
return True return True
else: else:
return False return False
def connect(self, baud_rate: int) -> bool:
"""Try to find a comport and connect to the microcontroller. Returns the success as a boolean"""
self._baudrate = baud_rate
return self._connection_check()
def close(self) -> None: def close(self) -> None:
"""Close the serial connection, if possible""" """Close the serial connection, if possible"""
if self._serial != None: if self._serial != None:
@@ -58,23 +73,24 @@ class Com:
def receive(self, byte_count: int) -> bytes: def receive(self, byte_count: int) -> bytes:
"""Recieve bytes from microcontroller over serial. Returns bytes. Might want to decode using functions from lib.tools""" """Recieve bytes from microcontroller over serial. Returns bytes. Might want to decode using functions from lib.tools"""
if self._serial == None: self._connection_check()
self.connect(19200)
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_CONNECTION') raise Exception('ERR_CONNECTING')
def send(self, msg: str) -> None: def send(self, msg: str) -> None:
"""Send a string over serial connection.""" """Send a string over serial connection. Will open a connection if none is available"""
if self._serial == None: self._connection_check()
self.connect(19200)
if self._serial != None: if self._serial != None:
self._serial.write(msg.encode()) self._serial.write(msg.encode())
else:
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"""
if self._serial == None: self._connection_check()
self.connect(19200)
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:
raise Exception('ERR_CONNECTING')

View File

@@ -1,22 +1,61 @@
from typing import Optional
import lib.com import lib.com
import lib.decoder import lib.decoder
import time
# TODO: Load filters (for comport search) # TODO: Load filters (for comport search)
com = lib.com.Com() com = lib.com.Com()
decoder = lib.decoder.Decoder() decoder = lib.decoder.Decoder()
class Instructions: class Instructions:
def __init__(self) -> None: def set_port_override(self, override: str) -> None:
pass com.set_port_override(override)
def _hook(self, instruction: str, sequence: list[str]) -> bool: def _hook(self, instruction: str, sequence: list[str]) -> bool:
# Send instruction to microcontroller to start hooking process
com.send(instruction)
# Record start time to respond to timeout
start = time.time()
# Check for timeout
pointer = 0
sequence_max = len(sequence) - 1
while time.time() - start < 5:
if ( decoder.decode_ascii( com.receive(1) ) ) == sequence[pointer]:
pointer += 1
else:
pointer = 0
if pointer == sequence_max:
return True
return False return False
def change_temperature(self, new_temps: list[float]) -> None: def _change_data(self, instruction: str, readback: list[str], data: list[float], readback_length: int) -> None:
pass # Hook to stream
if self._hook(instruction, readback):
while len(data) > 0:
if com.receive(readback_length) != '':
com.send_float(data.pop(0))
else:
com.close()
raise Exception('Failed to transmit data. No response from controller')
com.close()
else:
com.close()
raise ConnectionError('Failed to hook to controller data stream. No fitting response received')
def change_config(self, new_config: list[float]) -> None: def change_config(self, new_config: list[float]) -> None:
pass self._change_data('PR', ['\n', 'P', 'R', '\n'], new_config, 3)
def change_temperature(self, temperatures: list[float]) -> None:
self._change_data('PT', ['\n', 'P', 'T', '\n'], temperatures, 3)
def enable_fastmode(self) -> None:
com.send('FM')
com.close()
def disable_fastmode(self) -> None:
com.send('NM')
com.close()