Skip to content

Commit

Permalink
Merge pull request #110 from DCC-EX:next-release
Browse files Browse the repository at this point in the history
Next-release
  • Loading branch information
peteGSX authored Sep 17, 2023
2 parents 752e379 + d79b96b commit 0102b91
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 11 deletions.
5 changes: 5 additions & 0 deletions build_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,11 @@ def get_site_packages_path():
f"{ctkmessagebox_dir}:CTkMessagebox/icons",
"--hidden-import=PIL._tkinter_finder"
]
# Append Linux specific parameters
if platform_name.startswith("Lin"):
param_list += [
"--additional-hooks-dir=."
]
param_list += [script_file]
print(param_list)

Expand Down
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.
29 changes: 29 additions & 0 deletions ex_installer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Initialisation module
© 2023, Peter Cole.
All rights reserved.
This file is part of EX-Installer.
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
"""

import sys
import os

# In Linux we also need to set up SSL certs properly
if getattr(sys, "frozen", None):
basedir = sys._MEIPASS
os.environ["SSL_CERT_FILE"] = os.path.join(basedir, "certifi", "cacert.pem")
27 changes: 23 additions & 4 deletions ex_installer/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""
Main application controller
© 2023, Peter Cole. All rights reserved.
© 2023, Peter Cole.
© 2023, Harald Barth.
All rights reserved.
This file is part of EX-Installer.
Expand Down Expand Up @@ -60,6 +62,8 @@ def main(debug):
# Start the app
_log.debug("EX-Installer launched")
app = EXInstaller()

# Do OS specific stuff for scaling and SSL
if sys.platform == "darwin":
pass # high DPI scaling works automatically it is said
elif sys.platform.startswith("win"):
Expand All @@ -68,8 +72,23 @@ def main(debug):
else:
import customtkinter
dpi = app.winfo_fpixels('1i')
customtkinter.set_widget_scaling(dpi/72)
customtkinter.set_window_scaling(dpi/72)
scaling = dpi/96 # 96 looks good
if scaling < 1:
scaling = 1 # we do not scale smaller because of dpi
winheight = app.winfo_screenheight()
winwidth = app.winfo_screenwidth()
# check if window would fit into onto screen
if winwidth > 200 and winwidth < scaling * 880:
scaling = winwidth / 880
if winheight > 150 and winheight < scaling * 660:
scaling = winheight / 660
# as scaling smaller does not work, just issue a warning and do not scale at all
if scaling < 1:
print("Warning: Not everything fits on screen")
# check if we need to bother to scale
if scaling > 1.1:
customtkinter.set_widget_scaling(scaling)
customtkinter.set_window_scaling(scaling)
# switch to first view _after_ the scaling because of a bug in Linux that would unhide all hidden buttons
app.switch_view("welcome")
app.mainloop()
Expand All @@ -89,6 +108,6 @@ def main(debug):
debug = True
else:
debug = False

# Start the app
main(debug)
8 changes: 8 additions & 0 deletions ex_installer/arduino_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,14 @@ def upgrade_platforms(self, file_path, queue):
acli = ThreadedArduinoCLI(file_path, params, queue)
acli.start()

def install_library(self, file_path, library, queue):
"""
Install the specified Arduino library
"""
params = ["lib", "install", library, "--format", "jsonmini"]
acli = ThreadedArduinoCLI(file_path, params, queue)
acli.start()

def list_boards(self, file_path, queue):
"""
Returns a list of attached boards
Expand Down
91 changes: 87 additions & 4 deletions ex_installer/ex_commandstation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ def __init__(self, parent, *args, **kwargs):
local_repo_dir = pd[self.product]["repo_name"].split("/")[1]
self.ex_commandstation_dir = fm.get_install_dir(local_repo_dir)

# Variables for version dependent options
self.trackmanager_available = False
self.current_override_available = False
self.disable_prog_available = False

# Set up title
self.set_title_logo(pd[self.product]["product_logo"])
self.set_title_text("Install EX-CommandStation")
Expand Down Expand Up @@ -126,19 +131,30 @@ def set_product_version(self, version, major=None, minor=None, patch=None):
(self.product_major_version == 4 and self.product_minor_version >= 2)
):
self.track_modes_switch.configure(state="normal")
self.trackmanager_available = True
else:
self.track_modes_switch.deselect() # make sure it's off
self.track_modes_switch.configure(state="disabled")
self.trackmanager_available = False
if (
self.product_major_version >= 5 or
(self.product_major_version >= 4 and self.product_minor_version >= 2 and self.product_patch_version >= 61)
):
self.override_current_limit.configure(state="normal")
self.current_override_available = True
else:
self.override_current_limit.deselect()
self.override_current_limit.configure(state="disabled")
self.current_override_available = False
if self.product_major_version >= 5:
self.disable_prog_switch.configure(state="normal")
self.disable_prog_available = True
else:
self.disable_prog_switch.configure(state="disabled")
self.disable_prog_available = False
self.set_track_modes()
self.current_override()
self.check_selected_device()

