diff --git a/adafruit-pitft-old.py b/adafruit-pitft-old.py new file mode 100644 index 0000000..d857c84 --- /dev/null +++ b/adafruit-pitft-old.py @@ -0,0 +1,1007 @@ +""" +Adafruit PiTFT Installer Script +(C) Adafruit Industries, Creative Commons 3.0 - Attribution Share Alike + +Written in Python by Melissa LeBlanc-Williams for Adafruit Industries +""" + +import time +import os +try: + import click +except ImportError: + raise RuntimeError("The library 'Click' was not found. To install, try typing: pip3 install Click") +try: + from adafruit_shell import Shell +except ImportError: + raise RuntimeError("The library 'adafruit_shell' was not found. To install, try typing: pip3 install adafruit-python-shell") + +shell = Shell() +shell.group = 'PITFT' + +__version__ = "3.9.0" + +""" +This is the main configuration. Displays should be placed in the order +they are to appear in the menu. + +Config Parameters: +REQUIRED fields: +type: Unique identifier for the display type. +menulabel: The label to display in the menu. +product: The product name for the display. +kernel_upgrade: Whether the kernel needs to be upgraded for this display. +overlay: The overlay string to apply for the display. +width: Width of the display in pixels. +height: Height of the display in pixels. + +OPTIONAL fields: +touchscreen: Dictionary containing touchscreen calibration data. + identifier: Name of the touchscreen calibration. + product: Product name for the touchscreen. + transforms: Calibration transforms for different rotations. + overlay_params: Overlay parameters for different rotations. +overlay_drm_option: Optional parameter to add for DRM support. +force_x11: Whether to force X11 for the display. +calibrations: Calibration data for the touchscreen. +overlay_src: Source path for the overlay file (if applicable). +overlay_dest: Destination path for the compiled overlay file (if applicable). +mipi_data (Optional): Dictionary containing MIPI display data. + command_bin: Name of the command binary for MIPI. + gpio: GPIO settings for the display. + viewport: Viewport settings for different rotations. +use_kms: Whether to use KMS for the display. +""" +config = [ + { + "type": "28r", + "menulabel": "PiTFT 2.4\", 2.8\" or 3.2\" resistive (240x320)", + "product": "2.8\" resistive, PID 1601", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", + "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", + "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", + "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", + }, + "overlay_params": { + "0": None, + "90": "touch-swapxy,touch-invx", + "180": "touch-invx,touch-invy", + "270": "touch-swapxy,touch-invy", + }, + }, + "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + "width": 320, + "height": 240, + }, + { + "type": "22", + "menulabel": "PiTFT 2.2\" no touch", + "product": "2.2\" no touch", + "kernel_upgrade": False, + "overlay": "dtoverlay=pitft22,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "width": 320, + "height": 240, + }, + { + "type": "28c", + "menulabel": "PiTFT 2.8\" capacitive touch", + "product": "2.8\" capacitive, PID 1983", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "FocalTech Touchscreen Calibration", + "product": "EP0110M09", + "transforms": { + "0": "-1 0 1 0 -1 1 0 0 1", + "90": "0 1 0 -1 0 1 0 0 1", + "180": "1 0 0 0 1 0 0 0 1", + "270": "0 -1 1 1 0 0 0 0 1", + }, + "overlay_params": { + "0": "touch-invx,touch-invy", + "90": "touch-swapxy,touch-invy", + "180": None, + "270": "touch-swapxy,touch-invx", + }, + }, + "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": "320 65536 0 -65536 0 15728640 65536", + "width": 320, + "height": 240, + }, + { + "type": "35r", + "menulabel": "PiTFT 3.5\" resistive touch", + "product": "3.5\" Resistive", + "kernel_upgrade": False, + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", + "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", + "180": "1.102807 0.000030 -0.066352 0.001374 1.085417 -0.027208 0 0 1", + "270": "0.003893 -1.087542 1.025913 1.084281 0.008762 -0.060700 0 0 1", + }, + "overlay_params": { + "0": None, + "90": "touch-swapxy,touch-invx", + "180": "touch-invx,touch-invy", + "270": "touch-swapxy,touch-invy", + }, + }, + "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "5724 -6 -1330074 26 8427 -1034528 65536", + "90": "5 8425 -978304 -5747 61 22119468 65536", + "180": "-5682 -1 22069150 13 -8452 32437698 65536", + "270": "3 -8466 32440206 5703 -1 -1308696 65536", + }, + "width": 480, + "height": 320, + "x11_scale": 1.5, + }, + { + "type": "st7789_240x240", + "menulabel": "PiTFT Mini 1.3\" or 1.54\" display", + "product": "1.54\" or 1.3\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/minipitft13-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-minipitft13.dtbo", + "overlay": "dtoverlay=drm-minipitft13,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=240,height=240", + "90": "width=240,height=240,x-offset=80", + "180": "width=240,height=240,y-offset=80", + "270": "width=240,height=240", + }, + }, + "width": 240, + "height": 240, + "fbcp_rotations": { + "0": "0", + "90": "1", + "180": "2", + "270": "3", + }, + }, + { + "type": "st7789_240x320", + "menulabel": "ST7789V 2.0\" no touch", + "product": "2.0\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/st7789v_240x320-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-st7789v_240x320.dtbo", + "overlay": "dtoverlay=drm-st7789v_240x320,rotate={pitftrot},fps=30", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=320,height=240", + "90": "width=240,height=320", + "180": "width=320,height=240", + "270": "width=240,height=320", + }, + }, + "width": 320, + "height": 240, + }, + { + "type": "st7789_240x135", + "menulabel": "MiniPiTFT 1.14\" display", + "product": "1.14\" no touch", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/minipitft114-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-minipitft114.dtbo", + "overlay": "dtoverlay=drm-minipitft114,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=22", + "viewport": { + "0": "width=136,height=240,x-offset=52,y-offset=40", + "90": "width=240,height=136,y-offset=52,x-offset=40", + "180": "width=136,height=240,x-offset=52,y-offset=40", + "270": "width=240,height=136,y-offset=52,x-offset=40", + }, + }, + "rotations": { + "0": None, + "90": "90", + "180": None, + "270": "270", + }, + "width": 240, + "height": 135, + "fbcp_rotations": { + "0": "3", + "90": "2", + "180": "1", + "270": "0", + }, + }, + { + "type": "st7789v_bonnet_240x240", + "menulabel": "BrainCraft HAT or 1.3\" TFT Bonnet + Joystick", + "product": "1.3\" Joystick", + "kernel_upgrade": True, + "kernel_module": "fb_st7789v", + "overlay_src": "overlays/tftbonnet13-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/drm-tftbonnet13.dtbo", + "overlay": "dtoverlay=drm-tftbonnet13,rotate={pitftrot},fps=60", + "use_kms": True, + "mipi_data": { + "command_bin": "adafruit_st7789_drm", + "gpio": "dc-gpio=25,backlight-gpio=26", + "viewport": { + "0": "width=240,height=240", + "90": "width=240,height=240,x-offset=80", + "180": "width=240,height=240,y-offset=80", + "270": "width=240,height=240", + }, + }, + "width": 240, + "height": 240, + "fbcp_rotations": { + "0": "0", + "90": "1", + "180": "2", + "270": "3", + }, + }, + { + "type": "24v2", + "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", + "product": "2.4\" V2 resistive", + "kernel_upgrade": False, + "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", + "touchscreen": { + "identifier": "STMPE Touchscreen Calibration", + "product": "stmpe", + "transforms": { + "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", + "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", + "180": "-1.115235 -0.010589 1.057967 -0.005964 -1.107968 1.025780 0 0 1", + "270": "-0.033192 1.126869 -0.014114 -1.115846 0.006580 1.050030 0 0 1", + }, + }, + "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", + "overlay_drm_option": "drm", + "force_x11": True, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + "width": 320, + "height": 240, + }, +] + +# default rotations +fbcp_rotations = { + "0": "1", + "90": "0", + "180": "3", + "270": "2", +} + +# default mipi data +mipi_data = { + "speed": 40000000, + "spi": "spi0-0", +} + +PITFT_ROTATIONS = ("90", "180", "270", "0") +UPDATE_DB = False +SYSTEMD = None +REMOVE_KERNEL_PINNING = False +pitft_config = None +pitftrot = None +auto_reboot = None +wayland = False +is_bullseye = False + +def warn_exit(message): + shell.warn(message) + shell.exit(1) + +def uninstall_cb(ctx, param, value): + if not value or ctx.resilient_parsing: + return + uninstall() + +def print_version(ctx, param, value): + if not value or ctx.resilient_parsing: + return + print("Adafruit PiTFT Helper v{}".format(__version__)) + shell.exit(1) + +def progress(ellipsis_count): + for i in range(ellipsis_count): + print("...", end='') + time.sleep(1) + print("") + +def sysupdate(): + global UPDATE_DB + if not UPDATE_DB: + print("Updating apt indexes...", end='') + progress(3) + if not shell.run_command('sudo apt update', suppress_message=True): + warn_exit("Apt failed to update indexes! Try running 'sudo apt update' manually.") + if not shell.run_command('sudo apt-get update', suppress_message=True): + warn_exit("Apt failed to update indexes! Try running 'sudo apt-get update' manually.") + print("Reading package lists...") + progress(3) + UPDATE_DB = True + return True + +############################ Sub-Scripts ############################ + +def is_wayland(): + username = os.environ["SUDO_USER"] + output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() + return len(output) > 0 + +def softwareinstall(): + print("Installing Pre-requisite Software...This may take a few minutes!") + if not shell.run_command("apt-get install -y libts0", suppress_message=True): + if not shell.run_command("apt-get install -y tslib"): + if not shell.run_command("apt-get install -y libts-dev"): + warn_exit("Apt failed to install TSLIB!") + if not shell.run_command("apt-get install -y bc fbi git python3-dev python3-pip python3-smbus python3-spidev evtest libts-bin device-tree-compiler libraspberrypi-dev build-essential python3-evdev"): + warn_exit("Apt failed to install software!") + return True + +def uninstall_bootconfigtxt(): + """Remove any old flexfb/fbtft stuff""" + if shell.pattern_search(f"{boot_dir}/config.txt", "adafruit-pitft-helper"): + print(f"Already have an adafruit-pitft-helper section in {boot_dir}/config.txt.") + print("Removing old section...") + shell.run_command(f"cp {boot_dir}/config.txt {boot_dir}/configtxt.bak") + shell.pattern_replace(f"{boot_dir}/config.txt", '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) + return True + +def uninstall_etc_modules(): + """Remove any old flexfb/fbtft stuff""" + shell.run_command('rm -f /etc/modprobe.d/fbtft.conf') + shell.pattern_replace("/etc/modules", 'spi-bcm2835') + shell.pattern_replace("/etc/modules", 'flexfb') + shell.pattern_replace("/etc/modules", 'fbtft_device') + return True + +def use_mipi_driver(config = None): + """Check if MIPI Overlay is Present""" + if not config: + config = pitft_config + if not "mipi_data" in config: + return False + if not shell.exists(f"{boot_dir}/overlays/mipi-dbi-spi.dtbo"): + return False + return True + +def is_kernel_upgrade_required(config = None): + """Check if kernel upgrade is required""" + if not config: + config = pitft_config + if not config['kernel_upgrade']: + return False + # Only upgrade Kernel if MIPI Driver is not an option + if use_mipi_driver(config): + return False + + return True + +def install_drivers(): + """Compile display driver and overlay if required""" + if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: + print("Compiling Device Tree Overlay") + destination = pitft_config['overlay_dest'].format(boot_dir=boot_dir) + shell.run_command("dtc --warning no-unit_address_vs_reg -I dts -O dtb -o {dest} {src}".format(dest=destination, src=pitft_config['overlay_src'])) + + if use_mipi_driver(): + mipi_data.update(pitft_config['mipi_data']) + if not compile_display_fw(): + shell.bail("Unable to compile MIPI firmware") + + if is_kernel_upgrade_required(): + print("############# UPGRADING KERNEL ###############") + print("Updating packages...") + if not shell.run_command("sudo apt-get update", suppress_message=True): + warn_exit("Apt failed to update itself!") + print("Upgrading packages...") + if not shell.run_command("sudo apt-get -y upgrade"): + warn_exit("Apt failed to install software!") + print("Installing Kernel Headers. This may take a few minutes...") + if not shell.run_command("apt-get install -y raspberrypi-kernel-headers", suppress_message=True): + warn_exit("Apt failed to install software!") + # If the kernel was upgraded, a build folder should exist once it has been loaded + module = pitft_config['kernel_module'] + if not shell.isdir(f"/lib/modules/{shell.release()}/build"): + warn_exit(f"Kernel headers build folder for {shell.release()}, not found. Please reboot now and re-run script!") + print("Compiling and installing display driver...") + shell.pushd("st7789_module") + if not shell.run_command("make"): + warn_exit("Apt failed to compile ST7789V drivers!") + shell.run_command(f"mv /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.BACK.xz") + shell.run_command(f"xz -v2 {module}.ko") + shell.run_command(f"mv {module}.ko.xz /lib/modules/{shell.release()}/kernel/drivers/staging/fbtft/{module}.ko.xz") + shell.popd() + return True + +def update_configtxt(rotation_override=None, tinydrm_install=False): + """update /boot/firmware/config.txt (or equivalent folder) with appropriate values""" + uninstall_bootconfigtxt() + uninstall_etc_modules() + overlay_key = "overlay" + overlay = pitft_config[overlay_key] + if "{pitftrot}" in overlay: + rotation = str(rotation_override) if rotation_override is not None else pitftrot + overlay = overlay.format(pitftrot=rotation) + if use_mipi_driver(): + # Mipi Driver does not work if hdmi_force_hotplug=1 is present + shell.pattern_replace(f"{boot_dir}/config.txt", "hdmi_force_hotplug=1", "hdmi_force_hotplug=0") + # Use the MIPI Overlay instead + overlay = f"dtoverlay=mipi-dbi-spi,{mipi_data['spi']},speed=40000000" + overlay += f"\ndtparam=compatible={mipi_data['command_bin']}\\0panel-mipi-dbi-spi" + viewport = "" + if mipi_data['viewport'][pitftrot] is not None: + viewport = mipi_data['viewport'][pitftrot] + overlay += f"\ndtparam={viewport}" + if "gpio" in mipi_data: + overlay += f"\ndtparam={mipi_data['gpio']}" + + if tinydrm_install and "overlay_drm_option" in pitft_config: + overlay += "," + pitft_config["overlay_drm_option"] + if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead + if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: + overlay += "," + pitft_config["overlay_params"][pitftrot] + shell.write_text_file(f"{boot_dir}/config.txt", """ +# --- added by adafruit-pitft-helper {date} --- +[all] +hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! +dtparam=spi=on +dtparam=i2c1=on +dtparam=i2c_arm=on +{overlay} +# --- end adafruit-pitft-helper {date} --- +""".format(date=shell.date(), overlay=overlay)) + return True + +def update_udev(): + shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ +SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +""", append=False) + return True + +def compile_display_fw(): + command_src = "mipi/panel.txt" + + # We could just copy the file to panel.txt, edit that, then remove it after + shell.copy(f"mipi/{mipi_data['command_bin']}.txt", command_src) + + # Make sure each of the lines with "# rotation" starts with exactly 1 # and no more + shell.pattern_replace(command_src, "^#*(.*?# rotation.*?$)", "#\\1") + # Uncomment the one for the rotation we are going for + shell.pattern_replace(command_src, "^#(.*?# rotation " + pitftrot + ".*?$)", "\\1") + # Download the mipi-dbi-cmd script if it doesn't exist + if not shell.exists("mipi-dbi-cmd"): + shell.run_command("wget https://raw.githubusercontent.com/notro/panel-mipi-dbi/main/mipi-dbi-cmd") + shell.run_command("chmod +x mipi-dbi-cmd") + # Run the mipi-dbi-script and output directly to the /lib/firmware folder + shell.run_command(f"./mipi-dbi-cmd /lib/firmware/{mipi_data['command_bin']}.bin mipi/panel.txt") + shell.remove(command_src) + return True + +def update_pointercal(): + if "calibrations" in pitft_config: + if isinstance(pitft_config["calibrations"], dict): + shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) + else: + shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) + return True + +def install_console(): + print("Set up main console turn on") + if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): + print(f"Updating {boot_dir}/cmdline.txt") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8") + else: + print(f"{boot_dir}/cmdline.txt already updated") + + print("Turning off console blanking") + + # pre-stretch this is what you'd do: + if shell.exists("/etc/kbd/config"): + shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0") + + # as of stretch.... + # removing any old version + shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') + shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') + shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") + + shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") + shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") + + print("Setting raspi-config to boot to console w/o login...") + shell.chdir(target_homedir) + shell.run_command("raspi-config nonint do_boot_behaviour B2") + + # remove fbcp + shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$") + return True + +def uninstall_console(): + print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') + if shell.exists("/etc/kbd/config"): + print("Screen blanking time reset to 10 minutes") + shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') + shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') + shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') + return True + +def install_fbcp(): + global fbcp_rotations + print("Installing cmake...") + if not shell.run_command("apt-get --yes --allow-downgrades --allow-remove-essential --allow-change-held-packages install cmake", suppress_message=True): + warn_exit("Apt failed to install software!") + print("Downloading rpi-fbcp...") + shell.pushd("/tmp") + shell.run_command("curl -sLO https://github.com/adafruit/rpi-fbcp/archive/master.zip") + print("Uncompressing rpi-fbcp...") + shell.run_command("rm -rf /tmp/rpi-fbcp-master") + if not shell.run_command("unzip master.zip", suppress_message=True): + warn_exit("Failed to uncompress fbcp!") + shell.chdir("rpi-fbcp-master") + shell.run_command("mkdir build") + shell.chdir("build") + print("Building rpi-fbcp...") + shell.write_text_file("../CMakeLists.txt", "\nset (CMAKE_C_FLAGS \"-std=gnu99 ${CMAKE_C_FLAGS}\")") + if not shell.run_command("cmake ..", suppress_message=True): + warn_exit("Failed to cmake fbcp!") + if not shell.run_command("make", suppress_message=True): + warn_exit("Failed to make fbcp!") + print("Installing rpi-fbcp...") + shell.run_command("install fbcp /usr/local/bin/fbcp") + shell.popd() + shell.run_command("rm -rf /tmp/rpi-fbcp-master") + + if "fbcp_rotations" in pitft_config: + fbcp_rotations = pitft_config['fbcp_rotations'] + + # Start fbcp in the appropriate place, depending on init system: + if SYSTEMD: + # Add fbcp to /etc/rc.local: + print("We have sysvinit, so add fbcp to /etc/rc.local...") + if shell.pattern_search("/etc/rc.local", "fbcp"): + # fbcp already in rc.local, but make sure correct: + shell.pattern_replace("/etc/rc.local", "^.*fbcp.*$", "/usr/local/bin/fbcp \&") + else: + # Insert fbcp into rc.local before final 'exit 0': + shell.pattern_replace("/etc/rc.local", "^exit 0", "/usr/local/bin/fbcp \&\\nexit 0") + else: + # Install fbcp systemd unit, first making sure it's not in rc.local: + uninstall_fbcp_rclocal() + print("We have systemd, so install fbcp systemd unit...") + if not install_fbcp_unit(): + shell.bail("Unable to install fbcp unit file") + shell.run_command("sudo systemctl enable fbcp.service") + + # if there's X11 installed... + if shell.exists("/etc/lightdm"): + print("Setting raspi-config to boot to desktop w/o login...") + shell.run_command("raspi-config nonint do_boot_behaviour B4") + + # Disable overscan compensation (use full screen): + shell.run_command("raspi-config nonint do_overscan 1") + # Set up HDMI parameters: + print("Configuring boot/config.txt for forced HDMI") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=1") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_group.*$", "hdmi_group=2") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_mode.*$", "hdmi_mode=87") + + # Don't use the 3D driver if we're on X11, unless the display specifically uses kms + if (is_bullseye or not wayland) and ("use_kms" not in pitft_config or not pitft_config["use_kms"]): + shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-kms-v3d.*$", "#dtoverlay=vc4-kms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", "^[^#]*dtoverlay=vc4-fkms-v3d.*$", "#dtoverlay=vc4-fkms-v3d") + + # if there's X11 installed... + scale = 1 + if shell.exists("/etc/lightdm"): + if "x11_scale" in pitft_config: + scale = pitft_config["x11_scale"] + else: + scale = 2 + WIDTH = int(pitft_config['width'] * scale) + HEIGHT = int(pitft_config['height'] * scale) + + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_cvt.*$", "hdmi_cvt={} {} 60 1 0 0 0".format(WIDTH, HEIGHT)) + + try: + default_orientation = int(list(fbcp_rotations.keys())[list(fbcp_rotations.values()).index("0")]) + except ValueError: + default_orientation = 90 + + if fbcp_rotations[pitftrot] == "0": + # dont rotate HDMI on default orientation + shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "") + else: + display_rotate = fbcp_rotations[pitftrot] + shell.reconfig(f"{boot_dir}/config.txt", "^.*display_hdmi_rotate.*$", "display_hdmi_rotate={}".format(display_rotate)) + # Because we rotate HDMI we have to 'unrotate' the TFT by overriding pitftrot! + if not update_configtxt(default_orientation): + shell.bail(f"Unable to update {boot_dir}/config.txt") + return True + +def update_wayfire_settings(): + # Set the scale factor for Wayland, which is the reciprocal of the X11 scale factor + if "x11_scale" in pitft_config: + scale = 1/pitft_config["x11_scale"] + else: + scale = 0.5 + device_name = "SPI-1" + wayfire_config = f"{target_homedir}/.config/wayfire.ini" + # Remove any existing settings previously added by this script + shell.pattern_replace(wayfire_config, '\n# --- added by adafruit-pitft-helper.*?\n# --- end adafruit-pitft-helper.*?\n', multi_line=True) + date = shell.date() + shell.write_text_file(wayfire_config, f""" +# --- added by adafruit-pitft-helper {date} --- +[output:{device_name}] +scale = {scale} +# --- end adafruit-pitft-helper {date} --- +""") + + shell.pattern_replace(target_homedir, "^.*[output:SPI-1].*$", "hdmi_force_hotplug=0") + + +def install_fbcp_unit(): + shell.write_text_file("/etc/systemd/system/fbcp.service", + """[Unit] +Description=Framebuffer copy utility for PiTFT +After=network.target + +[Service] +Type=simple +ExecStartPre=/bin/sleep 10 +ExecStart=/usr/local/bin/fbcp + +[Install] +WantedBy=multi-user.target +""", append=False) + return True + +def uninstall_fbcp(): + uninstall_fbcp_rclocal() + # Enable overscan compensation + if shell.exists("/etc/systemd/system/fbcp.service"): + shell.run_command("sudo systemctl disable fbcp.service") + # Set up HDMI parameters: + shell.run_command("raspi-config nonint do_overscan 0") + print("Configuring boot/config.txt for default HDMI") + shell.reconfig(f"{boot_dir}/config.txt", "^.*hdmi_force_hotplug.*$", "hdmi_force_hotplug=0") + shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-kms-v3d.*$", "dtoverlay=vc4-kms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", "^.*#.*dtoverlay=vc4-fkms-v3d.*$", "dtoverlay=vc4-fkms-v3d") + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_group=2.*$') + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_mode=87.*$') + shell.pattern_replace(f"{boot_dir}/config.txt", '^hdmi_cvt=.*$') + + if not wayland and not is_bullseye and shell.exists("/etc/lightdm"): + print("Restoring Wayland as default display manager...") + shell.set_window_manager("wayland") + + return True + +def uninstall_fbcp_rclocal(): + """Remove fbcp from /etc/rc.local:""" + print("Remove fbcp from /etc/rc.local, if it's there...") + shell.pattern_replace("/etc/rc.local", '^.*fbcp.*$') + return True + +def update_xorg(tinydrm_install=False): + if "touchscreen" in pitft_config: + transform_setting = pitft_config["touchscreen"]["transforms"][pitftrot] + if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: + transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] + transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" + shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ +Section "InputClass" + Identifier "{identifier}" + MatchProduct "{product}" + MatchDevicePath "/dev/input/event*" + Driver "libinput" + {transform} +EndSection +""".format( + identifier=pitft_config["touchscreen"]["identifier"], + product=pitft_config["touchscreen"]["product"], + transform=transform + ), + append=False, + ) + return True + +def get_config_types(): + types = [] + for item in config: + types.append(item["type"]) + return types + +def get_config(type): + for item in config: + if item["type"] == type: + return item + return None + +def uninstall(): + shell.info("Uninstalling PiTFT") + uninstall_bootconfigtxt() + uninstall_console() + uninstall_fbcp() + uninstall_fbcp_rclocal() + uninstall_etc_modules() + success() + +def success(): + global auto_reboot + shell.info("Success!") + print(""" +Settings take effect on next boot. +""") + if auto_reboot is None: + auto_reboot = shell.prompt("REBOOT NOW?", default="y") + if not auto_reboot: + print("Exiting without reboot.") + shell.exit() + print("Reboot started...") + shell.reboot() + shell.exit() + +####################################################### MAIN +target_homedir = "/home/pi" +username = os.environ["SUDO_USER"] +user_homedir = os.path.expanduser(f"~{username}") +if shell.isdir(user_homedir): + target_homedir = user_homedir + +boot_dir = "/boot/firmware" if shell.isdir("/boot/firmware") else "/boot" + +# If neither directory is valid, use shell.get_boot_config() +if not shell.isdir(boot_dir): + boot_dir = os.path.dirname(shell.get_boot_config()) + +# Final safeguard: Ensure boot_dir is actually a directory and not a file path +if boot_dir is None or not shell.isdir(boot_dir): + shell.bail("Unable to find boot directory") + +if shell.get_raspbian_version() == "bullseye": + is_bullseye = True + +@click.command() +@click.option('-v', '--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True, help="Print version information") +@click.option('-u', '--user', nargs=1, default=target_homedir, type=str, help="Specify path of primary user's home directory", show_default=True) +@click.option('--display', nargs=1, default=None, help="Specify a display option (1-{}) or type {}".format(len(config), get_config_types())) +@click.option('--rotation', nargs=1, default=None, type=int, help="Specify a rotation option (1-4) or degrees {}".format(tuple(sorted([int(x) for x in PITFT_ROTATIONS])))) +@click.option('--install-type', nargs=1, default=None, type=click.Choice(['mirror', 'fbcp', 'console', 'uninstall']), help="Installation Type") +@click.option('--reboot', nargs=1, default=None, type=click.Choice(['yes', 'no']), help="Specify whether to reboot after the script is finished") +@click.option('--boot', nargs=1, default=boot_dir, type=str, help="Specify the boot directory", show_default=True) +def main(user, display, rotation, install_type, reboot, boot): + global target_homedir, pitft_config, pitftrot, auto_reboot, boot_dir, wayland + shell.clear() + if user != target_homedir: + target_homedir = user + print(f"Homedir = {target_homedir}") + if boot != boot_dir: + if shell.isdir(boot): + boot_dir = boot + print(f"Boot dir = {boot_dir}") + else: + print(f"{boot} not found or not a directory. Using {boot_dir} instead.") + wayland = is_wayland() + print(("Wayland" if wayland else "X11") + " Detected") + if is_bullseye: + print("Bullseye Detected") + print() + # "mirror" will be the new install type, but keep fbcp for backwards compatibility + if install_type == "fbcp": + install_type = "mirror" + + print("""This script downloads and installs +PiTFT Support using userspace touch +controls and a DTO for display drawing. +one of several configuration files. +Run time of up to 5 minutes. Reboot required! +""") + if reboot is not None: + auto_reboot = reboot.lower() == 'yes' + + if install_type == "uninstall": + uninstall() + + def select_display(config, interactive=False): + global pitft_config, wayland + pitft_config = config + print("Display Type: {}".format(pitft_config["menulabel"])) + if is_kernel_upgrade_required(): + print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") + if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland: + if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"): + shell.set_window_manager("x11") + wayland = False + + if display in [str(x) for x in range(1, len(config) + 1)]: + select_display(config[int(display) - 1]) + elif display in get_config_types(): + select_display(get_config(display)) + else: + # Build menu from config + selections = [] + for item in config: + option = "{} ({}x{})".format(item['menulabel'], item['width'], item['height']) + if is_kernel_upgrade_required(item): + option += " - WARNING! WILL UPGRADE YOUR KERNEL TO LATEST" + selections.append(option) + selections.append("Uninstall PiTFT") + selections.append("Quit without installing") + + PITFT_SELECT = shell.select_n("Select configuration:", selections) + if PITFT_SELECT == len(config) + 2: + shell.exit(1) + if PITFT_SELECT == len(config) + 1: + uninstall() + select_display(config[PITFT_SELECT - 1], True) + + if rotation is not None and 1 <= rotation <= 4: + pitftrot = PITFT_ROTATIONS[rotation - 1] + shell.info("Rotation: {}".format(pitftrot)) + elif str(rotation) in PITFT_ROTATIONS: + pitftrot = str(rotation) + shell.info("Rotation: {}".format(pitftrot)) + else: + PITFT_ROTATE = shell.select_n( + "Select rotation:", ( + "90 degrees (landscape)", + "180 degrees (portrait)", + "270 degrees (landscape)", + "0 degrees (portrait)" + )) + pitftrot = PITFT_ROTATIONS[PITFT_ROTATE - 1] + + if 'rotations' in pitft_config and isinstance(pitft_config['rotations'], dict) and pitftrot in pitft_config['rotations'] and pitft_config['rotations'][pitftrot] is None: + shell.bail("""Unfortunately {rotation} degrees for the {display} is not working at this time. Please +restart the script and choose a different orientation.""".format(rotation=pitftrot, display=pitft_config["menulabel"])) + + if REMOVE_KERNEL_PINNING: + # Checking if kernel is pinned + if shell.exists('/etc/apt/preferences.d/99-adafruit-pin-kernel'): + shell.warn("WARNING! Script detected your system is currently pinned to an older kernel. The pin will be removed and your system will be updated.") + if not shell.prompt("Continue?"): + shell.exit() + shell.remove('/etc/apt/preferences.d/99-adafruit-pin-kernel') + + # check init system (technique borrowed from raspi-config): + shell.info('Checking init system...') + if shell.run_command("which systemctl", suppress_message=True) and shell.run_command("systemctl | grep '\-\.mount'", suppress_message=True): + SYSTEMD = True + print("Found systemd") + elif os.path.isfile("/etc/init.d/cron") and not os.path.islink("/etc/init.d/cron"): + SYSTEMD = False + print("Found sysvinit") + else: + shell.bail("Unrecognised init system") + + if shell.grep("boot", "/proc/mounts"): + print("/boot is mounted") + else: + print("/boot must be mounted. if you think it's not, quit here and try: sudo mount /dev/mmcblk0p1 /boot") + if shell.prompt("Continue?"): + print("Proceeding.") + else: + shell.bail("Aborting.") + + if not shell.isdir(target_homedir): + shell.bail("{} must be an existing directory (use -u /home/foo to specify)".format(target_homedir)) + + shell.info("System update") + if not sysupdate(): + shell.bail("Unable to apt-get update") + + shell.info("Installing Python libraries & Software...") + if not softwareinstall(): + shell.bail("Unable to install software") + + if "overlay_src" in pitft_config and "overlay_dest" in pitft_config: + shell.info("Installing display drivers and device tree overlay...") + if not install_drivers(): + shell.bail("Unable to install display drivers") + + shell.info(f"Updating {boot_dir}/config.txt...") + if not update_configtxt(tinydrm_install=(not is_bullseye)): + shell.bail(f"Unable to update {boot_dir}/config.txt") + + if "touchscreen" in pitft_config: + shell.info("Updating SysFS rules for Touchscreen...") + if not update_udev(): + shell.bail("Unable to update /etc/udev/rules.d") + + shell.info("Updating TSLib default calibration...") + if not update_pointercal(): + shell.bail("Unable to update /etc/pointercal") + + # ask for console access + if install_type == "console" or (install_type is None and shell.prompt("Would you like the console to appear on the PiTFT display?")): + shell.info("Updating console to PiTFT...") + if not uninstall_fbcp(): + shell.bail("Unable to uninstall fbcp") + if not install_console(): + shell.bail("Unable to configure console") + else: + shell.info("Making sure console doesn't use PiTFT") + if not uninstall_console(): + shell.bail("Unable to configure console") + + mirror_prompt = "Would you like the HDMI display to mirror to the PiTFT display?" + if wayland: + # With wayland, PiTFT shows up as an additional display rather than a mirror + mirror_prompt = "Would you like the to use the PiTFT as a desktop display?" + if install_type == "mirror" or (install_type is None and shell.prompt(mirror_prompt)): + if wayland: + shell.info("Updating Wayland desktop settings...") + update_wayfire_settings() + else: + shell.info("Adding FBCP support...") + if not install_fbcp(): + shell.bail("Unable to configure fbcp") + + if shell.exists("/etc/lightdm"): + shell.info("Updating Desktop Touch calibration...") + if not update_xorg(tinydrm_install=wayland): + shell.bail("Unable to update calibration") + else: + if not uninstall_fbcp(): + shell.bail("Unable to uninstall fbcp") + success() + +# Main function +if __name__ == "__main__": + shell.require_root() + if shell.is_raspberry_pi_os() and shell.is_kernel_userspace_mismatched() and shell.is_pi5_or_newer(): + shell.bail("Unable to proceed on Pi 5 or newer boards with a with a 32-bit OS. Please reinstall with a 64-bit OS.") + shell.check_kernel_userspace_mismatch() + main() diff --git a/adafruit-pitft.py b/adafruit-pitft.py index c6c9d04..3274b16 100644 --- a/adafruit-pitft.py +++ b/adafruit-pitft.py @@ -19,12 +19,44 @@ except ImportError: shell = Shell() shell.group = 'PITFT' -__version__ = "3.9.0" +__version__ = "4.0.0" """ This is the main configuration. Displays should be placed in the order they are to appear in the menu. + +Config Parameters: +REQUIRED fields: +type: Unique identifier for the display type. +menulabel: The label to display in the menu. +product: The product name for the display. +kernel_upgrade: Whether the kernel needs to be upgraded for this display. +overlay: The overlay string to apply for the display. +width: Width of the display in pixels. +height: Height of the display in pixels. + +OPTIONAL fields: +touchscreen: Dictionary containing touchscreen settings for displays with touchscreens. + identifier: Name of the touchscreen calibration. + product: Product name for the touchscreen. + transforms: X11 transforms for different rotations. + overlay_params: Overlay parameters for different rotations. + calibrations: Calibration data for the touchscreen. Can be overall or per rotation. +overlay_drm_option: Optional parameter to add for DRM support. +overlay_src: Source path for the overlay file (if applicable). +overlay_dest: Destination path for the compiled overlay file (if applicable). +mipi_data (Optional): Dictionary containing MIPI display data. + command_bin: Name of the command binary for MIPI. + gpio: GPIO settings for the display. + viewport: Viewport settings for different rotations. +use_kms: Whether to use KMS for the display. """ + +# Touchscreen Products +TS_STMPE = "stmpe" +TS_TSC2007 = "tsc2007" +TS_FOCALTOUCH = "EP0110M09" + config = [ { "type": "28r", @@ -33,7 +65,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", + "product": TS_STMPE, + # X11 Transforms "transforms": { "0": "0.988809 -0.023645 0.060523 -0.028817 1.003935 0.034176 0 0 1", "90": "0.014773 -1.132874 1.033662 1.118701 0.009656 -0.065273 0 0 1", @@ -46,16 +79,15 @@ config = [ "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, }, "overlay": "dtoverlay=pitft28-resistive,rotate={pitftrot},speed=64000000,fps=30", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "4232 11 -879396 1 5786 -752768 65536", - "90": "33 -5782 21364572 4221 35 -1006432 65536", - "180": "-4273 61 16441290 4 -5772 21627524 65536", - "270": "-9 5786 -784608 -4302 19 16620508 65536", - }, "width": 320, "height": 240, }, @@ -76,7 +108,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "FocalTech Touchscreen Calibration", - "product": "EP0110M09", + "product": TS_FOCALTOUCH, + # X11 Transforms "transforms": { "0": "-1 0 1 0 -1 1 0 0 1", "90": "0 1 0 -1 0 1 0 0 1", @@ -89,11 +122,10 @@ config = [ "180": None, "270": "touch-swapxy,touch-invx", }, + "calibrations": "320 65536 0 -65536 0 15728640 65536", }, "overlay": "dtoverlay=pitft28-capacitive,rotate={pitftrot},speed=64000000,fps=30", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": "320 65536 0 -65536 0 15728640 65536", "width": 320, "height": 240, }, @@ -104,7 +136,8 @@ config = [ "kernel_upgrade": False, "touchscreen": { "identifier": "STMPE Touchscreen Calibration", - "product": "stmpe", + "product": TS_STMPE, + # X11 Transforms "transforms": { "0": "-1.098388 0.003455 1.052099 0.005512 -1.093095 1.026309 0 0 1", "90": "-0.000087 1.094214 -0.028826 -1.091711 -0.004364 1.057821 0 0 1", @@ -117,16 +150,15 @@ config = [ "180": "touch-invx,touch-invy", "270": "touch-swapxy,touch-invy", }, + "calibrations": { + "0": "5724 -6 -1330074 26 8427 -1034528 65536", + "90": "5 8425 -978304 -5747 61 22119468 65536", + "180": "-5682 -1 22069150 13 -8452 32437698 65536", + "270": "3 -8466 32440206 5703 -1 -1308696 65536", + }, }, "overlay": "dtoverlay=pitft35-resistive,rotate={pitftrot},speed=20000000,fps=20", "overlay_drm_option": "drm", - "force_x11": True, - "calibrations": { - "0": "5724 -6 -1330074 26 8427 -1034528 65536", - "90": "5 8425 -978304 -5747 61 22119468 65536", - "180": "-5682 -1 22069150 13 -8452 32437698 65536", - "270": "3 -8466 32440206 5703 -1 -1308696 65536", - }, "width": 480, "height": 320, "x11_scale": 1.5, @@ -247,6 +279,35 @@ config = [ "270": "3", }, }, + { + "type": "24v2", + "menulabel": "PiTFT 2.4\" V2 resistive (240x320)", + "product": "2.4\" V2 resistive", + "kernel_upgrade": False, + "overlay_src": "overlays/pitft24v2-tsc2007-overlay.dts", + "overlay_dest": "{boot_dir}/overlays/pitft24v2.dtbo", + "touchscreen": { + "identifier": "TSC2007 Touchscreen Calibration", + "product": TS_TSC2007, + "transforms": { + # TODO: Test because 0 and 180 may be reversed + "0": "-1 0 1 0 -1 1", + "90": "0 1 0 -1 0 1", + "180": "1 0 0 0 1 0", + "270": "0 -1 1 1 0 0", + }, + "calibrations": { + "0": "4232 11 -879396 1 5786 -752768 65536", + "90": "33 -5782 21364572 4221 35 -1006432 65536", + "180": "-4273 61 16441290 4 -5772 21627524 65536", + "270": "-9 5786 -784608 -4302 19 16620508 65536", + }, + }, + "overlay": "dtoverlay=pitft24v2,rotate={pitftrot},fps=60", + "overlay_drm_option": "drm", + "width": 320, + "height": 240, + }, ] # default rotations @@ -277,12 +338,12 @@ def warn_exit(message): shell.warn(message) shell.exit(1) -def uninstall_cb(ctx, param, value): +def uninstall_cb(ctx, _param, value): if not value or ctx.resilient_parsing: return uninstall() -def print_version(ctx, param, value): +def print_version(ctx, _param, value): if not value or ctx.resilient_parsing: return print("Adafruit PiTFT Helper v{}".format(__version__)) @@ -311,6 +372,7 @@ def sysupdate(): ############################ Sub-Scripts ############################ def is_wayland(): + "Check if the current session is Wayland" username = os.environ["SUDO_USER"] output = shell.run_command("loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type | grep wayland", suppress_message=True, return_output=True, run_as_user=username).strip() return len(output) > 0 @@ -423,34 +485,29 @@ def update_configtxt(rotation_override=None, tinydrm_install=False): if "gpio" in mipi_data: overlay += f"\ndtparam={mipi_data['gpio']}" - if tinydrm_install and "overlay_drm_option" in pitft_config: - overlay += "," + pitft_config["overlay_drm_option"] - if tinydrm_install: # Wayland ignores X11 Transformations, so use params instead + if tinydrm_install: # Wayland ignores X11 Transformations, so use overlay params instead + overlay += ",drm" if "overlay_params" in pitft_config and pitftrot in pitft_config["overlay_params"] and pitft_config["overlay_params"][pitftrot] is not None: overlay += "," + pitft_config["overlay_params"][pitftrot] - shell.write_text_file(f"{boot_dir}/config.txt", """ -# --- added by adafruit-pitft-helper {date} --- -[all] -hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! -dtparam=spi=on -dtparam=i2c1=on -dtparam=i2c_arm=on -{overlay} -# --- end adafruit-pitft-helper {date} --- -""".format(date=shell.date(), overlay=overlay)) + config_text_base = shell.load_template("templates/config_text_base.txt", date=shell.date(), overlay=overlay) + shell.write_text_file(f"{boot_dir}/config.txt", config_text_base) return True def update_udev(): - shell.write_text_file("/etc/udev/rules.d/95-touchmouse.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-ftcaptouch.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) - shell.write_text_file("/etc/udev/rules.d/95-stmpe.rules", """ -SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" -""", append=False) + product = None + if "touchscreen" in pitft_config and "product" in pitft_config["touchscreen"]: + product = pitft_config["touchscreen"]["product"] + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-touchmouse.rules") + if product == TS_FOCALTOUCH: + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-ftcaptouch.rules") + elif product == TS_STMPE: + shell.write_templated_file("/etc/udev/rules.d/", "templates/95-stmpe.rules") + elif product == TS_TSC2007: + calibration_matrix = "0 1 0 -1 0 1" # Default calibration matrix + if "transforms" in pitft_config["touchscreen"] and pitftrot in pitft_config["touchscreen"]["transforms"]: + calibration_matrix = pitft_config["touchscreen"]["transforms"][pitftrot] + shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen.rules", calibration_matrix=calibration_matrix) + shell.write_templated_file("/etc/udev/rules.d/", "templates/99-tsc2007-touchscreen-drm.rules") return True def compile_display_fw(): @@ -473,33 +530,32 @@ def compile_display_fw(): return True def update_pointercal(): - if "calibrations" in pitft_config: - if isinstance(pitft_config["calibrations"], dict): - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"][pitftrot]) + if "touchscreen" in pitft_config and "calibrations" in pitft_config["touchscreen"]: + if isinstance(pitft_config["touchscreen"]["calibrations"], dict): + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"][pitftrot]) else: - shell.write_text_file("/etc/pointercal", pitft_config["calibrations"]) + shell.write_text_file("/etc/pointercal", pitft_config["touchscreen"]["calibrations"]) return True def install_console(): - print("Set up main console turn on") - if not shell.pattern_search(f"{boot_dir}/cmdline.txt", 'fbcon=map:10 fbcon=font:VGA8x8'): - print(f"Updating {boot_dir}/cmdline.txt") - shell.pattern_replace(f"{boot_dir}/cmdline.txt", "rootwait", "rootwait fbcon=map:10 fbcon=font:VGA8x8") - else: - print(f"{boot_dir}/cmdline.txt already updated") + print("Installing console fbcon map helper...") + shell.copy("con2fbmap-helper.sh", "/usr/local/bin/") + shell.chmod("/usr/local/bin/con2fbmap-helper.sh", "+x") + + print("Installing console fbcon map service...") + shell.copy("con2fbmap.service", "/etc/systemd/system/") + shell.run_command("systemctl daemon-reload") + shell.run_command("systemctl restart con2fbmap.service") print("Turning off console blanking") - - # pre-stretch this is what you'd do: - if shell.exists("/etc/kbd/config"): - shell.pattern_replace("/etc/kbd/config", "BLANK_TIME=.*", "BLANK_TIME=0") - - # as of stretch.... - # removing any old version + # removing old versions shell.pattern_replace("/etc/rc.local", '# disable console blanking.*') shell.pattern_replace("/etc/rc.local", 'sudo sh -c "TERM=linux setterm -blank.*') + + # adding new version shell.pattern_replace("/etc/rc.local", '^exit 0', "# disable console blanking on PiTFT\\nsudo sh -c \"TERM=linux setterm -blank 0 >/dev/tty0\"\\nexit 0") + # Set the console font to Terminus 6x12 shell.reconfig("/etc/default/console-setup", "^.*FONTFACE.*$", "FONTFACE=\"Terminus\"") shell.reconfig("/etc/default/console-setup", "^.*FONTSIZE.*$", "FONTSIZE=\"6x12\"") @@ -514,9 +570,15 @@ def install_console(): def uninstall_console(): print(f"Removing console fbcon map from {boot_dir}/cmdline.txt") shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'rootwait fbcon=map:10 fbcon=font:VGA8x8', 'rootwait') - print("Screen blanking time reset to 10 minutes") - if shell.exists("/etc/kbd/config"): - shell.pattern_replace(f"{boot_dir}/cmdline.txt", 'BLANK_TIME=0', 'BLANK_TIME=10') + + print("Removing console fbcon map service...") + shell.run_command("systemctl stop con2fbmap.service") + shell.run_command("systemctl disable con2fbmap.service") + shell.remove("/etc/systemd/system/con2fbmap.service") + + print("Removing console fbcon map helper...") + shell.remove("/usr/local/bin/con2fbmap-helper.sh") + shell.pattern_replace("/etc/rc.local", '^# disable console blanking.*') shell.pattern_replace("/etc/rc.local", '^sudo sh -c "TERM=linux.*') return True @@ -568,7 +630,7 @@ def install_fbcp(): shell.bail("Unable to install fbcp unit file") shell.run_command("sudo systemctl enable fbcp.service") - # if there's X11 installed... + # if desktop environment is installed... if shell.exists("/etc/lightdm"): print("Setting raspi-config to boot to desktop w/o login...") shell.run_command("raspi-config nonint do_boot_behaviour B4") @@ -636,19 +698,7 @@ scale = {scale} def install_fbcp_unit(): - shell.write_text_file("/etc/systemd/system/fbcp.service", - """[Unit] -Description=Framebuffer copy utility for PiTFT -After=network.target - -[Service] -Type=simple -ExecStartPre=/bin/sleep 10 -ExecStart=/usr/local/bin/fbcp - -[Install] -WantedBy=multi-user.target -""", append=False) + shell.write_templated_file("/etc/systemd/system/", "templates/fbcp.service") return True def uninstall_fbcp(): @@ -684,7 +734,8 @@ def update_xorg(tinydrm_install=False): if not tinydrm_install and "old_transforms" in pitft_config["touchscreen"]: transform_setting = pitft_config["touchscreen"]["old_transforms"][pitftrot] transform = f"Option \"TransformationMatrix\" \"{transform_setting}\"" - shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ + if shell.exists("/usr/share/X11/xorg.conf.d"): + shell.write_text_file("/usr/share/X11/xorg.conf.d/20-calibration.conf", """ Section "InputClass" Identifier "{identifier}" MatchProduct "{product}" @@ -804,10 +855,6 @@ Run time of up to 5 minutes. Reboot required! print("Display Type: {}".format(pitft_config["menulabel"])) if is_kernel_upgrade_required(): print("WARNING! WILL UPGRADE YOUR KERNEL TO LATEST") - if "force_x11" in pitft_config and pitft_config["force_x11"] and wayland: - if not interactive or shell.prompt("This display works better with X11, but Wayland is currently running. Use X11 instead? (Recommended)", default="y"): - shell.set_window_manager("x11") - wayland = False if display in [str(x) for x in range(1, len(config) + 1)]: select_display(config[int(display) - 1]) diff --git a/con2fbmap-helper.sh b/con2fbmap-helper.sh new file mode 100644 index 0000000..52a1da6 --- /dev/null +++ b/con2fbmap-helper.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Wait for TFT framebuffer to be ready +echo "Waiting for SPI TFT framebuffer..." + +# Wait up to 30 seconds for /dev/fb0 or /dev/fb1 to appear +for i in {1..300}; do + for fbdev in 0 1; do + if [ -e /dev/fb$fbdev ]; then + echo "Found /dev/fb$fbdev, checking if it's ili9341..." + + # Check if it's actually the ili9341 device + if dmesg | grep -q "ili9341.*fb$fbdev"; then + echo "ili9341 framebuffer ready, mapping console..." + con2fbmap 1 $fbdev + echo "Console mapped to framebuffer $fbdev" + exit 0 + fi + fi + done + sleep 0.1 +done + +echo "Timeout waiting for SPI TFT framebuffer" +exit 1 diff --git a/con2fbmap.service b/con2fbmap.service new file mode 100644 index 0000000..7605709 --- /dev/null +++ b/con2fbmap.service @@ -0,0 +1,14 @@ +[Unit] +Description=Map console to framebuffer for SPI TFT +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/con2fbmap-helper.sh +RemainAfterExit=yes +StandardOutput=journal +StandardError=journal +TimeoutStartSec=60 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/adafruit-pitft-mipi.py b/old_scripts/adafruit-pitft-mipi.py similarity index 100% rename from adafruit-pitft-mipi.py rename to old_scripts/adafruit-pitft-mipi.py diff --git a/overlays/pitft24v2-tsc2007-overlay.dts b/overlays/pitft24v2-tsc2007-overlay.dts new file mode 100644 index 0000000..7934017 --- /dev/null +++ b/overlays/pitft24v2-tsc2007-overlay.dts @@ -0,0 +1,114 @@ +/* + * Device Tree overlay for Adafruit PiTFT 2.4" resistive touch screen with tsc2007 + * + */ + +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&spi0>; + __overlay__ { + status = "okay"; + }; + }; + + fragment@2 { + target = <&spidev0>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@3 { + target = <&spidev1>; + __overlay__ { + status = "disabled"; + }; + }; + + fragment@4 { + target = <&gpio>; + __overlay__ { + adafruit_2_4_tft_pins: adafruit_2_4_tft_pins { + brcm,pins = <25 24 18>; + brcm,function = <1 0 1>; /* out in out*/ + brcm,pull = <0 2 2>; /* none up up */ + }; + }; + }; + + fragment@5 { + target = <&spi0>; + __overlay__ { + /* needed to avoid dtc warning */ + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio 8 1>; + + adafruit_2_4_tft: adafruit_2_4_tft@0{ + compatible = "adafruit,yx240qv29"; + reg = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + + spi-max-frequency = <32000000>; + rotation = <90>; + reset-gpios = <&gpio 23 0>; + dc-gpios = <&gpio 25 0>; + }; + }; + }; + + fragment@7 { + target = <&i2c1>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + status = "okay"; + + tsc2007: tsc2007@48 { + compatible = "ti,tsc2007"; + reg = <0x48>; + ti,fuzzx = <4>; + ti,fuzzy = <4>; + ti,max-rt = <3700>; + ti,fuzzz = <4>; + ti,x-plate-ohms = <180>; + + panel = <&adafruit_2_4_tft>; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + interrupt-parent = <&gpio>; + interrupts = <24 2>; + gpios = <&gpio 24 1>; + + }; + }; + }; + + fragment@8 { + target-path = "/soc"; + __overlay__ { + backlight: backlight { + compatible = "gpio-backlight"; + pinctrl-0 = <&adafruit_2_4_tft_pins>; + gpios = <&gpio 18 2>; + default-on; + }; + }; + }; + + __overrides__ { + speed = <&adafruit_2_4_tft>,"spi-max-frequency:0"; + rotate = <&adafruit_2_4_tft>,"rotation:0"; + fps = <&adafruit_2_4_tft>,"fps:0"; + debug = <&adafruit_2_4_tft>,"debug:0"; + // xohms = <&tsc2007>,"touchscreen-x-plate-ohms;0"; + // swapxy = <&tsc2007>,"touchscreen-swapped-x-y?"; + }; + +}; diff --git a/templates/95-ftcaptouch.rules b/templates/95-ftcaptouch.rules new file mode 100644 index 0000000..3685669 --- /dev/null +++ b/templates/95-ftcaptouch.rules @@ -0,0 +1,3 @@ +# udev rule for Focaltouch Capactive Touchscreen +SUBSYSTEM=="input", ATTRS{name}=="EP0110M09", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" +SUBSYSTEM=="input", ATTRS{name}=="generic ft5x06*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" diff --git a/templates/95-stmpe.rules b/templates/95-stmpe.rules new file mode 100644 index 0000000..d92b2ef --- /dev/null +++ b/templates/95-stmpe.rules @@ -0,0 +1,2 @@ +udev rule for STMPE Resistive Touchscreen Controller +SUBSYSTEM=="input", ATTRS{name}=="*stmpe*", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" diff --git a/templates/95-touchmouse.rules b/templates/95-touchmouse.rules new file mode 100644 index 0000000..24cf4a4 --- /dev/null +++ b/templates/95-touchmouse.rules @@ -0,0 +1 @@ +SUBSYSTEM=="input", ATTRS{name}=="touchmouse", ENV{DEVNAME}=="*event*", SYMLINK+="input/touchscreen" \ No newline at end of file diff --git a/templates/99-tsc2007-touchscreen-drm.rules b/templates/99-tsc2007-touchscreen-drm.rules new file mode 100644 index 0000000..d90e8b9 --- /dev/null +++ b/templates/99-tsc2007-touchscreen-drm.rules @@ -0,0 +1,14 @@ +# udev rules for SPI TFT DRM devices to create reliable symlinks +# Supports ili9341, st7789, and other SPI TFT displays + +# Rule for ili9341 DRM device (and other SPI TFT displays) +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="ili9341", SYMLINK+="dri/spitft" + +# Generic rule for any SPI TFT display on spi0.0 +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", SUBSYSTEMS=="spi", SYMLINK+="dri/spitft" + +# Rule for st7789 displays (add when needed) +SUBSYSTEM=="drm", KERNEL=="card*", KERNELS=="spi0.0", DRIVERS=="st7789", SYMLINK+="dri/spitft" + +# Alternative rule using device path pattern +SUBSYSTEM=="drm", KERNEL=="card*", DEVPATH=="*/spi0/spi0.0/drm/card*", SYMLINK+="dri/spitft" \ No newline at end of file diff --git a/templates/99-tsc2007-touchscreen.rules b/templates/99-tsc2007-touchscreen.rules new file mode 100644 index 0000000..609643c --- /dev/null +++ b/templates/99-tsc2007-touchscreen.rules @@ -0,0 +1,5 @@ +# udev rule for TSC2007 touchscreen to create /dev/input/touchscreen symlink and apply transformation +SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="TSC2007 Touchscreen", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}" + +# Backup rule using touchscreen capability and TSC2007 name pattern +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_TOUCHSCREEN}=="1", ATTRS{name}=="*TSC2007*", SYMLINK+="input/touchscreen", ENV{LIBINPUT_CALIBRATION_MATRIX}="{calibration_matrix}" diff --git a/templates/config_text_base.txt b/templates/config_text_base.txt new file mode 100644 index 0000000..6136bf7 --- /dev/null +++ b/templates/config_text_base.txt @@ -0,0 +1,8 @@ +# --- added by adafruit-pitft-helper {date} --- +[all] +hdmi_force_hotplug=1 # required for cases when HDMI is not plugged in! +dtparam=spi=on +dtparam=i2c1=on +dtparam=i2c_arm=on +{overlay} +# --- end adafruit-pitft-helper {date} --- diff --git a/templates/fbcp.service b/templates/fbcp.service new file mode 100644 index 0000000..c39105b --- /dev/null +++ b/templates/fbcp.service @@ -0,0 +1,11 @@ +[Unit] +Description=Framebuffer copy utility for PiTFT +After=network.target + +[Service] +Type=simple +ExecStartPre=/bin/sleep 10 +ExecStart=/usr/local/bin/fbcp + +[Install] +WantedBy=multi-user.target \ No newline at end of file