Skip to content

Commit

Permalink
Merge pull request #44 from AndreWohnsland/dev
Browse files Browse the repository at this point in the history
In GUI log view
  • Loading branch information
AndreWohnsland authored Mar 7, 2023
2 parents 611507d + 887ad15 commit 345c4d3
Show file tree
Hide file tree
Showing 20 changed files with 917 additions and 275 deletions.
24 changes: 22 additions & 2 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ If you don't understand what docker is or what it does, don't worry.
CocktailBerry will perfectly run without Docker installed.
It's for some optional advanced features you can add anytime you are interested or ready for them.

### How to Exit the Program

If you want to exit the program to get to the desktop, because you want to do some adjustments,
just press alt+F4 in the main program on your keyboard.
Like with most programs, this will exit the current opened program.
You will then be on your desktop.

### I don't Need a Password

Just set the password empty / delete all numbers.
If the password setting ist empty, actions requiring a password will automatically succeed without prompting a password.

## Other

### What about Tube Volume
Expand All @@ -146,7 +158,7 @@ If your pumps got a long tube to the bottle, the first cocktail may have too lit
You can set the `MAKER_TUBE_VOLUME` to an approximate value which corresponds to the average of the tube volume.
When applying a new bottle, CocktailBerry will also pump that much volume up.

### Whats up with LEDs
### Implementing LEDs

You can define one or more pins which control a LED (array).
The LEDs will light up during cocktail preparation, as well when the cocktail is finished.
Expand All @@ -155,4 +167,12 @@ Instead of just turning on / off / blinking, the LED will then have some advance
If you want to have multiple ring LEDs having the effect synchronously, you can define the number of identical daisy chained rings.
The program will then not treat this chain as one, but as multiple chains.
This does not include some default LEDs used for general lighting of the machine, because they usually don't need controlling.
It's better to directly connect them to the main source current and turn them on when the machine is turned on.
It's better to directly connect them to the main source current and turn them on when the machine is turned on.

### View Logs

You can either go to the logs folder to have the raw logs.
Or you can go to the option window and select the logs option.
Then you will get a summarized view of the logs.
The latest logs are shown on top.
Identical logs are only shown once, with their latest occurrence time, as well as count.
1 change: 1 addition & 0 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Usually, updating to the lastest version is always a good idea.
| **v1.13.1** | Clearing the database over the CLI |
| **v1.14.0** | Can invert pin, set simultaneous pump count, generic board |
| **v1.15.0** | Control a LED during cocktail preparation |
| **v1.16.0** | Summarized logs over the GUI |

!!! abstract "And much More"
This list is by no means a full list of changes.
Expand Down
5 changes: 5 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ Ensure that you have unchecked the "Reserve space, and not covered by maximised
You can find it under the panel preferences (right click the task bar > panel settings > Advanced).
Unchecking this box usually fixes this problem.

## Reset Config

In case you want to reset the configuration, it is the best way to just delete the custom_config.yaml in the main folder.
This file holds your configuration and will be created with the defaults if it does not exists.

## Problems Installing Software on Raspberry Pi

The Raspberry Pi can sometimes differ from other machines in terms of installation. Here are some issues that might occur.
Expand Down
2 changes: 1 addition & 1 deletion scripts/compile_ui_to_python.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cd .\src\ui_elements\

$files = @("available", "bonusingredient", "bottlewindow", "cocktailmanager", "calibration", "customdialog", "datepicker", "handadds", "keyboard", "optionwindow", "numpad", "progressbarwindow", "teamselection", "passworddialog", "customprompt")
$files = @("available", "bonusingredient", "bottlewindow", "cocktailmanager", "calibration", "customdialog", "datepicker", "handadds", "keyboard", "optionwindow", "numpad", "progressbarwindow", "teamselection", "passworddialog", "customprompt", "logwindow")

foreach ($f in $files) {
pyuic5 -x .\$f.ui -o .\$f.py
Expand Down
7 changes: 7 additions & 0 deletions src/dialog_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def user_okay(self, text: str):
def password_prompt(self):
"""Opens a password prompt, return if successful entered password"""
from src.ui.setup_password_dialog import PasswordDialog
# if password is empty, return true
if cfg.UI_MASTERPASSWORD == "":
return True
password_dialog = PasswordDialog()
if password_dialog.exec_():
return True
Expand Down Expand Up @@ -474,5 +477,9 @@ def adjust_custom_prompt(self, w):
w.yes_button.setText(self.__choose_language("yes_button"))
w.no_button.setText(self.__choose_language("no_button"))

def adjust_log_window(self, w):
"""Translates the elements from the logs window"""
w.button_back.setText(self.__choose_language("back"))


UI_LANGUAGE = UiLanguage()
3 changes: 3 additions & 0 deletions src/language.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ ui:
no_button:
en: 'No'
de: 'Nein'
back:
en: '< Back'
de: '< Zurück'
# dynamic values for maker tab
maker:
add_self:
Expand Down
2 changes: 1 addition & 1 deletion src/logger_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, logger_name: str, filename: str = LogFiles.PRODUCTION):
if not logger.hasHandlers():
file_handler = logging.FileHandler(self.path)
file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s | %(name)s | %(levelname)s: %(message)s", "%Y-%m-%d %H:%M")
formatter = logging.Formatter("%(asctime)s | %(levelname)-8s | %(message)s [%(name)s]", "%Y-%m-%d %H:%M")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