def setup_config_frame(self):
"""
Expand Down Expand Up @@ -169,6 +185,11 @@ def setup_config_frame(self):
current_tip = ("It is possible to define a custom current limit for the motor driver by enabling this option " +
"and specifying a new limit in mA. If this option is disabled, the version you have selected " +
"does not have this feature available.")
disable_eeprom_tip = ("On memory restricted devices such as Uno and Nano, disabling EEPROM support frees up " +
"valuable memory to enable using features such as limited EXRAIL scripts. This will " +
"be disabled for boards such as ESP32 and Nucleo that have no EEPROM on board.")
disable_prog_tip = ("On memory restricted devices such as Uno and Nano, disabling programming support frees " +
"up valuable memory to enable using features such as limited EXRAIL scripts.")

# Set up hardware instruction label
self.hardware_label = ctk.CTkLabel(self.config_frame, text=self.hardware_text,
Expand Down Expand Up @@ -213,7 +234,8 @@ def setup_config_frame(self):
"https://dcc-ex.com/reference/hardware/motor-boards.html")

# Set up display widgets
self.display_type_label = ctk.CTkLabel(self.options_frame, text="Select display type (if in use):",
self.display_frame = ctk.CTkFrame(self.options_frame, border_width=2)
self.display_type_label = ctk.CTkLabel(self.display_frame, text="Select display type (if in use):",
font=self.instruction_font)
self.display_enabled = ctk.StringVar(self, value="off")
self.display_type = ctk.StringVar(self)
Expand All @@ -222,7 +244,7 @@ def setup_config_frame(self):
command=self.set_display, font=self.instruction_font)
CreateToolTip(self.display_switch, display_tip,
"https://dcc-ex.com/reference/hardware/i2c-displays.html")
self.display_radio_frame = ctk.CTkFrame(self.options_frame, fg_color="#D9D9D9", border_width=0)
self.display_radio_frame = ctk.CTkFrame(self.display_frame, fg_color="#D9D9D9", border_width=0)
row = 0
for display in self.supported_displays.keys():
display_radio = ctk.CTkRadioButton(self.display_radio_frame, text=display, variable=self.display_type,
Expand All @@ -233,6 +255,12 @@ def setup_config_frame(self):
display_radio.select()
row += 1

# Layout display frame
self.display_frame.grid_columnconfigure(0, weight=1)
self.display_frame.grid_rowconfigure(1, weight=1)
self.display_type_label.grid(column=0, row=0, columnspan=2, sticky="w", padx=5, pady=(5, 1))
self.display_radio_frame.grid(column=0, row=1, sticky="w", padx=5, pady=(1, 5))

# Set up current limit widgets
self.override_current_limit = ctk.CTkSwitch(self.switch_frame, text="Override current limit",
onvalue="on", offvalue="off",
Expand Down Expand Up @@ -342,6 +370,29 @@ def setup_config_frame(self):
command=self.set_advanced_config, font=self.instruction_font)
CreateToolTip(self.advanced_config_switch, advanced_tip)

# Device hardware option widgets
self.hardware_options_frame = ctk.CTkFrame(self.options_frame, border_width=2)
self.device_options_label = ctk.CTkLabel(self.hardware_options_frame, text="Device hardware options:",
font=self.instruction_font)
self.disable_eeprom_switch = ctk.CTkSwitch(self.hardware_options_frame, text="Disable EEPROM support",
width=200, onvalue="on", offvalue="off", font=self.instruction_font)
CreateToolTip(self.disable_eeprom_switch, disable_eeprom_tip)
self.disable_prog_switch = ctk.CTkSwitch(self.hardware_options_frame, text="Disable programming support",
width=200, onvalue="on", offvalue="off", font=self.instruction_font)
CreateToolTip(self.disable_prog_switch, disable_prog_tip)
low_mem = ("Note that you have selected a device that has limited memory available. We recommend disabling " +
"EEPROM and programming support in order to support limited EXRAIL functionality.")
self.low_mem_label = ctk.CTkLabel(self.hardware_options_frame, text=low_mem, font=self.instruction_font,
text_color="orange", width=300, wraplength=295)

# Layout hardware options frame
self.hardware_options_frame.grid_columnconfigure(0, weight=1)
self.hardware_options_frame.grid_rowconfigure((1, 2), weight=1)
self.device_options_label.grid(column=0, row=0, sticky="w", **grid_options)
self.disable_eeprom_switch.grid(column=0, row=1, sticky="w", **grid_options)
self.disable_prog_switch.grid(column=0, row=2, sticky="w", **grid_options)
self.low_mem_label.grid(column=0, row=3, sticky="w", **grid_options)

# Layout wifi_frame
self.wifi_tab_frame.grid_columnconfigure((0, 1), weight=1)
self.wifi_tab_frame.grid_rowconfigure(0, weight=1)
Expand Down Expand Up @@ -379,10 +430,10 @@ def setup_config_frame(self):
self.options_frame.grid_rowconfigure((0, 1, 2, 3), weight=1)
self.motor_driver_label.grid(column=0, row=0, sticky="e", **grid_options)
self.motor_driver_combo.grid(column=1, row=0, sticky="w", **grid_options)
self.display_type_label.grid(column=0, row=1, columnspan=2, sticky="w", padx=5, pady=(5, 1))
self.display_radio_frame.grid(column=0, row=2, columnspan=2, sticky="w", padx=5, pady=(1, 5))
self.display_frame.grid(column=0, row=2, sticky="w", **grid_options)
self.current_limit_label.grid(column=0, row=3, sticky="e", **grid_options)
self.current_limit_entry.grid(column=1, row=3, sticky="w", **grid_options)
self.hardware_options_frame.grid(column=1, row=2, stick="w", **grid_options)

# Layout track_frame
self.track_tab_frame.grid_columnconfigure(0, weight=1)
Expand Down Expand Up @@ -417,6 +468,34 @@ def setup_config_frame(self):
self.hardware_label.grid(column=0, row=0, **grid_options)
self.config_tabview.grid(column=0, row=1, sticky="nsew", **grid_options)

def check_selected_device(self):
"""
Sets recommended device options based on selected device
Disables EEPROM option for boards without it also
"""
device = self.acli.selected_device
device_fqbn = self.acli.detected_devices[device]["matching_boards"][0]["fqbn"]
if device_fqbn.startswith("esp32") or device_fqbn.startswith("STMicroelectronics:stm32"):
self.disable_eeprom_switch.select()
self.disable_eeprom_switch.configure(state="disabled")
else:
self.disable_eeprom_switch.deselect()
self.disable_eeprom_switch.configure(state="normal")
if device_fqbn.startswith("arduino:avr:nano") or device_fqbn == "arduino:avr:uno":
self.track_modes_switch.deselect()
self.track_modes_switch.configure(state="disabled")
self.disable_eeprom_switch.select()
if self.disable_prog_available:
self.disable_prog_switch.select()
self.low_mem_label.grid()
else:
if self.trackmanager_available:
self.track_modes_switch.configure(state="normal")
self.disable_eeprom_switch.deselect()
self.disable_prog_switch.deselect()
self.low_mem_label.grid_remove()

def set_display(self):
"""
Sets display options on or off
Expand Down Expand Up @@ -693,6 +772,10 @@ def generate_config(self):
param_errors.append("Current limit must be a number in mA")
else:
config_list.append(f"#define MAX_CURRENT {self.current_limit.get()}\n")
if self.disable_eeprom_switch.get() == "on":
config_list.append("#define DISABLE_EEPROM\n")
if self.disable_prog_switch.get() == "on":
config_list.append("#define DISABLE_PROG\n")
if len(param_errors) > 0:
self.log.error("Missing parameters: %s", param_errors)
return (False, param_errors)
Expand Down
22 changes: 22 additions & 0 deletions ex_installer/ex_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ def __init__(self, *args, **kwargs):
self.info_menu.add_checkbutton(label="Enable debug logging", command=self.toggle_debug,
variable=self.enable_debug, onvalue="on", offvalue="off")
self.menubar.add_cascade(label="Info", menu=self.info_menu)
# Create Tools menu and options
self.tools_menu = Menu(self.menubar, tearoff=0)
# self.tools_menu.add_command(label="WiFi Flasher", command=lambda parent=self: WiFiFlasher(parent))
self.menubar.add_cascade(label="Tools", menu=self.tools_menu)
# Submenu for screen scaling
self.scaling_option = ctk.IntVar(self, value=100)
self.scaling_menu = Menu(self.menubar, tearoff=0)
self.scaling_menu.add_radiobutton(label="90%", var=self.scaling_option, value=90, command=self.set_scaling)
self.scaling_menu.add_radiobutton(label="100%", var=self.scaling_option, value=100, command=self.set_scaling)
self.scaling_menu.add_radiobutton(label="110%", var=self.scaling_option, value=110, command=self.set_scaling)
self.scaling_menu.add_radiobutton(label="125%", var=self.scaling_option, value=125, command=self.set_scaling)
self.scaling_menu.add_radiobutton(label="150%", var=self.scaling_option, value=150, command=self.set_scaling)
self.scaling_menu.add_radiobutton(label="200%", var=self.scaling_option, value=200, command=self.set_scaling)
self.tools_menu.add_cascade(label="Scaling", menu=self.scaling_menu)
self.configure(menu=self.menubar)

