diff --git a/biogascontrollerapp/BiogasControllerAppLogo.png b/biogascontrollerapp/BiogasControllerAppLogo.png new file mode 100644 index 0000000..2bd6e93 Binary files /dev/null and b/biogascontrollerapp/BiogasControllerAppLogo.png differ diff --git a/biogascontrollerapp/biogascontrollerapp.py b/biogascontrollerapp/biogascontrollerapp.py index e69de29..eec58f6 100644 --- a/biogascontrollerapp/biogascontrollerapp.py +++ b/biogascontrollerapp/biogascontrollerapp.py @@ -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() diff --git a/biogascontrollerapp/config.ini b/biogascontrollerapp/config.ini new file mode 100644 index 0000000..467137c --- /dev/null +++ b/biogascontrollerapp/config.ini @@ -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 = + diff --git a/biogascontrollerapp/gui/PopupManager.py b/biogascontrollerapp/gui/PopupManager.py new file mode 100644 index 0000000..adc546c --- /dev/null +++ b/biogascontrollerapp/gui/PopupManager.py @@ -0,0 +1,3 @@ +from gui.popups import * + + diff --git a/biogascontrollerapp/gui/credits.kv b/biogascontrollerapp/gui/credits.kv deleted file mode 100644 index e69de29..0000000 diff --git a/biogascontrollerapp/gui/credits/credits.kv b/biogascontrollerapp/gui/credits/credits.kv new file mode 100644 index 0000000..5f879ad --- /dev/null +++ b/biogascontrollerapp/gui/credits/credits.kv @@ -0,0 +1,27 @@ +: + 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 diff --git a/biogascontrollerapp/gui/credits/credits.py b/biogascontrollerapp/gui/credits/credits.py new file mode 100644 index 0000000..4906c62 --- /dev/null +++ b/biogascontrollerapp/gui/credits/credits.py @@ -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') diff --git a/biogascontrollerapp/gui/home.kv b/biogascontrollerapp/gui/home.kv deleted file mode 100644 index e69de29..0000000 diff --git a/biogascontrollerapp/gui/home/home.kv b/biogascontrollerapp/gui/home/home.kv new file mode 100644 index 0000000..40a78cb --- /dev/null +++ b/biogascontrollerapp/gui/home/home.kv @@ -0,0 +1,46 @@ +: + 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() diff --git a/biogascontrollerapp/gui/home/home.py b/biogascontrollerapp/gui/home/home.py new file mode 100644 index 0000000..4273484 --- /dev/null +++ b/biogascontrollerapp/gui/home/home.py @@ -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') diff --git a/biogascontrollerapp/gui/popups.kv b/biogascontrollerapp/gui/popups.kv deleted file mode 100644 index e69de29..0000000 diff --git a/biogascontrollerapp/gui/popups/popups.kv b/biogascontrollerapp/gui/popups/popups.kv new file mode 100644 index 0000000..6c6f922 --- /dev/null +++ b/biogascontrollerapp/gui/popups/popups.kv @@ -0,0 +1,105 @@ +: + 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() + +: + 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() + +: + 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() + +: + 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() + +: + 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() diff --git a/biogascontrollerapp/gui/popups/popups.py b/biogascontrollerapp/gui/popups/popups.py new file mode 100644 index 0000000..5971a70 --- /dev/null +++ b/biogascontrollerapp/gui/popups/popups.py @@ -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') diff --git a/biogascontrollerapp/gui/program.kv b/biogascontrollerapp/gui/program/program.kv similarity index 100% rename from biogascontrollerapp/gui/program.kv rename to biogascontrollerapp/gui/program/program.kv diff --git a/biogascontrollerapp/gui/program/program.py b/biogascontrollerapp/gui/program/program.py new file mode 100644 index 0000000..5971a70 --- /dev/null +++ b/biogascontrollerapp/gui/program/program.py @@ -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') diff --git a/biogascontrollerapp/gui/settings.kv b/biogascontrollerapp/gui/settings.kv deleted file mode 100644 index e69de29..0000000 diff --git a/biogascontrollerapp/gui/settings/settings.kv b/biogascontrollerapp/gui/settings/settings.kv new file mode 100644 index 0000000..b1354a7 --- /dev/null +++ b/biogascontrollerapp/gui/settings/settings.kv @@ -0,0 +1,37 @@ +: + 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" diff --git a/biogascontrollerapp/gui/settings/settings.py b/biogascontrollerapp/gui/settings/settings.py new file mode 100644 index 0000000..0251b1c --- /dev/null +++ b/biogascontrollerapp/gui/settings/settings.py @@ -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') diff --git a/biogascontrollerapp/lib/com.py b/biogascontrollerapp/lib/com.py index f884688..279e80d 100644 --- a/biogascontrollerapp/lib/com.py +++ b/biogascontrollerapp/lib/com.py @@ -9,11 +9,22 @@ class Com: self._serial: Optional[serial.Serial] = None self._filters = filters if filters != None else [ 'USB-Serial Controller', 'Prolific USB-Serial Controller' ] self._port_override = '' + self._baudrate = 19200 def set_port_override(self, override: str) -> None: """Set the port override, to disable port search""" 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: """Find the comport the microcontroller has attached to""" if self._port_override != '': @@ -34,20 +45,24 @@ class Com: return '' - def connect(self, baud_rate: int, port_override: Optional[str] = None) -> bool: - """Try to find a comport and connect to the microcontroller. Returns the success as a boolean""" + def _open(self) -> bool: comport = self.get_comport() # Comport search returns empty string if search unsuccessful if comport == '': try: - self._serial = serial.Serial(comport, baud_rate, timeout=5) + self._serial = serial.Serial(comport, self._baudrate, timeout=5) except: return False return True else: 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: """Close the serial connection, if possible""" if self._serial != None: @@ -58,23 +73,24 @@ class Com: def receive(self, byte_count: int) -> bytes: """Recieve bytes from microcontroller over serial. Returns bytes. Might want to decode using functions from lib.tools""" - if self._serial == None: - self.connect(19200) + self._connection_check() if self._serial != None: return self._serial.read(byte_count) else: - raise Exception('ERR_CONNECTION') + raise Exception('ERR_CONNECTING') def send(self, msg: str) -> None: - """Send a string over serial connection.""" - if self._serial == None: - self.connect(19200) + """Send a string over serial connection. Will open a connection if none is available""" + self._connection_check() if self._serial != None: self._serial.write(msg.encode()) + else: + raise Exception('ERR_CONNECTING') def send_float(self, msg: float) -> None: """Send a float number over serial connection""" - if self._serial == None: - self.connect(19200) + self._connection_check() if self._serial != None: self._serial.write(bytearray(struct.pack('>f', msg))[0:3]) + else: + raise Exception('ERR_CONNECTING') diff --git a/biogascontrollerapp/lib/instructions.py b/biogascontrollerapp/lib/instructions.py index 182bde2..0db6b8a 100644 --- a/biogascontrollerapp/lib/instructions.py +++ b/biogascontrollerapp/lib/instructions.py @@ -1,22 +1,61 @@ -from typing import Optional import lib.com import lib.decoder +import time # TODO: Load filters (for comport search) com = lib.com.Com() decoder = lib.decoder.Decoder() class Instructions: - def __init__(self) -> None: - pass + def set_port_override(self, override: str) -> None: + com.set_port_override(override) 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 - def change_temperature(self, new_temps: list[float]) -> None: - pass + def _change_data(self, instruction: str, readback: list[str], data: list[float], readback_length: int) -> None: + # 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: - 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()