From 04b8bb18747c22134db055cc6046fff15f35066c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Wed, 11 Feb 2026 14:56:09 +0100 Subject: [PATCH 1/4] Update bootstrap_linux.bash to work with Debian 13 - Trixie * apt-get and apt-cache commands are replaced with apt * A virtual python environment is set up defaulting to ~/.pels and is by default activated by .bashrc * The 'install' clause will always run "sudo apt update" * openssh-server is assumed to be installed by default and activated by the user * fixme added to the clauses beyond 'all' to reevaluate later --- bootstrap/bootstrap_linux.bash | 103 +++++++++++++++++---------------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/bootstrap/bootstrap_linux.bash b/bootstrap/bootstrap_linux.bash index ce2ced3a..10bf85e3 100755 --- a/bootstrap/bootstrap_linux.bash +++ b/bootstrap/bootstrap_linux.bash @@ -6,18 +6,23 @@ # EDIT POINT START: Edit here to change what the script does # ############################################################## +# Install virtual python environment in ~/$PELS_ENV +PELS_ENV=.pels + # apt install packages line 1, general packages # -apt1="openssh-server emacs graphviz screen i2c-tools vim-nox" +apt1="emacs-nox graphviz screen i2c-tools vim-nox" # apt install packages line 2, python extensions # -# NOTE: This line used to contain colorama, but it is a dependency of -# pip, so it will be installed anyway -apt2="python3-pip python3-numpy black" +# NOTE: This line used to contain colorama, but it was a dependency of +# pip, so it will be installed anyway. Only used on Windows systems? +apt2="python3-pip python3-venv python3-numpy black" # apt install packages that has possibly changed name, written in list form and installed one at at time -declare -a apt3=("libpython3-dev" "python3-dev") +# https://pypi.org/project/mysqlclient/ +declare -a apt3=("libpython3-dev" "python3-dev default-libmysqlclient-dev build-essential pkg-config") +# for rhel, you need python3-devel mysql-devel pkgconfig instead to be able to pip mysqlclient # packages to be installed by pip pip3packages="minimalmodbus pyusb python-usbtmc pyserial pyyaml mysqlclient" @@ -37,16 +42,13 @@ if [ -d $machine_dir ]; then cd $machine_dir fi pistatus.py +if [ -f ~/'$PELS_ENV'/bin/activate ]; then + source ~/'$PELS_ENV'/bin/activate +fi ' # These lines will be added to ~/.bash_aliases bash_aliases=" -alias sagi=\"sudo apt-get install\" -alias sagr=\"sudo apt-get remove\" -alias acs=\"apt-cache search\" -alias sagu=\"sudo apt-get update\" -alias sagup=\"sudo apt-get upgrade\" -alias sagdu=\"sudo apt-get dist-upgrade\" alias ll=\"ls -lh\" alias df=\"df -h\" alias emacs-nolint=\"emacs -q --load ~/PyExpLabSys/bootstrap/.emacs-simple\" @@ -87,6 +89,7 @@ abelec Setup device to use daq-modules from AB Electronics (NOT part of all dash Install packages for dash qt Setup GUI environment: Qt and Qwt (for plots) " + ################## # EDIT POINT END # ################## @@ -200,69 +203,66 @@ fi # Install packages if [ $1 == "install" ] || [ $1 == "all" ];then + # Verified for Debian 13 - Trixie echo echobold "===> INSTALLING PACKAGES" echoblue "---> Updating package archive information" - # Update, but only if it has not just been done (within the last - # 10 hours), since it actually takes a while on a RPi. - # - # NOTE. The method is based on checking the last modification of - # the apt cache file, which may not the perfect method, we will - # test it and see - last_update=`stat /var/cache/apt/pkgcache.bin --format="%Y"` - now=`date +%s` - since_last_update=$((now-last_update)) - if [ $since_last_update -gt 36000 ];then - sudo apt-get update - else - echoblue "Skipping, since it was done" $(($since_last_update/3600)) "hours ago" - fi + # Update - it does not take that long on newer hardware and the old + # method seemingly did not work when run on a fresh minimal installation. + sudo apt update - aptgetprefix='sudo apt-get -y' + aptprefix='sudo apt -y' echoblue "---> Upgrade all existing packages" - $aptgetprefix dist-upgrade + $aptprefix full-upgrade echoblue "---> Installing packages" echoblue "----> Install: $apt1" - $aptgetprefix install $apt1 + $aptprefix install $apt1 echoblue "----> Install: $apt2" - $aptgetprefix install $apt2 + $aptprefix install $apt2 # Install package individually, that may have changed name for package in "${apt3[@]}";do echoblue "----> Attempting to install \"$package\" as an individual package" - $aptgetprefix install $package + $aptprefix install $package done echoblue "---> Remove un-needed packages, if any" - $aptgetprefix autoremove + $aptprefix autoremove echoblue "---> Clear apt cache" - $aptgetprefix clean + sudo apt -y clean - echoblue "---> Enable ssh by creating /boot/ssh" - sudo touch /boot/ssh echogood "+++++> DONE" fi # Install extra packages with pip if [ $1 == "pip" ] || [ $1 == "all" ];then + # Verified for Debian 13 - Trixie echo - # Test if pip3 is there - PIPEXECUTABLE=`which pip3` - if [ $? -eq 0 ];then - echobold "===> INSTALLING EXTRA PYTHON PACKAGES WITH PIP3" - echoblue "---> Installing: $pip3packages" - sudo $PIPEXECUTABLE install -U $pip3packages - # Individual packages - for package in "${pip3problempackages[@]}";do - echoblue "---> Installing \"$package\" as an invididual package" - sudo $PIPEXECUTABLE install -U $package - done - echogood "+++++> DONE" + # Create virtual Python environment if not existing + if [ ! -d ~/$PELS_ENV ];then + echoblue "Creating virtual environment for PyExpLabSys as $PELS_ENV" + python3 -m venv ~/$PELS_ENV + fi + # Activate virtual environment + if [ -f ~/$PELS_ENV/bin/activate ];then + source ~/$PELS_ENV/bin/activate + + # Install packages + echobold "===> INSTALLING EXTRA PYTHON PACKAGES WITH PIP3" + echoblue "---> Installing: $pip3packages" + python -m pip install -U $pip3packages + # Individual packages + for package in "${pip3problempackages[@]}";do + echoblue "---> Installing \"$package\" as an invididual package" + python -m pip install -U $package + done + echogood "+++++> DONE" else - echobad "pip3 not installed, run install step and then re-try pip step" + echobad "~/$PELS_ENV already exists, but is not a virtual environment for Python" + echobad "Skipping pip installs" fi fi @@ -309,7 +309,7 @@ $cronline" fi -# Setup autostart cronjob +# Setup settings if [ $1 == "settings" ] || [ $1 == "all" ];then echobold "===> LINKING PYEXPLABSYS SETTINGS FILE IN PLACE" if [ -f ~/.config/PyExpLabSys/user_settings.yaml ];then @@ -349,6 +349,7 @@ fi # Install extra packages needed for writing docs if [ $1 == "docs" ];then + # FIXME: Should be updated/removed or is working? # TODO add sphinx and extra package needed to dependency graph echobold "===> INSTALLING EXTRA PACKAGES FOR WRITING DOCS" echo @@ -370,6 +371,7 @@ if [ $1 == "docs" ];then fi if [ $1 == "wiringpi" ];then + # FIXME: Should be updated/removed or is working? echobold "===> INSTALLING WIRINGPI" echoblue "---> Installing wiringpi with apt-get" sudo apt-get install wiringpi @@ -383,9 +385,10 @@ if [ $1 == "wiringpi" ];then fi if [ $1 == "abelec" ];then + # FIXME: Should be updated/removed or is working? # TODO: Improve script to allow multiple executions echobold "===> INSTALLING EXTRA PACKAGES FOR AB ELECTRONICS" - sudo apt-get install i2c-tools python-smbus + sudo apt install i2c-tools python-smbus echo sudo touch /etc/modprobe.d/raspi-blacklist.conf # Make sure file is there before removing @@ -439,6 +442,7 @@ fi if [ $1 == "dash" ];then + # FIXME: Should be updated/removed or is working? # TODO: Improve script to allow multiple executions echobold "===> INSTALLING EXTRA PACKAGES FOR DASH" pip3 install dash==0.28.5 # The core dash backend @@ -450,6 +454,7 @@ fi # GUI section (Qt and Qwt) if [ $1 == "qt" ];then + # FIXME: Should be updated/removed or is working? echobold "===> INSTALLING EXTRA PACKAGES FOR GUIS" echo From b110124e8294669d15bcf5e82b4fdc71765e36f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Thu, 12 Feb 2026 17:49:30 +0100 Subject: [PATCH 2/4] Python 3-only changes to sockets --- PyExpLabSys/common/sockets.py | 47 +++++++++++++---------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/PyExpLabSys/common/sockets.py b/PyExpLabSys/common/sockets.py index ce4a89ac..01f2b5ad 100644 --- a/PyExpLabSys/common/sockets.py +++ b/PyExpLabSys/common/sockets.py @@ -57,11 +57,9 @@ # Queue was renamed to queue in Python 3 import queue as Queue import logging -import six from .utilities import call_spec_string from .system_status import SystemStatus from ..settings import Settings -from .supported_versions import python2_and_3 # Instantiate module logger LOGGER = logging.getLogger(__name__) @@ -71,9 +69,6 @@ # Instantiate a global system status object SYSTEM_STATUS = SystemStatus() -# Indicate Python 2/3 -python2_and_3(__file__) - # Instantiate settings object SETTINGS = Settings() LOGGER.debug("Settings loaded with the following values: %s", SETTINGS.settings) @@ -200,9 +195,9 @@ def _single_value(self, command): elif command == 'json' and name in DATA[self.port]['data']: if self._old_data(name): - out = six.text_type(json.dumps(OLD_DATA)) + out = json.dumps(OLD_DATA) else: - out = six.text_type(json.dumps(DATA[self.port]['data'][name])) + out = json.dumps(DATA[self.port]['data'][name]) # The command is unknown else: out = UNKNOWN_COMMAND @@ -239,7 +234,7 @@ def _all_values(self, command): else: data = DATA[self.port]['data'][codename] points.append(data) - out = six.text_type(json.dumps(points)) + out = json.dumps(points) # Return a raw string with all measurements in codenames order including names elif command == 'raw_wn': strings = [] @@ -258,25 +253,23 @@ def _all_values(self, command): for codename in DATA[self.port]['codenames']: if self._old_data(codename): datacopy[codename] = OLD_DATA - out = six.text_type(json.dumps(datacopy)) + out = json.dumps(datacopy) # Return all codesnames in a raw string elif command == 'codenames_raw': out = ','.join(DATA[self.port]['codenames']) # Return a list with all codenames encoded as a json string elif command == 'codenames_json': - out = six.text_type(json.dumps(DATA[self.port]['codenames'])) + out = json.dumps(DATA[self.port]['codenames']) # Return the socket server name elif command == 'name': out = DATA[self.port]['name'] # Return status of system and all socket servers elif command == 'status': - out = six.text_type( - json.dumps( - { - 'system_status': SYSTEM_STATUS.complete_status(), - 'socket_server_status': socket_server_status(), - } - ) + out = json.dumps( + { + 'system_status': SYSTEM_STATUS.complete_status(), + 'socket_server_status': socket_server_status(), + } ) # The command is not known else: @@ -686,13 +679,11 @@ def handle(self): commands = ['json_wn#', 'raw_wn#', 'name', 'status', 'commands'] return_value = '{}#{}'.format(PUSH_RET, json.dumps(commands)) elif request == 'status': - return_value = six.text_type( - json.dumps( - { - 'system_status': SYSTEM_STATUS.complete_status(), - 'socket_server_status': socket_server_status(), - } - ) + return_value = json.dumps( + { + 'system_status': SYSTEM_STATUS.complete_status(), + 'socket_server_status': socket_server_status(), + } ) elif request.count('#') != 1: return_value = '{}#{}'.format(PUSH_ERROR, UNKNOWN_COMMAND) @@ -939,7 +930,7 @@ def _format_return_raw_dict(argument): # Check all values in list are of same type types = [type(element) for element in value] element_type = types[0] - element_type_name = six.text_type(element_type.__name__) + element_type_name = element_type.__name__ if types != len(types) * [element_type]: message = ( 'With return format raw, value in list must have same type' @@ -953,13 +944,9 @@ def _format_return_raw_dict(argument): value_string = '' else: # Single element conversion - element_type_name = six.text_type(type(value).__name__) + element_type_name = type(value).__name__ value_string = '{}'.format(str(value)) - # We always call it str - if sys.version_info[0] == 2 and element_type_name == 'unicode': - element_type_name = 'str' - # Check that the element type makes sense for raw conversion if element_type_name not in ['int', 'float', 'bool', 'str']: message = ( From f14f50f60dadcda66f099ef4888420bc38449f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Thu, 12 Feb 2026 17:51:29 +0100 Subject: [PATCH 3/4] os.chdir is no longer part of pathlib, but the effect is already handled by .bashrc --- bin/autostart.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/autostart.py b/bin/autostart.py index 4db49244..0c554e21 100755 --- a/bin/autostart.py +++ b/bin/autostart.py @@ -73,9 +73,6 @@ def main(): args = parse_args() config_file_path = find_config(args) - # Change current working directory to config dir - pathlib.os.chdir(config_file_path.parent) - # Parse XML config file etree_object = XML.parse(config_file_path) xml = etree_object.getroot() From 5c9dc90a01180ea9e6efecc4750070ea957ddec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20Ejler=20S=C3=B8rensen?= Date: Thu, 12 Feb 2026 17:53:33 +0100 Subject: [PATCH 4/4] Bootstrap_linux: install numpy via pip instead of as a system package --- bootstrap/bootstrap_linux.bash | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/bootstrap_linux.bash b/bootstrap/bootstrap_linux.bash index 10bf85e3..aa9e1e83 100755 --- a/bootstrap/bootstrap_linux.bash +++ b/bootstrap/bootstrap_linux.bash @@ -17,7 +17,7 @@ apt1="emacs-nox graphviz screen i2c-tools vim-nox" # # NOTE: This line used to contain colorama, but it was a dependency of # pip, so it will be installed anyway. Only used on Windows systems? -apt2="python3-pip python3-venv python3-numpy black" +apt2="python3-pip python3-venv black" # apt install packages that has possibly changed name, written in list form and installed one at at time # https://pypi.org/project/mysqlclient/ @@ -25,7 +25,7 @@ declare -a apt3=("libpython3-dev" "python3-dev default-libmysqlclient-dev build- # for rhel, you need python3-devel mysql-devel pkgconfig instead to be able to pip mysqlclient # packages to be installed by pip -pip3packages="minimalmodbus pyusb python-usbtmc pyserial pyyaml mysqlclient" +pip3packages="minimalmodbus pyusb python-usbtmc pyserial pyyaml mysqlclient numpy" # Put packages into this array, whose installation sometimes fail declare -a pip3problempackages=("pylint")