Skip to content

Commit

Permalink
Merge pull request #144 from DCC-EX:ex-turntable-0.7.0
Browse files Browse the repository at this point in the history
EX-Installer 0.0.18
  • Loading branch information
peteGSX authored Mar 7, 2024
2 parents 9b1b1b8 + 9ff14f3 commit 3a197c8
Show file tree
Hide file tree
Showing 15 changed files with 618 additions and 141 deletions.
Binary file modified dist/EX-Installer-Linux64
Binary file not shown.
Binary file modified dist/EX-Installer-Win32.exe
Binary file not shown.
Binary file modified dist/EX-Installer-Win64.exe
Binary file not shown.
Binary file modified dist/EX-Installer-macOS
Binary file not shown.
2 changes: 1 addition & 1 deletion ex_installer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@ def main(debug):
debug = True
else:
debug = False

# Start the app
main(debug)
5 changes: 5 additions & 0 deletions ex_installer/common_fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ def __init__(self, root):
size=14,
weight="bold")

self.italic_instruction_font = ctk.CTkFont(family=self.default_font,
size=14,
weight="normal",
slant="italic")

self.large_bold_instruction_font = ctk.CTkFont(family=self.default_font,
size=16,
weight="bold")
Expand Down
1 change: 1 addition & 0 deletions ex_installer/common_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def __init__(self, parent, *args, **kwargs):
# Define fonts
self.instruction_font = self.common_fonts.instruction_font
self.bold_instruction_font = self.common_fonts.bold_instruction_font
self.italic_instruction_font = self.common_fonts.italic_instruction_font
self.large_bold_instruction_font = self.common_fonts.large_bold_instruction_font
self.small_italic_instruction_font = self.common_fonts.small_italic_instruction_font
self.title_font = self.common_fonts.title_font
Expand Down
9 changes: 8 additions & 1 deletion ex_installer/compile_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ def show_backup_popup(self):
self.backup_popup.focus()
self.backup_popup.lift(self)

# Ensure pop up is within the confines of the app window to start
main_window = self.winfo_toplevel()
backup_offset_x = main_window.winfo_x() + 75
backup_offset_y = main_window.winfo_y() + 200
self.backup_popup.geometry(f"+{backup_offset_x}+{backup_offset_y}")
self.backup_popup.update()

