From 1e71ed5a0a4ee8a0e7731a79b233600b2129135a Mon Sep 17 00:00:00 2001 From: Ptosiek <16878205+Ptosiek@users.noreply.github.com> Date: Wed, 25 Oct 2023 19:23:50 +0200 Subject: [PATCH 1/4] Display inherits from base (X-Window) display Simplify code a bit Move display related conf out of config object --- modules/config.py | 22 +-- modules/display/dfrobot_rpi_display.py | 37 ++-- modules/display/display_core.py | 194 ++++++++----------- modules/display/mip_display.py | 70 +++---- modules/display/mip_sharp_display.py | 46 +++-- modules/display/papirus_display.py | 22 ++- modules/display/pitft_28_r.py | 32 ++- modules/gui_pyqt.py | 137 +++++++------ modules/logger_core.py | 9 +- modules/pyqt/graph/pyqt_course_profile.py | 2 +- modules/pyqt/graph/pyqt_map.py | 2 +- modules/pyqt/menu/pyqt_adjust_widget.py | 2 +- modules/pyqt/menu/pyqt_menu_widget.py | 2 +- modules/pyqt/menu/pyqt_sensor_menu_widget.py | 2 +- modules/pyqt/pyqt_screen_widget.py | 2 +- modules/sensor_core.py | 6 +- pizero_bikecomputer.py | 4 +- 17 files changed, 276 insertions(+), 315 deletions(-) diff --git a/modules/config.py b/modules/config.py index 9c0d25bb..8b4d7832 100644 --- a/modules/config.py +++ b/modules/config.py @@ -442,25 +442,7 @@ class Config: G_FULLSCREEN = False # display type (overwritten with setting.conf) - G_DISPLAY = "None" # PiTFT, MIP, Papirus, MIP_Sharp - - # screen size (need to add when adding new device) - G_AVAILABLE_DISPLAY = { - "None": {"size": (400, 240), "touch": True, "color": True}, - "PiTFT": {"size": (320, 240), "touch": True, "color": True}, - "MIP": { - "size": (400, 240), - "touch": False, - "color": True, - }, # LPM027M128C, LPM027M128B - "MIP_640": {"size": (640, 480), "touch": False, "color": True}, # LPM044M141A - "MIP_Sharp": {"size": (400, 240), "touch": False, "color": False}, - "MIP_Sharp_320": {"size": (320, 240), "touch": False, "color": False}, - "Papirus": {"size": (264, 176), "touch": False, "color": False}, - "DFRobot_RPi_Display": {"size": (250, 122), "touch": False, "color": False}, - } - G_WIDTH = 400 - G_HEIGHT = 240 + G_DISPLAY = "None" # PiTFT, MIP, MIP_640, Papirus, MIP_Sharp, MIP_Sharp_320, DFRobot_RPi_Display G_DISPLAY_PARAM = { "SPI_CLOCK": 2000000, @@ -759,7 +741,7 @@ def __init__(self): def init_loop(self, call_from_gui=False): if self.G_GUI_MODE == "PyQt": if call_from_gui: - #asyncio.set_event_loop(self.loop) + # asyncio.set_event_loop(self.loop) # workaround for latest qasync and older version(~0.24.0) asyncio.events._set_running_loop(self.loop) self.start_coroutine() diff --git a/modules/display/dfrobot_rpi_display.py b/modules/display/dfrobot_rpi_display.py index ce803fd7..4c8babe6 100644 --- a/modules/display/dfrobot_rpi_display.py +++ b/modules/display/dfrobot_rpi_display.py @@ -1,4 +1,5 @@ from logger import app_logger +from .display_core import Display _SENSOR_DISPLAY = False @@ -21,29 +22,32 @@ # e-ink Display Module for Raspberry Pi 4B/3B+/Zero W version 1.0 -class DFRobotRPiDisplay: - config = None +class DFRobotRPiDisplay(Display): epaper = None + has_color = False + has_touch = False + + size = (250, 122) + def __init__(self, config): - self.config = config - - if _SENSOR_DISPLAY: - self.epaper = DFRobot_Epaper_SPI( - RASPBERRY_SPI_BUS, - RASPBERRY_SPI_DEV, - RASPBERRY_PIN_CS, - RASPBERRY_PIN_CD, - RASPBERRY_PIN_BUSY, - ) - self.epaper.begin() - self.clear() + super().__init__(config) + + self.epaper = DFRobot_Epaper_SPI( + RASPBERRY_SPI_BUS, + RASPBERRY_SPI_DEV, + RASPBERRY_PIN_CS, + RASPBERRY_PIN_CD, + RASPBERRY_PIN_BUSY, + ) + self.epaper.begin() + self.clear() def clear(self): self.epaper.clear(self.epaper.WHITE) self.epaper.flush(self.epaper.FULL) - def update(self, im_array): + def update(self, im_array, direct_update=False): self.epaper.bitmap( 0, 0, # start X and Y @@ -56,5 +60,4 @@ def update(self, im_array): self.epaper.flush(self.epaper.PART) def quit(self): - if _SENSOR_DISPLAY: - self.clear() + self.clear() diff --git a/modules/display/display_core.py b/modules/display/display_core.py index 6746450b..48858a11 100644 --- a/modules/display/display_core.py +++ b/modules/display/display_core.py @@ -1,135 +1,111 @@ import os from logger import app_logger -from ..sensor.sensor import Sensor -DISPLAY = None +DEFAULT_RESOLUTION = (400, 240) +SUPPORTED_DISPLAYS = { + # display name, resolution if different from its class default + "None": None, # DEFAULT_RESOLUTION + "PiTFT": None, + "MIP": None, # LPM027M128C, LPM027M128B + "MIP_640": (640, 480), # LPM044M141A + "MIP_Sharp": None, + "MIP_Sharp_320": (320, 240), + "Papirus": None, + "DFRobot_RPi_Display": None, +} -class Display(Sensor): - sensor = {} - elements = () - display = None - send_display = False - def sensor_init(self): - self.detect_display() - self.set_resolution() +# default display (X window) +class Display: + has_color = True + has_touch = True + send = True - self.reset() + def __init__(self, config): + self.config = config - if not self.config.G_IS_RASPI: - self.config.G_DISPLAY = "None" - return + @property + def resolution(self): + return getattr(self, "size", DEFAULT_RESOLUTION) - if self.config.G_DISPLAY == "PiTFT": - from .pitft_28_r import PiTFT28r + def start_coroutine(self): + pass - self.display = PiTFT28r(self.config) - elif self.config.G_DISPLAY in ("MIP", "MIP_640"): - from .mip_display import MipDisplay + def quit(self): + pass - self.display = MipDisplay(self.config) - self.send_display = True - elif self.config.G_DISPLAY in ("MIP_Sharp", "MIP_Sharp_320"): - from .mip_sharp_display import MipSharpDisplay + def update(self, buf, direct_update): + pass - self.display = MipSharpDisplay(self.config) - self.send_display = True - elif self.config.G_DISPLAY == "Papirus": - from .papirus_display import PapirusDisplay + def screen_flash_long(self): + pass - self.display = PapirusDisplay(self.config) - self.send_display = True - elif self.config.G_DISPLAY == "DFRobot_RPi_Display": - from .dfrobot_rpi_display import DFRobotRPiDisplay + def screen_flash_short(self): + pass - self.display = DFRobotRPiDisplay(self.config) - self.send_display = True + def change_brightness(self): + pass - def detect_display(self): - hatdir = "/proc/device-tree/hat" - product_file = hatdir + "/product" - vendor_file = hatdir + "/vendor" - if (os.path.exists(product_file)) and (os.path.exists(vendor_file)): - with open(hatdir + "/product") as f: - p = f.read() - with open(hatdir + "/vendor") as f: - v = f.read() - app_logger.info(f"{product_file}: {p}") - app_logger.info(f"{vendor_file}: {v}") +def detect_display(): + hatdir = "/proc/device-tree/hat" + product_file = f"{hatdir}/product" + vendor_file = f"{hatdir}/vendor" + if os.path.exists(product_file) and os.path.exists(vendor_file): + with open(product_file) as f: + p = f.read() + with open(vendor_file) as f: + v = f.read() + app_logger.info(f"{product_file}: {p}") + app_logger.info(f"{vendor_file}: {v}") + # set display + if p.find("Adafruit PiTFT HAT - 2.4 inch Resistive Touch") == 0: + return "PiTFT" + elif (p.find("PaPiRus ePaper HAT") == 0) and (v.find("Pi Supply") == 0): + return "Papirus" + return None - # set display - if p.find("Adafruit PiTFT HAT - 2.4 inch Resistive Touch") == 0: - self.config.G_DISPLAY = "PiTFT" - elif (p.find("PaPiRus ePaper HAT") == 0) and (v.find("Pi Supply") == 0): - self.config.G_DISPLAY = "Papirus" - def set_resolution(self): - for key in self.config.G_AVAILABLE_DISPLAY.keys(): - if self.config.G_DISPLAY == key: - self.config.G_WIDTH = self.config.G_AVAILABLE_DISPLAY[key]["size"][0] - self.config.G_HEIGHT = self.config.G_AVAILABLE_DISPLAY[key]["size"][1] - break +def init_display(config): + # default dummy display - def has_touch(self): - return self.config.G_AVAILABLE_DISPLAY[self.config.G_DISPLAY]["touch"] + display = Display(config) - def has_color(self): - return self.config.G_AVAILABLE_DISPLAY[self.config.G_DISPLAY]["color"] + if not config.G_IS_RASPI: + config.G_DISPLAY = "None" + return display - def start_coroutine(self): - if self.config.G_DISPLAY in ("MIP", "MIP_640", "MIP_Sharp", "MIP_Sharp_320"): - self.display.start_coroutine() + auto_detect = detect_display() - def quit(self): - if not self.config.G_IS_RASPI: - return - if self.config.G_DISPLAY == "PiTFT": - pass - elif ( - self.config.G_DISPLAY in ("MIP", "MIP_640", "MIP_Sharp", "MIP_Sharp_320") - and self.send_display - ): - self.display.quit() - elif self.config.G_DISPLAY in ("Papirus", "DFRobot_RPi_Display"): - self.display.quit() + if auto_detect is not None: + config.G_DISPLAY = auto_detect - def update(self, buf, direct_update): - if not self.config.G_IS_RASPI or not self.send_display: - return - - if direct_update and self.config.G_DISPLAY in ( - "MIP", - "MIP_Sharp", - "MIP_Sharp_320", - ): - self.display.update(buf, direct_update) - elif self.config.G_DISPLAY in ("MIP", "MIP_640", "MIP_Sharp", "MIP_Sharp_320"): - self.display.update(buf, direct_update=False) - elif self.config.G_DISPLAY in ("Papirus", "DFRobot_RPi_Display"): - self.display.update(buf) + if config.G_DISPLAY == "PiTFT": + from .pitft_28_r import _SENSOR_DISPLAY, PiTFT28r - def screen_flash_long(self): - if ( - self.config.G_DISPLAY in ("MIP", "MIP_640", "MIP_Sharp", "MIP_Sharp_320") - and self.send_display - ): - self.display.inversion(0.8) - # self.display.blink(1.0) + if _SENSOR_DISPLAY: + display = PiTFT28r(config) + elif config.G_DISPLAY in ("MIP", "MIP_640"): + from .mip_display import _SENSOR_DISPLAY, MipDisplay - def screen_flash_short(self): - if ( - self.config.G_DISPLAY in ("MIP", "MIP_640", "MIP_Sharp", "MIP_Sharp_320") - and self.send_display - ): - self.display.inversion(0.3) - - def brightness_control(self): - if not self.config.G_IS_RASPI: - return - if self.config.G_DISPLAY == "PiTFT": - self.display.change_brightness() - elif self.config.G_DISPLAY in ("MIP", "MIP_640") and self.send_display: - self.display.change_brightness() + if _SENSOR_DISPLAY: + display = MipDisplay(config, SUPPORTED_DISPLAYS[config.G_DISPLAY]) + elif config.G_DISPLAY in ("MIP_Sharp", "MIP_Sharp_320"): + from .mip_sharp_display import _SENSOR_DISPLAY, MipSharpDisplay + + if _SENSOR_DISPLAY: + display = MipSharpDisplay(config, SUPPORTED_DISPLAYS[config.G_DISPLAY]) + elif config.G_DISPLAY == "Papirus": + from .papirus_display import _SENSOR_DISPLAY, PapirusDisplay + + if _SENSOR_DISPLAY: + display = PapirusDisplay(config) + elif config.G_DISPLAY == "DFRobot_RPi_Display": + from .dfrobot_rpi_display import _SENSOR_DISPLAY, DFRobotRPiDisplay + + if _SENSOR_DISPLAY: + display = DFRobotRPiDisplay(config) + + return display diff --git a/modules/display/mip_display.py b/modules/display/mip_display.py index b09f1a69..8af8c11a 100644 --- a/modules/display/mip_display.py +++ b/modules/display/mip_display.py @@ -4,6 +4,7 @@ import numpy as np from logger import app_logger +from .display_core import Display _SENSOR_DISPLAY = False MODE = "Python" @@ -42,8 +43,7 @@ GPIO_BACKLIGHT_FREQ = 64 -class MipDisplay: - config = None +class MipDisplay(Display): pi = None spi = None interval = 0.25 @@ -52,21 +52,21 @@ class MipDisplay: brightness = 0 mip_display_cpp = None - def __init__(self, config): - self.config = config + has_touch = False - if not _SENSOR_DISPLAY: - return + size = (400, 240) + + def __init__(self, config, size=None): + super().__init__(config) + + if size: + self.size = size if MODE == "Cython": self.conv_color = conv_3bit_color elif MODE == "Cython_full": - self.mip_display_cpp = MipDisplay_CPP( - self.config.G_DISPLAY_PARAM["SPI_CLOCK"] - ) - self.mip_display_cpp.set_screen_size( - self.config.G_WIDTH, self.config.G_HEIGHT - ) + self.mip_display_cpp = MipDisplay_CPP(config.G_DISPLAY_PARAM["SPI_CLOCK"]) + self.mip_display_cpp.set_screen_size(self.size[0], self.size[1]) self.update = self.mip_display_cpp.update self.set_brightness = self.mip_display_cpp.set_brightness self.inversion = self.mip_display_cpp.inversion @@ -79,7 +79,7 @@ def __init__(self, config): # spi self.pi = pigpio.pi() - self.spi = self.pi.spi_open(0, self.config.G_DISPLAY_PARAM["SPI_CLOCK"], 0) + self.spi = self.pi.spi_open(0, config.G_DISPLAY_PARAM["SPI_CLOCK"], 0) self.pi.set_mode(GPIO_DISP, pigpio.OUTPUT) self.pi.set_mode(GPIO_SCS, pigpio.OUTPUT) @@ -93,22 +93,20 @@ def __init__(self, config): # backlight self.pi.set_mode(GPIO_BACKLIGHT, pigpio.OUTPUT) self.pi.hardware_PWM(GPIO_BACKLIGHT, GPIO_BACKLIGHT_FREQ, 0) - if self.config.G_USE_AUTO_BACKLIGHT: + if config.G_USE_AUTO_BACKLIGHT: self.brightness_index = len(self.brightness_table) else: self.brightness_index = 0 def init_buffer(self): - self.buff_width = int(self.config.G_WIDTH * 3 / 8) + 2 # for 3bit update mode - self.img_buff_rgb8 = np.empty( - (self.config.G_HEIGHT, self.buff_width), dtype="uint8" - ) - self.pre_img = np.zeros((self.config.G_HEIGHT, self.buff_width), dtype="uint8") + self.buff_width = int(self.size[0] * 3 / 8) + 2 # for 3bit update mode + self.img_buff_rgb8 = np.empty((self.size[1], self.buff_width), dtype="uint8") + self.pre_img = np.zeros((self.size[1], self.buff_width), dtype="uint8") self.img_buff_rgb8[:, 0] = UPDATE_MODE - self.img_buff_rgb8[:, 1] = np.arange(self.config.G_HEIGHT) + self.img_buff_rgb8[:, 1] = np.arange(self.size[1]) # for MIP_640 self.img_buff_rgb8[:, 0] = self.img_buff_rgb8[:, 0] + ( - np.arange(self.config.G_HEIGHT) >> 8 + np.arange(self.size[1]) >> 8 ) def start_coroutine(self): @@ -131,8 +129,6 @@ def no_update(self): time.sleep(0.000006) def blink(self, sec): - if not _SENSOR_DISPLAY: - return s = sec state = True while s > 0: @@ -149,8 +145,6 @@ def blink(self, sec): self.no_update() def inversion(self, sec): - if not _SENSOR_DISPLAY: - return s = sec state = True while s > 0: @@ -185,8 +179,9 @@ async def draw_worker(self): self.draw_queue.task_done() def update(self, im_array, direct_update): - if not _SENSOR_DISPLAY or self.config.G_QUIT: - return + # direct update not yet supported for MPI_640 + if direct_update and self.config.G_DISPLAY in ("MIP_640",): + direct_update = False # self.config.check_time("mip_update start") self.img_buff_rgb8[:, 2:] = self.conv_color(im_array) @@ -196,7 +191,7 @@ def update(self, im_array, direct_update): diff_lines = np.where( np.sum((self.img_buff_rgb8 == self.pre_img), axis=1) != self.buff_width )[0] - # print("diff ", int(len(diff_lines)/self.config.G_HEIGHT*100), "%") + # print("diff ", int(len(diff_lines)/self.size[1]*100), "%") # print(" ") if not len(diff_lines): @@ -230,7 +225,7 @@ def update(self, im_array, direct_update): def conv_2bit_color_py(self, im_array): return np.packbits( - (im_array >= 128).reshape(self.config.G_HEIGHT, self.config.G_WIDTH * 3), + (im_array >= 128).reshape(self.size[1], self.size[0] * 3), axis=1, ) @@ -271,9 +266,7 @@ def conv_3bit_color_py(self, im_array): ] ] = 1 - return np.packbits( - im_array_bin.reshape(self.config.G_HEIGHT, self.config.G_WIDTH * 3), axis=1 - ) + return np.packbits(im_array_bin.reshape(self.size[1], self.size[0] * 3), axis=1) def change_brightness(self): # brightness is changing as following, @@ -290,14 +283,12 @@ def change_brightness(self): self.set_brightness(b) def set_brightness(self, b): - if not _SENSOR_DISPLAY or b == self.brightness or self.config.G_QUIT: + if b == self.brightness or self.config.G_QUIT: return self.pi.hardware_PWM(GPIO_BACKLIGHT, GPIO_BACKLIGHT_FREQ, b * 10000) self.brightness = b def backlight_blink(self): - if not _SENSOR_DISPLAY: - return for x in range(2): for pw in range(0, 100, 1): self.pi.hardware_PWM(GPIO_BACKLIGHT, GPIO_BACKLIGHT_FREQ, pw * 10000) @@ -307,9 +298,6 @@ def backlight_blink(self): time.sleep(0.05) def quit(self): - if not _SENSOR_DISPLAY: - return - asyncio.create_task(self.draw_queue.put(None)) self.set_brightness(0) self.clear() @@ -319,3 +307,9 @@ def quit(self): self.pi.spi_close(self.spi) self.pi.stop() + + def screen_flash_long(self): + return self.inversion(0.8) + + def screen_flash_short(self): + return self.inversion(0.3) diff --git a/modules/display/mip_sharp_display.py b/modules/display/mip_sharp_display.py index 87f1dcb2..1422eff7 100644 --- a/modules/display/mip_sharp_display.py +++ b/modules/display/mip_sharp_display.py @@ -4,6 +4,7 @@ import numpy as np from logger import app_logger +from .display_core import Display _SENSOR_DISPLAY = False try: @@ -27,21 +28,25 @@ UPDATE_MODE = 0x80 -class MipSharpDisplay: - config = None +class MipSharpDisplay(Display): pi = None spi = None interval = 0.25 - def __init__(self, config): - self.config = config - self.init_buffer() + has_color = False + has_touch = False - if not _SENSOR_DISPLAY: - return + size = (400, 240) + + def __init__(self, config, size=None): + super().__init__(config) + if size: + self.size = size + + self.init_buffer() self.pi = pigpio.pi() - self.spi = self.pi.spi_open(0, self.config.G_DISPLAY_PARAM["SPI_CLOCK"], 0) + self.spi = self.pi.spi_open(0, config.G_DISPLAY_PARAM["SPI_CLOCK"], 0) self.pi.set_mode(GPIO_DISP, pigpio.OUTPUT) self.pi.set_mode(GPIO_SCS, pigpio.OUTPUT) @@ -59,17 +64,13 @@ def start_coroutine(self): asyncio.create_task(self.draw_worker()) def init_buffer(self): - self.buff_width = int(self.config.G_WIDTH / 8) + 2 - self.img_buff_rgb8 = np.zeros( - (self.config.G_HEIGHT, self.buff_width), dtype="uint8" - ) - self.pre_img = np.full( - (self.config.G_HEIGHT, self.buff_width), 255, dtype="uint8" - ) + self.buff_width = int(self.size[0] / 8) + 2 + self.img_buff_rgb8 = np.zeros((self.size[1], self.buff_width), dtype="uint8") + self.pre_img = np.full((self.size[1], self.buff_width), 255, dtype="uint8") self.img_buff_rgb8[:, 0] = UPDATE_MODE # address is set in reversed bits self.img_buff_rgb8[:, 1] = [ - int("{:08b}".format(a)[::-1], 2) for a in range(self.config.G_HEIGHT) + int("{:08b}".format(a)[::-1], 2) for a in range(self.size[1]) ] def clear(self): @@ -80,8 +81,6 @@ def clear(self): time.sleep(0.000006) def inversion(self, sec): - if not _SENSOR_DISPLAY: - return s = sec state = True while s > 0: @@ -119,7 +118,7 @@ async def draw_worker(self): self.draw_queue.task_done() def update(self, im_array, direct_update): - if not _SENSOR_DISPLAY or self.config.G_QUIT: + if self.config.G_QUIT: return # self.config.check_time("mip_sharp_update start") @@ -129,11 +128,12 @@ def update(self, im_array, direct_update): diff_lines = np.where( np.sum((self.img_buff_rgb8 == self.pre_img), axis=1) != self.buff_width )[0] - # print("diff ", int(len(diff_lines)/self.config.G_HEIGHT*100), "%") + # print("diff ", int(len(diff_lines)/self.size[1]*100), "%") # print(" ") if not len(diff_lines): return + self.pre_img[diff_lines] = self.img_buff_rgb8[diff_lines] # self.config.check_time("diff_lines") @@ -157,3 +157,9 @@ def quit(self): self.pi.spi_close(self.spi) self.pi.stop() + + def screen_flash_long(self): + return self.inversion(0.8) + + def screen_flash_short(self): + return self.inversion(0.3) diff --git a/modules/display/papirus_display.py b/modules/display/papirus_display.py index a3ed8d94..6c76b279 100644 --- a/modules/display/papirus_display.py +++ b/modules/display/papirus_display.py @@ -1,6 +1,7 @@ from PIL import Image from logger import app_logger +from .display_core import Display _SENSOR_DISPLAY = False try: @@ -14,21 +15,23 @@ app_logger.info(f"PAPIRUS E-INK DISPLAY: {_SENSOR_DISPLAY}") -class PapirusDisplay: - config = None +class PapirusDisplay(Display): papirus = None - def __init__(self, config): - self.config = config + has_color = False + has_touch = False + + size = (264, 176) - if _SENSOR_DISPLAY: - self.papirus = Papirus(rotation=180) - self.clear() + def __init__(self, config): + super().__init__(config) + self.papirus = Papirus(rotation=180) + self.clear() def clear(self): self.papirus.clear() - def update(self, im_array): + def update(self, im_array, direct_update=False): self.papirus.display( Image.frombytes( "1", (im_array.shape[1] * 8, im_array.shape[0]), (~im_array).tobytes() @@ -37,5 +40,4 @@ def update(self, im_array): self.papirus.fast_update() def quit(self): - if _SENSOR_DISPLAY: - self.clear() + self.clear() diff --git a/modules/display/pitft_28_r.py b/modules/display/pitft_28_r.py index 0459b154..914bc24f 100644 --- a/modules/display/pitft_28_r.py +++ b/modules/display/pitft_28_r.py @@ -1,17 +1,13 @@ from logger import app_logger from modules.utils.cmd import exec_cmd +from .display_core import Display _SENSOR_DISPLAY = True app_logger.info(f"PiTFT 2.8(r): {_SENSOR_DISPLAY}") -# SCREEN -SCREEN_WIDTH = 320 -SCREEN_HEIGHT = 240 - -class PiTFT28r: - config = None +class PiTFT28r(Display): spi = None # brightness @@ -26,23 +22,23 @@ class PiTFT28r: # brightness_cmd_init = ["/usr/bin/gpio", "-g", "mode", "18", "pwm"] # brightness_cmd_base = ["/usr/bin/gpio", "-g", "pwm", "18"] - def __init__(self, config): - self.config = config + size = (320, 240) - if _SENSOR_DISPLAY: - self.clear() + send = False + + def __init__(self, config): + super().__init__(config) + self.clear() def clear(self): pass def quit(self): - if _SENSOR_DISPLAY: - self.clear() - # GPIO.output(GPIO_DISP, 1) - # time.sleep(0.1) + self.clear() + # GPIO.output(GPIO_DISP, 1) + # time.sleep(0.1) def change_brightness(self): - if _SENSOR_DISPLAY: - self.brightness_index = not self.brightness_index - cmd = self.brightness_cmd[int(self.brightness_index)] - exec_cmd(cmd) + self.brightness_index = not self.brightness_index + cmd = self.brightness_cmd[int(self.brightness_index)] + exec_cmd(cmd) diff --git a/modules/gui_pyqt.py b/modules/gui_pyqt.py index 308b5433..8e6c0b4c 100644 --- a/modules/gui_pyqt.py +++ b/modules/gui_pyqt.py @@ -54,12 +54,15 @@ def __init__(self, *__args): self.setAlignment(QT_ALIGN_CENTER) -class MyWindow(QtWidgets.QMainWindow): +class MainWindow(QtWidgets.QMainWindow): gui = None - def __init__(self, parent=None): + def __init__(self, title, size, parent=None): super().__init__(parent=parent) app_logger.info(f"Qt version: {QtCore.QT_VERSION_STR}") + self.setWindowTitle(title) + self.setMinimumSize(*size) + self.set_color() # TODO, daylight does not seem to be used at all, # Could/Should be replaced by setting stylesheet on init @@ -79,16 +82,13 @@ async def closeEvent(self, event): # override from QtWidget def paintEvent(self, event): - if self.gui is not None: - self.gui.draw_display() + self.gui.draw_display() class GUI_PyQt(QtCore.QObject): config = None gui_config = None - logger = None app = None - style = None stack_widget = None button_box_widget = None @@ -118,12 +118,15 @@ class GUI_PyQt(QtCore.QObject): screen_shape = None screen_image = None remove_bytes = 0 - old_pyqt = False bufsize = 0 # for dialog display_dialog = False + @property + def logger(self): + return self.config.logger + def __init__(self, config): super().__init__() @@ -132,12 +135,6 @@ def __init__(self, config): self.gui_config = GUI_Config(config.G_LAYOUT_FILE) - if config.display.has_color(): - self.image_format = QT_FORMAT_RGB888 - else: - self.image_format = QT_FORMAT_MONO - - self.logger = self.config.logger try: signal.signal(signal.SIGTERM, self.quit_by_ctrl_c) signal.signal(signal.SIGINT, self.quit_by_ctrl_c) @@ -154,12 +151,11 @@ def init_window(self): self.config.loop.set_debug(True) self.config.init_loop(call_from_gui=True) - self.main_window = MyWindow() + self.main_window = MainWindow( + self.config.G_PRODUCT, self.config.display.resolution + ) self.main_window.set_gui(self) - self.main_window.setWindowTitle(self.config.G_PRODUCT) - self.main_window.setMinimumSize(self.config.G_WIDTH, self.config.G_HEIGHT) self.main_window.show() - self.main_window.set_color() self.stack_widget = QtWidgets.QStackedWidget(self.main_window) self.main_window.setCentralWidget(self.stack_widget) @@ -178,7 +174,7 @@ def init_window(self): splash_layout.addWidget(boot_status) # for draw_display - self.init_buffer() + self.init_buffer(self.config.display) self.exec() @@ -404,7 +400,7 @@ def delay_init(self): with timers[3]: # integrate main_layout main_layout.addWidget(self.main_page) - if self.config.display.has_touch(): + if self.config.display.has_touch: from modules.pyqt.pyqt_button_box_widget import ButtonBoxWidget self.button_box_widget = ButtonBoxWidget(main_widget, self.config) @@ -419,26 +415,62 @@ def delay_init(self): app_logger.info("Drawing components:") log_timers(timers, text_total="total : {0:.3f} sec") - def get_screen_shape(self, p): - remove_bytes = 0 - if self.config.display.has_color(): - screen_shape = (p.height(), p.width(), 3) - else: - screen_shape = (p.height(), int(p.width() / 8)) - remove_bytes = p.bytesPerLine() - int(p.width() / 8) - return screen_shape, remove_bytes + def init_buffer(self, display): + if display.send: + has_color = display.has_color + + # set image format + if has_color: + self.image_format = QT_FORMAT_RGB888 + else: + self.image_format = QT_FORMAT_MONO - def init_buffer(self): - if self.config.display.send_display: p = self.stack_widget.grab().toImage().convertToFormat(self.image_format) + # PyQt 5.11(Buster) or 5.15(Bullseye) - qt_version = (QtCore.QT_VERSION_STR).split(".") + qt_version = QtCore.QT_VERSION_STR.split(".") + if qt_version[0] == "5" and int(qt_version[1]) < 15: self.bufsize = p.bytesPerLine() * p.height() # PyQt 5.11(Buster) else: self.bufsize = p.sizeInBytes() # PyQt 5.15 or later (Bullseye) - self.screen_shape, self.remove_bytes = self.get_screen_shape(p) + if has_color: + self.screen_shape = (p.height(), p.width(), 3) + else: + self.screen_shape = (p.height(), int(p.width() / 8)) + self.remove_bytes = p.bytesPerLine() - int(p.width() / 8) + + def draw_display(self, direct_update=False): + if not self.bufsize: + return + + # self.config.check_time("draw_display start") + p = self.stack_widget.grab().toImage().convertToFormat(self.image_format) + + if self.screen_image is not None and p == self.screen_image: + return + + # self.config.check_time("grab") + ptr = p.constBits() + + if ptr is None: + return + + self.screen_image = p + + ptr.setsize(self.bufsize) + + if self.remove_bytes > 0: + buf = np.frombuffer(ptr, dtype=np.uint8).reshape( + (p.height(), self.remove_bytes + int(p.width() / 8)) + ) + buf = buf[:, : -self.remove_bytes] + else: + buf = np.frombuffer(ptr, dtype=np.uint8).reshape(self.screen_shape) + + self.config.display.update(buf, direct_update) + # self.config.check_time("draw_display end") def exec(self): with self.config.loop: @@ -493,7 +525,8 @@ def reset_count(self): self.logger.reset_count() self.map_widget.reset_track() - def press_key(self, key): + @staticmethod + def press_key(key): e_press = QtGui.QKeyEvent(QT_KEY_PRESS, key, QT_NO_MODIFIER, None) e_release = QtGui.QKeyEvent(QT_KEY_RELEASE, key, QT_NO_MODIFIER, None) QtCore.QCoreApplication.postEvent(QtWidgets.QApplication.focusWidget(), e_press) @@ -603,9 +636,6 @@ def change_color_high(self): f"HIGH: {self.config.G_DITHERING_CUTOFF['HIGH'][self.config.G_DITHERING_CUTOFF_HIGH_INDEX]}" ) - def dummy(self): - pass - def scroll(self, delta): mod_index = ( self.main_page.currentIndex() + delta + self.main_page.count() @@ -623,41 +653,12 @@ def screenshot(self): p = self.stack_widget.grab() p.save(os.path.join(self.config.G_SCREENSHOT_DIR, filename), "png") - def draw_display(self, direct_update=False): - if not self.config.display.send_display or self.stack_widget is None: - return - - # self.config.check_time("draw_display start") - p = self.stack_widget.grab().toImage().convertToFormat(self.image_format) - - # self.config.check_time("grab") - ptr = p.constBits() - if ptr is None: - return - - if self.screen_image is not None and p == self.screen_image: - return - self.screen_image = p - - ptr.setsize(self.bufsize) - - if self.remove_bytes > 0: - buf = np.frombuffer(ptr, dtype=np.uint8).reshape( - (p.height(), self.remove_bytes + int(p.width() / 8)) - ) - buf = buf[:, : -self.remove_bytes] - else: - buf = np.frombuffer(ptr, dtype=np.uint8).reshape(self.screen_shape) - - self.config.display.update(buf, direct_update) - # self.config.check_time("draw_display end") - def change_start_stop_button(self, status): if self.button_box_widget is not None: self.button_box_widget.change_start_stop_button(status) def brightness_control(self): - self.config.display.brightness_control() + self.config.display.change_brightness() def turn_on_off_light(self): self.config.logger.sensor.sensor_ant.set_light_mode("ON_OFF_FLASH_LOW") @@ -669,7 +670,7 @@ def change_menu_page(self, page, focus_reset=True): if focus_widget: if focus_reset: focus_widget.setFocus() - elif self.config.display.has_touch(): + elif self.config.display.has_touch: # reset automatic focus there might not be one focus_widget = QtWidgets.QApplication.focusWidget() if focus_widget: @@ -757,10 +758,6 @@ def show_forced_message(self, msg): else: self.show_dialog_ok_only(None, msg) - def show_navi_internal(self, title, title_icon=None): - # self.show_dialog_base(title=title, title_icon=title_icon, button_num=0, position=QT_ALIGN_BOTTOM) - pass - def show_dialog(self, fn, title): asyncio.create_task( self.msg_queue.put({"fn": fn, "title": title, "button_num": 2}) diff --git a/modules/logger_core.py b/modules/logger_core.py index 6295218b..ed4a3d52 100644 --- a/modules/logger_core.py +++ b/modules/logger_core.py @@ -489,7 +489,14 @@ async def record_log(self): # update lap stats if value is not Null for k, v in value.items(): # skip when null value(np.nan) - if k not in ["heart_rate", "cadence", "speed", "power", "distance", "accumulated_power"]: + if k not in [ + "heart_rate", + "cadence", + "speed", + "power", + "distance", + "accumulated_power", + ]: continue if np.isnan(v): continue diff --git a/modules/pyqt/graph/pyqt_course_profile.py b/modules/pyqt/graph/pyqt_course_profile.py index df219988..f25ea874 100644 --- a/modules/pyqt/graph/pyqt_course_profile.py +++ b/modules/pyqt/graph/pyqt_course_profile.py @@ -30,7 +30,7 @@ def setup_ui_extra(self): # map self.layout.addWidget(self.plot, 0, 0, 3, 3) - if self.config.display.has_touch(): + if self.config.display.has_touch: # zoom self.layout.addWidget(self.buttons["zoomdown"], 0, 0) self.layout.addWidget(self.buttons["lock"], 1, 0) diff --git a/modules/pyqt/graph/pyqt_map.py b/modules/pyqt/graph/pyqt_map.py index a4bd2c4f..6c36a1c0 100644 --- a/modules/pyqt/graph/pyqt_map.py +++ b/modules/pyqt/graph/pyqt_map.py @@ -168,7 +168,7 @@ def setup_ui_extra(self): # map self.layout.addWidget(self.plot, 0, 0, 4, 3) - if self.config.display.has_touch(): + if self.config.display.has_touch: # zoom self.layout.addWidget(self.buttons["zoomdown"], 0, 0) self.layout.addWidget(self.buttons["lock"], 1, 0) diff --git a/modules/pyqt/menu/pyqt_adjust_widget.py b/modules/pyqt/menu/pyqt_adjust_widget.py index a40bf3c3..896f78b2 100644 --- a/modules/pyqt/menu/pyqt_adjust_widget.py +++ b/modules/pyqt/menu/pyqt_adjust_widget.py @@ -99,7 +99,7 @@ def setup_menu(self): set_button.clicked.connect(self.set_value) self.menu_layout.addWidget(set_button, 2, 5) - if not self.config.display.has_touch(): + if not self.config.display.has_touch: self.focus_widget = num_buttons[1] self.init_extra() diff --git a/modules/pyqt/menu/pyqt_menu_widget.py b/modules/pyqt/menu/pyqt_menu_widget.py index d2a91caf..1e6d5e63 100644 --- a/modules/pyqt/menu/pyqt_menu_widget.py +++ b/modules/pyqt/menu/pyqt_menu_widget.py @@ -110,7 +110,7 @@ def add_buttons(self, buttons): self.menu_layout.addWidget(MenuButton("dummy", "", self.config)) # set first focus - if not self.config.display.has_touch(): + if not self.config.display.has_touch: self.focus_widget = self.buttons[buttons[0][0]] def setup_menu(self): diff --git a/modules/pyqt/menu/pyqt_sensor_menu_widget.py b/modules/pyqt/menu/pyqt_sensor_menu_widget.py index 10a0f1df..47ebd397 100644 --- a/modules/pyqt/menu/pyqt_sensor_menu_widget.py +++ b/modules/pyqt/menu/pyqt_sensor_menu_widget.py @@ -50,7 +50,7 @@ def setup_menu(self): for antName in self.config.G_ANT["ORDER"]: self.buttons[antName].setText(self.get_button_state(antName)) - if not self.config.display.has_touch(): + if not self.config.display.has_touch: self.focus_widget = self.buttons[self.config.G_ANT["ORDER"][0]] def get_button_state(self, antName): diff --git a/modules/pyqt/pyqt_screen_widget.py b/modules/pyqt/pyqt_screen_widget.py index 7ac437f3..74f87234 100644 --- a/modules/pyqt/pyqt_screen_widget.py +++ b/modules/pyqt/pyqt_screen_widget.py @@ -67,7 +67,7 @@ def setup_ui(self): self.add_items() # this depends on max_height so has to be done after add_items that recalculate it - self.set_font_size(self.config.G_HEIGHT) + self.set_font_size(self.config.display.resolution[1]) # call from on_change_main_page in gui_pyqt.py def start(self): diff --git a/modules/sensor_core.py b/modules/sensor_core.py index e2ae52ef..ff7dae9d 100644 --- a/modules/sensor_core.py +++ b/modules/sensor_core.py @@ -576,10 +576,8 @@ async def integrate(self): # auto backlight if self.config.G_IS_RASPI and self.config.G_USE_AUTO_BACKLIGHT: - if ( - self.config.G_DISPLAY in ("MIP", "MIP_640") - and self.config.display.send_display - and not np.isnan(v["I2C"]["light"]) + if self.config.G_DISPLAY in ("MIP", "MIP_640") and not np.isnan( + v["I2C"]["light"] ): if v["I2C"]["light"] <= self.config.G_AUTO_BACKLIGHT_CUTOFF: self.config.display.display.set_brightness(3) diff --git a/pizero_bikecomputer.py b/pizero_bikecomputer.py index c8bff291..1c72d6ca 100755 --- a/pizero_bikecomputer.py +++ b/pizero_bikecomputer.py @@ -25,10 +25,10 @@ def main(): # display with timers[2]: - from modules.display.display_core import Display + from modules.display.display_core import init_display with timers[3]: - config.set_display(Display(config, {})) + config.set_display(init_display(config)) # minimal gui with timers[4]: From e342b289952f7a1be8d533fdde1391904c7f10f4 Mon Sep 17 00:00:00 2001 From: hishizuka <12926652+hishizuka@users.noreply.github.com> Date: Tue, 31 Oct 2023 07:12:57 +0900 Subject: [PATCH 2/4] Update sensor_core.py --- modules/sensor_core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sensor_core.py b/modules/sensor_core.py index ff7dae9d..417e4688 100644 --- a/modules/sensor_core.py +++ b/modules/sensor_core.py @@ -580,9 +580,9 @@ async def integrate(self): v["I2C"]["light"] ): if v["I2C"]["light"] <= self.config.G_AUTO_BACKLIGHT_CUTOFF: - self.config.display.display.set_brightness(3) + self.config.display.set_brightness(3) else: - self.config.display.display.set_brightness(0) + self.config.display.set_brightness(0) if self.config.G_MANUAL_STATUS == "START": if v["I2C"]["light"] <= self.config.G_AUTO_BACKLIGHT_CUTOFF: self.sensor_ant.set_light_mode("FLASH_LOW", auto=True) From a4a1ceb314fb82620dcba31977ab1184d0b535fd Mon Sep 17 00:00:00 2001 From: hishizuka <12926652+hishizuka@users.noreply.github.com> Date: Tue, 31 Oct 2023 07:29:01 +0900 Subject: [PATCH 3/4] Update config.py Same order of execution as the official code. >>> loop = QEventLoop(app) >>> asyncio.set_event_loop(loop) --- modules/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/config.py b/modules/config.py index 8b4d7832..d5f1f726 100644 --- a/modules/config.py +++ b/modules/config.py @@ -741,9 +741,9 @@ def __init__(self): def init_loop(self, call_from_gui=False): if self.G_GUI_MODE == "PyQt": if call_from_gui: - # asyncio.set_event_loop(self.loop) # workaround for latest qasync and older version(~0.24.0) asyncio.events._set_running_loop(self.loop) + asyncio.set_event_loop(self.loop) self.start_coroutine() else: self.loop = asyncio.get_event_loop() From 0b07fa536d8c1afa615e1cca95ebb5897f231230 Mon Sep 17 00:00:00 2001 From: Ptosiek <16878205+Ptosiek@users.noreply.github.com> Date: Tue, 31 Oct 2023 09:35:37 +0100 Subject: [PATCH 4/4] no need to run init_buffer for x window --- modules/display/dfrobot_rpi_display.py | 1 + modules/display/display_core.py | 2 +- modules/display/mip_display.py | 1 + modules/display/mip_sharp_display.py | 1 + modules/display/papirus_display.py | 1 + modules/display/pitft_28_r.py | 2 -- 6 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/display/dfrobot_rpi_display.py b/modules/display/dfrobot_rpi_display.py index 4c8babe6..e97577c4 100644 --- a/modules/display/dfrobot_rpi_display.py +++ b/modules/display/dfrobot_rpi_display.py @@ -27,6 +27,7 @@ class DFRobotRPiDisplay(Display): has_color = False has_touch = False + send = True size = (250, 122) diff --git a/modules/display/display_core.py b/modules/display/display_core.py index 48858a11..67b2b9b7 100644 --- a/modules/display/display_core.py +++ b/modules/display/display_core.py @@ -21,7 +21,7 @@ class Display: has_color = True has_touch = True - send = True + send = False def __init__(self, config): self.config = config diff --git a/modules/display/mip_display.py b/modules/display/mip_display.py index 8af8c11a..920d476f 100644 --- a/modules/display/mip_display.py +++ b/modules/display/mip_display.py @@ -53,6 +53,7 @@ class MipDisplay(Display): mip_display_cpp = None has_touch = False + send = True size = (400, 240) diff --git a/modules/display/mip_sharp_display.py b/modules/display/mip_sharp_display.py index 1422eff7..c0f04676 100644 --- a/modules/display/mip_sharp_display.py +++ b/modules/display/mip_sharp_display.py @@ -35,6 +35,7 @@ class MipSharpDisplay(Display): has_color = False has_touch = False + send = True size = (400, 240) diff --git a/modules/display/papirus_display.py b/modules/display/papirus_display.py index 6c76b279..1cb6c9d8 100644 --- a/modules/display/papirus_display.py +++ b/modules/display/papirus_display.py @@ -20,6 +20,7 @@ class PapirusDisplay(Display): has_color = False has_touch = False + send = True size = (264, 176) diff --git a/modules/display/pitft_28_r.py b/modules/display/pitft_28_r.py index 914bc24f..af879e19 100644 --- a/modules/display/pitft_28_r.py +++ b/modules/display/pitft_28_r.py @@ -24,8 +24,6 @@ class PiTFT28r(Display): size = (320, 240) - send = False - def __init__(self, config): super().__init__(config) self.clear()