def exception_handler(self, exc_type, exc_value, exc_traceback):
Expand Down Expand Up @@ -265,3 +279,11 @@ def toggle_debug(self):
self.log.parent.setLevel(logging.DEBUG)
else:
self.log.parent.setLevel(logging.WARNING)

def set_scaling(self):
"""
Function to set the screen scaling value
"""
scale = self.scaling_option.get() / 100
ctk.set_widget_scaling(scale)
ctk.set_window_scaling(scale)
29 changes: 29 additions & 0 deletions ex_installer/manage_arduino_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# Import local modules
from .common_widgets import WindowLayout, CreateToolTip
from . import images
from .product_details import product_details as pd


class ManageArduinoCLI(WindowLayout):
Expand Down Expand Up @@ -75,6 +76,9 @@ def __init__(self, parent, *args, **kwargs):
"Arduino AVR": "arduino:avr"
}

# Set up list for library installs
self.library_list = []

# Set title and logo
self.set_title_logo(images.EX_INSTALLER_LOGO)
self.set_title_text("Manage the Arduino CLI")
Expand Down Expand Up @@ -147,6 +151,7 @@ def __init__(self, parent, *args, **kwargs):

def set_state(self):
self.next_back.hide_log_button()
self.get_library_list()
if self.acli.is_installed(self.acli.cli_file_path()):
self.cli_state_label.configure(text=self.installed_text,
text_color="#00353D",
Expand All @@ -165,6 +170,16 @@ def set_state(self):
command=lambda event="install_cli": self.manage_cli(event))
self.next_back.disable_next()