Expand Down
5 changes: 4 additions & 1 deletion src/machine/leds.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,10 @@ def __init__(self, pin: int) -> None:

def _preparation_thread(self):
"""Fills one by one with same random color, then repeats / overwrites old ones"""
wait_ms = 40
# Make the circle / dot approximate 2 rounds per second
wait_ms = 500 / cfg.LED_COUNT
# not faster than 10ms
wait_ms = max(10, wait_ms)
self.turn_on(Color(randint(0, 255), randint(0, 255), randint(0, 255)))
while self.is_preparing:
color = Color(
Expand Down
4 changes: 2 additions & 2 deletions src/tabs/maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from src.config_manager import shared


LOG_HANDLER = LoggerHandler("maker_module")
_LOGGER = LoggerHandler("maker_module")
T = TypeVar('T', int, float)


Expand Down Expand Up @@ -71,7 +71,7 @@ def __generate_maker_log_entry(cocktail_volume: int, cocktail_name: str, taken_t
if not shared.make_cocktail:
pumped_volume = round(cocktail_volume * (taken_time) / max_time)
cancel_log_addition = f" - Recipe canceled at {round(taken_time, 1)} s - {pumped_volume} ml"
LOG_HANDLER.log_event("INFO", f"{volume_string:6} | {cocktail_name}{cancel_log_addition}")
_LOGGER.log_event("INFO", f"{volume_string:6} - {cocktail_name}{cancel_log_addition}")


@logerror
Expand Down
95 changes: 95 additions & 0 deletions src/ui/setup_log_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import re
from pathlib import Path
from collections import Counter
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow
from src.dialog_handler import UI_LANGUAGE
from src.display_controller import DP_CONTROLLER
from src.ui_elements.logwindow import Ui_LogWindow

_DIRPATH = Path(__file__).parent.absolute()
_LOG_FOLDER = _DIRPATH.parents[1] / "logs"
_DEFAULT_SELECTED = "production_logs.log"
_DEBUG_FILE = "debuglog.log"


class LogWindow(QMainWindow, Ui_LogWindow):
""" Creates the log window Widget. """

def __init__(self):
""" Init. Connect all the buttons and set window policy. """
super().__init__()
self.setupUi(self)
self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowStaysOnTopHint) # type: ignore
self.setAttribute(Qt.WA_DeleteOnClose) # type: ignore
DP_CONTROLLER.inject_stylesheet(self)
# Connect all the buttons, generates a list of the numbers an object names to do that
self.button_back.clicked.connect(self.close)

# Get log file names, fill widget, select default, if it exists
self.log_files = self._get_log_files()
self.selection_logs.activated.connect(self._read_logs)
DP_CONTROLLER.fill_single_combobox(self.selection_logs, self.log_files, first_empty=False)
if _DEFAULT_SELECTED in self.log_files:
DP_CONTROLLER.set_combobox_item(self.selection_logs, _DEFAULT_SELECTED)
# activated does only trigger if changed by user, so we need to read in here
self._read_logs()

self.move(0, 0)
UI_LANGUAGE.adjust_log_window(self)
self.showFullScreen()
DP_CONTROLLER.set_display_settings(self)

def _get_log_files(self):
"""Checks the logs folder for all existing log files"""
return [file.name for file in _LOG_FOLDER.glob("*.log")]

def _read_logs(self):
"""Read the current selected log file"""
log_name = self.selection_logs.currentText()
# Return if empty selection
if log_name == "":
return
log_path = _LOG_FOLDER / log_name
log_text = log_path.read_text()
# Handle debug logs differently, since they save error traces,
# just display the read in text from log in this case
if log_name == _DEBUG_FILE:
logs_to_render = self._parse_debug_logs(log_text)
else:
logs_to_render = self._parse_log(log_text)
self.text_display.setText(logs_to_render)

def _parse_log(self, log_text: str):
"""Parse all logs and return display object.
Needs logs from new to old, if same message was already there, skip it.
"""
data: dict[str, str] = {}
counter = Counter()
for line in log_text.splitlines()[::-1]:
date, message = self._parse_log_line(line)
if message not in data:
data[message] = date
counter[message] = 1
else:
counter[message] += 1
log_list_data = [f"{key} ({counter[key]}x, latest: {value})" for key, value in data.items()]
return "\n".join(log_list_data)

def _parse_log_line(self, line: str):
"""Parse the log message and return the timestamp + msg"""
parts = line.split(" | ", maxsplit=1)
parsed_date = parts[0]
# usually, we only get 2 parts, due to the maxsplit
parsed_message = " | ".join(parts[1::])
return parsed_date, parsed_message

def _parse_debug_logs(self, log):
"""Parses and inverts the debug logs"""
# having into group returns also the matched date
# This needs to be joined before inverting.
# Also, the first value is an empty string
date_regex = r"(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2})"
information_list = [x for x in re.split(date_regex, log) if x != ""]
pairs = [" ".join(information_list[i:i + 2]) for i in range(0, len(information_list), 2)]
return "\n".join(pairs[::-1])
13 changes: 13 additions & 0 deletions src/ui/setup_option_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from PyQt5.QtWidgets import QMainWindow

from src.ui.create_config_window import ConfigWindow
from src.ui.setup_log_window import LogWindow
from src.ui_elements.optionwindow import Ui_Optionwindow
from src.display_controller import DP_CONTROLLER
from src.dialog_handler import UI_LANGUAGE
Expand Down Expand Up @@ -51,8 +52,11 @@ def __init__(self, parent):
self.button_backup.clicked.connect(self._create_backup)
self.button_restore.clicked.connect(self._upload_backup)
self.button_export.clicked.connect(SAVE_HANDLER.export_data)
self.button_logs.clicked.connect(self._show_logs)
self.button_rfid.clicked.connect(self._open_rfid_writer)

self.config_window: Optional[ConfigWindow] = None
self.log_window: Optional[LogWindow] = None
UI_LANGUAGE.adjust_option_window(self)
self.showFullScreen()
DP_CONTROLLER.set_display_settings(self)
Expand Down Expand Up @@ -131,3 +135,12 @@ def _get_user_folder_response(self):
if not selected_path:
return None
return Path(selected_path).absolute()

def _show_logs(self):
"""Opens the logs window"""
self.close()
self.log_window = LogWindow()

def _open_rfid_writer(self):
"""Opens the rfid writer window"""
# TODO: Implement the rfid logic
62 changes: 54 additions & 8 deletions src/ui/styles/_elements.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ QWidget {
&[cssClass~='right-shift'] {
margin: 5px 20px 5px 0px;
}
&:disabled {
text-decoration: line-through;
}
}

QTabWidget::pane {
Expand Down Expand Up @@ -275,12 +278,19 @@ QListWidget {
& QScrollBar:vertical {
border-right: 0px solid $border;
}
& QScrollBar:horizontal {
border-bottom: 0px solid $border;
}
}

QScrollArea QScrollBar:vertical {
border-right: $default-element-border;
}

QScrollArea QScrollBar:horizontal {
border-bottom: $default-element-border;
}

/* Moving List Widget here from the single objects */
QScrollBar:vertical {
border: $default-element-border;
Expand All @@ -291,12 +301,24 @@ QScrollBar:vertical {
margin: 25px 0px 25px 0px;
}

QScrollBar::handle:vertical {
background: $primary;
border-radius: $scrollbar-border-radius - 5px;
&:pressed {
background-color: $secondary;
border-color: $secondary;
QScrollBar:horizontal {
border: $default-element-border;
border-bottom: 1px solid $border;
border-radius: $scrollbar-border-radius;
background: $background;
height: 40px;
margin: 0px 25px 0px 25px;
}

QScrollBar::handle {
&:vertical,
&:horizontal {
background: $primary;
border-radius: $scrollbar-border-radius - 5px;
&:pressed {
background-color: $secondary;
border-color: $secondary;
}
}
}

Expand All @@ -311,6 +333,16 @@ QScrollBar::add-line:vertical {
subcontrol-origin: margin;
}

QScrollBar::add-line:horizontal {
border: $default-element-border;
border-top-right-radius: $scrollbar-border-radius;
border-bottom-right-radius: $scrollbar-border-radius;
background-color: $border;
width: 38px;
subcontrol-position: right;
subcontrol-origin: margin;
}

// top button
QScrollBar::sub-line:vertical {
border: $default-element-border;
Expand All @@ -322,14 +354,28 @@ QScrollBar::sub-line:vertical {
subcontrol-origin: margin;
}

QScrollBar::sub-line:horizontal {
border: $default-element-border;
border-top-left-radius: $scrollbar-border-radius;
border-bottom-left-radius: $scrollbar-border-radius;
background-color: $border;
width: 38px;
subcontrol-position: left;
subcontrol-origin: margin;
}

// remove the grid thingy from bg
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
QScrollBar::sub-page:vertical,
QScrollBar::add-page:horizontal,
QScrollBar::sub-page:horizontal {
background: none;
}

QScrollBar::sub-line:vertical:pressed,
QScrollBar::add-line:vertical:pressed {
QScrollBar::add-line:vertical:pressed,
QScrollBar::sub-line:horizontal:pressed,
QScrollBar::add-line:horizontal:pressed {
background-color: $secondary;
border-color: $secondary;
}
Expand Down
Loading

0 comments on commit 345c4d3

Please sign in to comment.