# Set icon and title
if sys.platform.startswith("win"):
self.backup_popup.after(250, lambda icon=images.DCC_EX_ICON_ICO: self.backup_popup.iconbitmap(icon))
Expand Down Expand Up @@ -337,7 +344,7 @@ def backup_config_files(self, overwrite):
self.backup_button.grid()
else:
if self.backup_path.get() == "":
message = "You must specific a valid folder to backup to"
message = "You must specify a valid folder to backup to"
else:
message = f"{self.backup_path.get()} is not a valid directory"
self.status_text.configure(text=message,
Expand Down
3 changes: 0 additions & 3 deletions ex_installer/ex_commandstation.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,6 @@ def generate_config(self):
else:
line = '#define WIFI_SSID "' + self.wifi_ssid_entry.get() + '"\n'
config_list.append(line)
# if self.wifi_pwd_entry.get() == "":
# param_errors.append("WiFi password not set")
# else:
invalid, issue = self.check_invalid_wifi_password()
if invalid:
param_errors.append(issue)
Expand Down
7 changes: 7 additions & 0 deletions ex_installer/ex_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def __init__(self, *args, **kwargs):
self.info_menu.add_command(label="About", command=self.about)
self.info_menu.add_command(label="DCC-EX Website", command=self.website)
self.info_menu.add_command(label="EX-Installer Instructions", command=self.instructions)
self.info_menu.add_command(label="EX-Installer News", command=self.news)
self.enable_debug = ctk.StringVar(self, value="off")
self.info_menu.add_checkbutton(label="Enable debug logging", command=self.toggle_debug,
variable=self.enable_debug, onvalue="on", offvalue="off")
Expand Down Expand Up @@ -275,6 +276,12 @@ def instructions(self):
"""
webbrowser.open_new("https://dcc-ex.com/ex-installer/index.html")

def news(self):
"""
Link to DCC-EX News articles for EX-Installer
"""
webbrowser.open_new("https://dcc-ex.com/news/tag/ex-installer.html")

def toggle_debug(self):
"""
Function to enable/disable debug logging from the menu
Expand Down
600 changes: 474 additions & 126 deletions ex_installer/ex_turntable.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion ex_installer/select_version_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,9 @@ def copy_config_files(self):
"""
copy_list = fm.get_config_files(self.config_path.get(), pd[self.product]["minimum_config_files"])
if copy_list:
extra_list = fm.get_config_files(self.config_path.get(), pd[self.product]["other_config_files"])
extra_list = None
if "other_config_files" in pd[self.product]:
extra_list = fm.get_config_files(self.config_path.get(), pd[self.product]["other_config_files"])
if extra_list:
copy_list += extra_list
file_copy = fm.copy_config_files(self.config_path.get(), self.product_dir, copy_list)
Expand Down
118 changes: 111 additions & 7 deletions ex_installer/serial_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import sys
import serial
import re
from datetime import datetime

# Import local modules
from . import images
from .common_fonts import CommonFonts
from .file_manager import FileManager as fm

# Define valid monitor highlights
monitor_highlights = {
Expand Down Expand Up @@ -129,8 +131,10 @@ def __init__(self, parent, *args, **kwargs):
self.window_frame.grid(column=0, row=0, sticky="nsew")

# Define fonts for use
button_font = self.common_fonts.button_font
instruction_font = self.common_fonts.instruction_font
self.button_font = self.common_fonts.button_font
self.instruction_font = self.common_fonts.instruction_font
self.bold_instruction_font = self.common_fonts.bold_instruction_font
self.action_button_font = self.common_fonts.action_button_font

self.command_frame = ctk.CTkFrame(self.window_frame, width=790, height=40)
self.monitor_frame = ctk.CTkFrame(self.window_frame, width=790, height=420)
Expand All @@ -148,19 +152,22 @@ def __init__(self, parent, *args, **kwargs):
# Create command frame widgets and layout frame
self.command_history = []
grid_options = {"padx": 5, "pady": 5}
self.command_label = ctk.CTkLabel(self.command_frame, text="Enter command:", font=instruction_font)
self.command_label = ctk.CTkLabel(self.command_frame, text="Enter command:", font=self.instruction_font)
self.command = ctk.StringVar(self)
self.command_entry = ctk.CTkComboBox(self.command_frame, variable=self.command, values=self.command_history,
command=self.send_command)
self.command_entry.bind("<Return>", self.send_command)
self.command_button = ctk.CTkButton(self.command_frame, text="Send", font=button_font, width=80,
self.command_button = ctk.CTkButton(self.command_frame, text="Send", font=self.button_font, width=80,
command=self.send_command)
self.close_button = ctk.CTkButton(self.command_frame, text="Close", font=button_font, width=80,
self.save_log_button = ctk.CTkButton(self.command_frame, text="Save Log", font=self.button_font, width=80,
command=self.show_save_log_popup)
self.close_button = ctk.CTkButton(self.command_frame, text="Close", font=self.button_font, width=80,
command=self.close_monitor)
self.command_label.grid(column=0, row=0, sticky="w", **grid_options)
self.command_entry.grid(column=1, row=0, sticky="ew", **grid_options)
self.command_button.grid(column=2, row=0, sticky="e", pady=5)
self.close_button.grid(column=3, row=0, sticky="e", **grid_options)
self.save_log_button.grid(column=3, row=0, sticky="e", padx=(5, 0), pady=5)
self.close_button.grid(column=4, row=0, sticky="e", **grid_options)

# Create monitor frame widgets and layout frame
self.output_textbox = ctk.CTkTextbox(self.monitor_frame, border_width=3, border_spacing=5,
Expand Down Expand Up @@ -190,7 +197,7 @@ def __init__(self, parent, *args, **kwargs):
foreground="white")

# Create device frame widgets and layout
self.device_label = ctk.CTkLabel(self.device_frame, text=None, font=instruction_font)
self.device_label = ctk.CTkLabel(self.device_frame, text=None, font=self.instruction_font)
self.device_label.grid(column=0, row=0, sticky="ew", padx=5, pady=5)

# Start serial monitor process
Expand Down Expand Up @@ -293,6 +300,103 @@ def send_command(self, event=None):
self.output_textbox.configure(state="disabled")
self.output_textbox.see("end")

def show_save_log_popup(self):
"""
Function to show the message box to select the folder to save device log
"""
if hasattr(self, "log_popup") and self.log_popup is not None and self.log_popup.winfo_exists():
self.log_popup.focus()
else:
self.log_popup = ctk.CTkToplevel(self)
self.log_popup.focus()
self.log_popup.lift(self)

# Ensure pop up is within the confines of the app window to start
main_window = self.winfo_toplevel()
log_offset_x = main_window.winfo_x() + 75
log_offset_y = main_window.winfo_y() + 200
self.log_popup.geometry(f"+{log_offset_x}+{log_offset_y}")
self.log_popup.update()

# Set icon and title
if sys.platform.startswith("win"):
self.log_popup.after(250, lambda icon=images.DCC_EX_ICON_ICO: self.log_popup.iconbitmap(icon))
self.log_popup.title("Save device log")
self.log_popup.withdraw()
self.log_popup.after(250, self.log_popup.deiconify)
self.log_popup.grid_columnconfigure(0, weight=1)
self.log_popup.grid_rowconfigure(0, weight=1)
self.window_frame = ctk.CTkFrame(self.log_popup, fg_color="grey95")
self.window_frame.grid_columnconfigure((0, 1, 2), weight=1)
self.window_frame.grid_rowconfigure((0, 1), weight=1)
self.window_frame.grid(column=0, row=0, sticky="nsew")
self.folder_label = ctk.CTkLabel(self.window_frame, text="Select the folder to save the log:",
font=self.instruction_font)
self.status_frame = ctk.CTkFrame(self.window_frame, border_width=2)
self.status_label = ctk.CTkLabel(self.status_frame, text="Status:",
font=self.instruction_font)
self.status_text = ctk.CTkLabel(self.status_frame, text="Enter or select destination",
font=self.bold_instruction_font)
self.log_path = ctk.StringVar(value=None)
self.log_path_entry = ctk.CTkEntry(self.window_frame, textvariable=self.log_path,
width=300)
self.browse_button = ctk.CTkButton(self.window_frame, text="Browse",
width=80, command=self.browse_log_dir)
self.save_button = ctk.CTkButton(self.window_frame, width=200, height=50,
text="Save and open log", font=self.action_button_font,
command=self.save_log_file)
self.folder_label.grid(column=0, row=0, padx=(10, 1), pady=(10, 5))
self.log_path_entry.grid(column=1, row=0, padx=1, pady=(10, 5))
self.browse_button.grid(column=2, row=0, padx=(1, 10), pady=(10, 5))
self.save_button.grid(column=0, row=1, columnspan=3, padx=10, pady=5)
self.status_frame.grid_columnconfigure((0, 1), weight=1)
self.status_frame.grid_rowconfigure(0, weight=1)
self.status_frame.grid(column=0, row=2, columnspan=3, sticky="nsew", padx=10, pady=(5, 10))
self.status_label.grid(column=0, row=0, padx=5, pady=5)
self.status_text.grid(column=1, row=0, padx=5, pady=5)

def browse_log_dir(self):
"""
Opens a directory browser dialogue to select the folder to save the device log to
"""
directory = ctk.filedialog.askdirectory()
if directory:
self.log_path.set(directory)
self.log.debug("Save device log to %s", directory)
self.log_popup.focus()

def save_log_file(self):
"""
Function to save the device log file to the chosen location, and open it
"""
if fm.is_valid_dir(self.log_path.get()):
log_name = datetime.now().strftime("device-logs-%Y%m%d-%H%M%S.log")
log_file = os.path.join(self.log_path.get(), log_name)
file_contents = self.output_textbox.get("1.0", ctk.END)
try:
with open(log_file, "w", encoding="utf-8") as f:
f.writelines(file_contents)
f.close()
except Exception as error:
message = "Unable to save device log"
self.status_text.configure(text=message, text_color="red")
self.log.error("Failed to save device log: %s", error)
else:
self.log_popup.destroy()
self.focus()
if platform.system() == "Darwin":
subprocess.call(("open", log_file))
elif platform.system() == "Windows":
os.startfile(log_file)
else:
subprocess.call(("xdg-open", log_file))
else:
if self.log_path.get() == "":
message = "You must specify a valid folder to save to"
else:
message = f"{self.log_path.get()} is not a valid directory"
self.status_text.configure(text=message, text_color="red")

def exception_handler(self, exc_type, exc_value, exc_traceback):
"""
Handler for uncaught exceptions
Expand Down
8 changes: 7 additions & 1 deletion ex_installer/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
read by the application build process to embed in the application details
"""

ex_installer_version = "0.0.17"
ex_installer_version = "0.0.18"

"""
Version history:
0.0.18 - Update EX-Turntable configuration options to suit changes in 0.7.0
- Dependabot update for cryptography to 42.0.4
- Add link to DCC-EX News articles about EX-Installer to the Info menu
- Ensure the config backup popup is always launched within the app window geometry
- Add a save log button to device monitor to save the device logs to a text file
- Fix bug where copying existing config files for EX-Turntable and EX-IOExpander causes an exception
0.0.17 - Move fonts to a separate common class
- Change default font to Arial for Windows/Mac and FreeSans for Linux
- Update various modules versions to resolve Dependapot identified vulnerabilities
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ attrs==23.1.0
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==2.1.1
cryptography==41.0.6
cryptography==42.0.4
CTkMessagebox==2.0
customtkinter==5.1.2
darkdetect==0.8.0
Expand Down

0 comments on commit 3a197c8

Please sign in to comment.