def get_library_list(self):
"""
Get list of library dependencies from product details
"""
self.library_list = []
for product in pd:
if "arduino_libraries" in pd[product]:
for library in pd[product]["arduino_libraries"]:
self.library_list.append(library)

def update_package_list(self, switch):
"""
Maintain the list of packages to install/refresh when switches updated
Expand Down Expand Up @@ -234,6 +249,7 @@ def manage_cli(self, event):
elif event == "refresh_cli" or self.process_phase == "extract_cli":
if event == "refresh_cli":
self.disable_input_states(self)
self.get_library_list()
if self.process_status == "success" or event == "refresh_cli":
self.process_start("config_cli", "Configuring the Arduino CLI", "Manage_CLI")
for widget in self.extra_platforms_frame.winfo_children():
Expand All @@ -260,6 +276,19 @@ def manage_cli(self, event):
self.process_start("install_packages", f"Installing package {package}", "Manage_CLI")
self.acli.install_package(self.acli.cli_file_path(), self.package_dict[package], self.queue)
del self.package_dict[package]
else:
self.process_stop()
self.manage_cli("install_libraries")
elif self.process_status == "error":
self.process_error(self.process_topic)
self.restore_input_states()
elif event == "install_libraries" or self.process_phase == "install_libraries":
if self.process_status == "success" or event == "install_libraries":
if len(self.library_list) > 0:
library = self.library_list[0]
del self.library_list[0]
self.process_start("install_libraries", "Install Arduino library " + library, "Manage_CLI")
self.acli.install_library(self.acli.cli_file_path(), library, self.queue)
else:
self.process_start("upgrade_platforms", "Upgrading Arduino platforms", "Manage_CLI")
self.acli.upgrade_platforms(self.acli.cli_file_path(), self.queue)
Expand Down
Loading

0 comments on commit 0102b91

Please sign in to comment.