Python 3/PyQt5 + picamera2 on Raspberry Pi, list available cameras.

In previous exercises of Python/PyQt5 GUI to control Raspberry Pi Camera using picamera2 lib, with camera_controls and White Balance, camera is assigned manually in Python code or command line argument. In this exercise (picam2_qt5_global.py), number of cameras attached and cameras info are retrieved by calling Picamera2.global_camera_info(), then run picam2_qt5_.py base on user selection.


Code:
picam2_qt5_global.py
"""
Python 3/PyQt5 + picamera2
List available cameras.
Run picam2_qt5_.py base on user selection.

"""
from PyQt5.QtWidgets import (QComboBox, QMainWindow, QApplication, QWidget, QVBoxLayout,
                             QLabel, QLineEdit, QPushButton)
from PyQt5.QtCore import QProcess
import sys
from picamera2 import Picamera2

cameras_info = Picamera2.global_camera_info()

num_of_cam = len(cameras_info)

if num_of_cam == 0:
    print("No camera attached! Exit")
    exit(1)

print("number of cameras: ", num_of_cam)

for i in range(len(cameras_info)):
    print(i)
    for keys, values in cameras_info[i].items():
        print("\t", keys, ":", values)
    
class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        
        self.combobox = QComboBox()
        for idx in range(len(cameras_info)):
            textCam = "Picamera(" + str(cameras_info[idx]["Num"]) + ") : " + cameras_info[idx]["Model"]
            self.combobox.addItem(textCam)
            
        print("self.combobox.currentIndex():", self.combobox.currentIndex())
        

        # Connect signals to the methods.
        self.combobox.activated.connect(self.activated)
        self.combobox.currentTextChanged.connect(self.text_changed)
        self.combobox.currentIndexChanged.connect(self.index_changed)
        
        labelCmd=QLabel("Cmmand:")
        
        self.lineCmd = QLabel()
        self.lineCmd.setText("python3 picam2_qt5_.py " +
                             str(cameras_info[self.combobox.currentIndex()]["Num"]))
        
        btnRun = QPushButton("Run")
        btnRun.clicked.connect(self.on_Run_Clicked)

        urlLink="<a href=\"https://coxxect.blogspot.com/\">coxxect.blogspot.com</a>" 
        labelLink=QLabel()
        labelLink.setText(urlLink)
        labelLink.setOpenExternalLinks(True)

        #
        layout = QVBoxLayout()
        layout.addWidget(self.combobox)
        layout.addWidget(labelCmd)
        layout.addWidget(self.lineCmd)
        layout.addWidget(btnRun)
        layout.addStretch()
        layout.addWidget(labelLink)

        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)

    def activated(Self, index):
        print("Activated index:", index)

    def text_changed(self, s):
        print("Text changed:", s)
        self.lineCmd.setText("python3 picam2_qt5_.py " +
                             str(cameras_info[self.combobox.currentIndex()]["Num"]))

    def index_changed(self, index):
        print("Index changed", index)
        
    def on_Run_Clicked(self):
        print("on_Run_Clicked")
        self.p = QProcess()
        self.p.start("python3", ['picam2_qt5_.py',
                                 str(cameras_info[self.combobox.currentIndex()]["Num"])])

app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

picam2_qt5_.py
"""
Python 3/PyQt5 + picamera2 to control Raspberry Pi Camera Modules
Tested on Raspberry Pi 5/64-bit Raspberry Pi OS (bookworm)
# in my setup:
# Picamera2(0) - Camera Module 3 NoIR Wide
# Picamera2(1) - Camera Module 3

picam2_qt5_2023-12-28.py first exercise
picam2_qt5_2024-01-03.py Added Auto-Focus feature detection, and switch AF Mode between Continuous & Manual
picam2_qt5_2024-01-07.py Display Preview in seperated window, both Main/Preview windows have Capture button.
picam2_qt5_2024-01-13.py Add camera_controls to adjust brightness at runtime.
                         Handle sys.argv, such that user can select cam at command line.
picam2_qt5_2024-01-15.py Add camera_controls: brightness, Contrast, ExposureValue, Saturation & Sharpness.
picam2_qt5_2024-01-20.py Add White Balance
"""
import sys, platform, os
from PyQt5.QtWidgets import (QMainWindow, QApplication, QPushButton, QLabel, QCheckBox,
                             QWidget, QTabWidget, QHBoxLayout, QVBoxLayout, QGridLayout,
                             QGroupBox, QSlider, QButtonGroup)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont

from picamera2 import Picamera2
from picamera2.previews.qt import QGlPicamera2
from picamera2 import __name__ as picamera2_name
from libcamera import controls

import time
from importlib.metadata import version

os.environ["LIBCAMERA_LOG_LEVELS"] = "3"

"""
Because my Camera Module 3 (with Auto-Focus) is assigned to Picamera2(1),
so I make 1 as default

command line usage:
python picam2_qt5_xxx.py    # using default cam
python picam2_qt5_xxx.py 0  # using cam 0
python picam2_qt5_xxx.py 1  # using cam 1
"""

DEFAULT_CAM_NUM = 1
cam_num = DEFAULT_CAM_NUM # default Picamera2(DEFAULT_CAM_NUM)

if len(sys.argv)==2:
    if sys.argv[1] == "1":
        cam_num = 1
    if sys.argv[1] == "0":
        cam_num = 0

print("picam2 = Picamera2("+str(cam_num)+")")
picam2 = Picamera2(cam_num)
#picam2 = Picamera2()   #default to Picamera2(0) without parameter passed
#picam2 = Picamera2(1)
#=====================================
preview_width= 800
preview_height = int(picam2.sensor_resolution[1] * preview_width/picam2.sensor_resolution[0])
preview_config_raw = picam2.create_preview_configuration(main={"size": (preview_width, preview_height)},
                                                         raw={"size": picam2.sensor_resolution})
picam2.configure(preview_config_raw)
#=====================================
#Detect if AF function is available
AF_Function = True
AF_Enable = True
try:
    picam2.set_controls({"AfMode": controls.AfModeEnum.Continuous})
    print("Auto-Focus Function Enabled")
except RuntimeError as err:
    print("RuntimeError:", err)
    AF_Function = False
#=====================================
class App(QMainWindow):

    def __init__(self):
        super().__init__()
        self.title = sys.argv[0]  #__file__
        self.left = 0
        self.top = 0
        self.setWindowTitle(self.title)

        self.main_widget = MyMainWidget(self)
        self.setCentralWidget(self.main_widget)
        self.show()
        
# factors to convert control's values in float to int for PyQt5 Widgets
FACTOR_Brighness = 10
FACTOR_1  = 1
FACTOR_10 = 10
class MyMainWidget(QWidget):

    #--- MyPreviewWidget ---
    #inner class for Preview Window
    class MyPreviewWidget(QWidget):
        
        def __init__(self, subLayout):
            super(QWidget, self).__init__()
            self.setLayout(subLayout)
    #--- End of MyPreviewWidget ---
    
    # --- SliderSetting ---
    #inner class for picam2.controls of float with slider setting
    class SliderSetting:

        def on_Setting_valueChanged(self):
            value = float(self.sliderSetting.value())/self.factor
            self.labelSetting.setText(self.setting_name + ": " + str(value))

        def on_Setting_sliderReleased(self):
            valueSetting = float(self.sliderSetting.value())/self.factor
            self.callback(self.setting_name, valueSetting)

        def __init__(self, setting_name, factor, callback):
            self.setting_name = setting_name
            self.factor = factor
            self.callback = callback
            
            cam_controls = picam2.camera_controls
            
            self.sliderSetting = QSlider(Qt.Horizontal)
            self.sliderSetting.setMinimum(int(cam_controls[self.setting_name][0]*self.factor))
            self.sliderSetting.setMaximum(int(cam_controls[self.setting_name][1]*self.factor))
            value = cam_controls[self.setting_name][2]
            self.sliderSetting.setValue(int(value*self.factor))
            self.sliderSetting.setTickPosition(QSlider.TicksBelow)
            self.sliderSetting.setTickInterval(1)
            self.sliderSetting.sliderReleased.connect(self.on_Setting_sliderReleased)
            self.sliderSetting.valueChanged.connect(self.on_Setting_valueChanged)
        
            self.labelSetting = QLabel(self.setting_name + ": " + str(value))
    #--- End of SliderSetting ---
            
    # --- ColourGains (red & blue) ---
    #inner class for picam2.controls of float with slider setting for ColourGains (red & blue)
    class ColourGains_panel:

        def on_ColourGains_valueChanged(self):
            print("ColourGains_panel > on_ColourGains_valueChanged")
            self.labelColourGains_red.setText("ColourGains (red):  " + str(self.sliderColourGains_red.value()))
            self.labelColourGains_blue.setText("ColourGains (blue): " + str(self.sliderColourGains_blue.value()))

        def on_ColourGains_sliderReleased(self):
            print("ColourGains_panel > on_ColourGains_sliderReleased")
            newRed = self.sliderColourGains_red.value()
            newBlue = self.sliderColourGains_blue.value()
            self.labelColourGains_red.setText("ColourGains (red):  " + str(newRed))
            self.labelColourGains_blue.setText("ColourGains (blue): " + str(newBlue))
            
            with picam2.controls as cam_controls:
                cam_controls.ColourGains = (float(newRed), float(newBlue))
                
            self.parent.cbAwbEnable.setChecked(False)
            

        def __init__(self, parent):
            self.parent = parent
            
            cam_controls = picam2.camera_controls
            
            ColourGains = cam_controls["ColourGains"]
            
            print("===> ColourGains = ", ColourGains)
            
            self.sliderColourGains_red = QSlider(Qt.Horizontal)
            self.sliderColourGains_red.setMinimum(0)
            self.sliderColourGains_red.setMaximum(32)
            self.sliderColourGains_red.setValue(0)
            self.sliderColourGains_red.setTickPosition(QSlider.TicksBelow)
            self.sliderColourGains_red.setTickInterval(1)
            self.sliderColourGains_red.sliderReleased.connect(self.on_ColourGains_sliderReleased)
            self.sliderColourGains_red.valueChanged.connect(self.on_ColourGains_valueChanged)
            
            self.labelColourGains_red = QLabel()
            self.labelColourGains_red = QLabel("ColourGains (red):  " + str(0))
            
            self.sliderColourGains_blue = QSlider(Qt.Horizontal)
            self.sliderColourGains_blue.setMinimum(0)
            self.sliderColourGains_blue.setMaximum(32)
            self.sliderColourGains_blue.setValue(0)
            self.sliderColourGains_blue.setTickPosition(QSlider.TicksBelow)
            self.sliderColourGains_blue.setTickInterval(1)
            self.sliderColourGains_blue.sliderReleased.connect(self.on_ColourGains_sliderReleased)
            self.sliderColourGains_blue.valueChanged.connect(self.on_ColourGains_valueChanged)
        
            self.labelColourGains_blue = QLabel()
            self.labelColourGains_blue = QLabel("ColourGains (blue): " + str(0))
        
            self.layout = QVBoxLayout()
            self.layout.addWidget(self.labelColourGains_red)
            self.layout.addWidget(self.sliderColourGains_red)
            self.layout.addWidget(self.labelColourGains_blue)
            self.layout.addWidget(self.sliderColourGains_blue)
            
            WB_notice = "\n*** Actually I'm not sure about the setting of Awb and ColourGains."
            self.labelWB_notice = QLabel(WB_notice)
            self.layout.addWidget(self.labelWB_notice)
            
            
    #--- End of ColourGains (red & blue) ---
    
    def read_f(self, file):
        with open(file, encoding='UTF-8') as reader:
            content = reader.read()
        return content
    
    def read_pretty_name(self):
        with open("/etc/os-release", encoding='UTF-8') as f:
            os_release = {}
            for line in f:
                k,v = line.rstrip().split("=")
                os_release[k] = v.strip('"')
        return os_release['PRETTY_NAME']

    def AF_Enable_CheckBox_onStateChanged(self):
        with picam2.controls as cam_controls:
            if self.AF_Enable_CheckBox.isChecked():
                cam_controls.AfMode = controls.AfModeEnum.Continuous
            else:
                cam_controls.AfMode = controls.AfModeEnum.Manual
        
    def on_Capture_Clicked(self):
        # There are two buttons on Main/Child Window connected here,
        # identify the sender for info only, no actual use.
        sender = self.sender()
        if sender is self.btnCapture:
            print("Capture button on Main Window clicked")
        if sender is self.btnChildCapture:
            print("Capture button on Child Preview Window clicked")

        self.btnCapture.setEnabled(False)
        
        cfg = picam2.create_still_configuration()
        
        timeStamp = time.strftime("%Y%m%d-%H%M%S")
        targetPath="/home/pi/Desktop/img" + str(cam_num) + "_"+timeStamp+".jpg"
        print("- Capture image:", targetPath)
        
        picam2.switch_mode_and_capture_file(cfg, targetPath, signal_function=self.qpicamera2.signal_done)

    def capture_done(self, job):
        result = picam2.wait(job)
        self.btnCapture.setEnabled(True)
        print("- capture_done.")
        print(result)
        
    def cbAwbEnable_stateChanged(self):
        print("cbAwbEnable_stateChanged")
        with picam2.controls as cam_controls:
            cam_controls.AwbEnable = self.cbAwbEnable.isChecked()
        
    def bg_WB_option_clicked(self, bg):
        
        match bg:
            case self.cbWB_Auto:
                awb = controls.AwbModeEnum.Auto
            case self.cbWB_Tungsten:
                awb = controls.AwbModeEnum.Tungsten
            case self.cbWB_Fluorescent:
                awb = controls.AwbModeEnum.Fluorescent
            case self.cbWB_Indoor:
                awb = controls.AwbModeEnum.Indoor
            case self.cbWB_Daylight:
                awb = controls.AwbModeEnum.Daylight
            case self.cbWB_Cloudy:
                awb = controls.AwbModeEnum.Cloudy
            case self.cbWB_Custom:
                awb = controls.AwbModeEnum.Custom
            case _:
                print("unknown")
                
        with picam2.controls as cam_controls:
            cam_controls.AwbMode = awb
            print(cam_controls.AwbMode)
    
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)
        
        #--- Prepare child Preview Window ----------
        self.childPreviewLayout = QVBoxLayout()
        
        # Check Auto-Focus feature
        if AF_Function:
            self.AF_Enable_CheckBox = QCheckBox("Auto-Focus (Continuous)")
            self.AF_Enable_CheckBox.setChecked(True)
            self.AF_Enable_CheckBox.setEnabled(True)
            self.AF_Enable_CheckBox.stateChanged.connect(self.AF_Enable_CheckBox_onStateChanged)
            self.childPreviewLayout.addWidget(self.AF_Enable_CheckBox)
            print("show Auto-Focus Mode Change QCheckBox")
        else:
            self.AF_Enable_CheckBox = QCheckBox("No Auto-Focus function")
            self.AF_Enable_CheckBox.setChecked(False)
            self.AF_Enable_CheckBox.setEnabled(False)
            print("No Auto-Focus Mode Change QCheckBox")
            
        # Preview qpicamera2
        self.qpicamera2 = QGlPicamera2(picam2,
                          width=preview_width, height=preview_height,
                          keep_ar=True)
        self.qpicamera2.done_signal.connect(self.capture_done)
        
        self.childPreviewLayout.addWidget(self.qpicamera2)
        
        # Capture button on Child Window
        self.btnChildCapture = QPushButton("Capture Image" + str(cam_num))
        self.btnChildCapture.setFont(QFont("Helvetica", 13, QFont.Bold))
        self.btnChildCapture.clicked.connect(self.on_Capture_Clicked)
        
        self.childPreviewLayout.addWidget(self.btnChildCapture)
        
        # pass layout to child Preview Window
        self.myPreviewWindow = self.MyPreviewWidget(self.childPreviewLayout)
        # roughly set Preview windows size according to preview_width x preview_height
        self.myPreviewWindow.setGeometry(10, 10, preview_width+10, preview_height+100)
        self.myPreviewWindow.setWindowTitle("Preview size (" + str(cam_num) + ") - " +
                                            str(preview_width) + " x " + str(preview_height))
        self.myPreviewWindow.show()
        #--- End of Prepare child Preview Window ---

        self.layout = QVBoxLayout()

        # Initialize tab screen
        self.tabs = QTabWidget()
        self.tabControl = QWidget()
        self.tabWB = QWidget()
        self.tabInfo = QWidget()

        # Add tabs
        self.tabs.addTab(self.tabControl, "   Control   ")
        self.tabs.addTab(self.tabWB, "White Balance")
        self.tabs.addTab(self.tabInfo, "    Info     ")
        
        #=== Tab Capture ===
        # Create first tab
        self.tabControl.layout = QVBoxLayout()
        
        self.btnCapture = QPushButton("Capture Image " + str(cam_num))
        self.btnCapture.setFont(QFont("Helvetica", 15, QFont.Bold))
        self.btnCapture.clicked.connect(self.on_Capture_Clicked)
        
        self.tabControl.layout.addWidget(self.btnCapture)
        
        # Prepre camera_controls
        cam_controls = picam2.camera_controls

        # gboxCamControls: QGroupBox to hold all controls
        self.gboxCamControls = QGroupBox()
        self.gboxCamControls.setTitle("picam2.camera_controls")
        self.vboxCamControls = QVBoxLayout()
        self.gboxCamControls.setLayout(self.vboxCamControls)
        self.tabControl.layout.addWidget(self.gboxCamControls)

        # - More Control Setting -
        
        #Brightness
        def callback_Brightness(setting_name, value):
            with picam2.controls as cam_controls:
                cam_controls.Brightness = value
                print(str(value), "=>", setting_name, "=", str(cam_controls.Brightness))
        self.sliderBrightness = self.SliderSetting("Brightness", 10, callback_Brightness)
        self.vboxCamControls.addWidget(self.sliderBrightness.labelSetting)
        self.vboxCamControls.addWidget(self.sliderBrightness.sliderSetting)
        
        #Contrast
        def callback_Contrast(setting_name, value):
            with picam2.controls as cam_controls:
                cam_controls.Contrast = value
                print(str(value), "=>", setting_name, "=", str(cam_controls.Contrast))
        self.sliderContrast = self.SliderSetting("Contrast", 1, callback_Contrast)
        self.vboxCamControls.addWidget(self.sliderContrast.labelSetting)
        self.vboxCamControls.addWidget(self.sliderContrast.sliderSetting)
        
        #ExposureValue
        def callback_ExposureValue(setting_name, value):
            with picam2.controls as cam_controls:
                cam_controls.ExposureValue = value
                print(str(value), "=>", setting_name, "=", str(cam_controls.ExposureValue))
        self.sliderExposureValue = self.SliderSetting("ExposureValue", 1, callback_ExposureValue)
        self.vboxCamControls.addWidget(self.sliderExposureValue.labelSetting)
        self.vboxCamControls.addWidget(self.sliderExposureValue.sliderSetting)

        #Saturation
        def callback_Saturation(setting_name, value):
            with picam2.controls as cam_controls:
                cam_controls.Saturation = value
                print(str(value), "=>", setting_name, "=", str(cam_controls.Saturation))
        self.sliderSaturation = self.SliderSetting("Saturation", 1, callback_Saturation)
        self.vboxCamControls.addWidget(self.sliderSaturation.labelSetting)
        self.vboxCamControls.addWidget(self.sliderSaturation.sliderSetting)
        
        #Sharpness
        def callback_Sharpness(setting_name, value):
            with picam2.controls as cam_controls:
                cam_controls.Sharpness = value
                print(str(value), "=>", setting_name, "=", str(cam_controls.Sharpness))
        self.sliderSharpness = self.SliderSetting("Sharpness", 1, callback_Sharpness)
        self.vboxCamControls.addWidget(self.sliderSharpness.labelSetting)
        self.vboxCamControls.addWidget(self.sliderSharpness.sliderSetting)
        
        # - End of Control Setting -
        
        self.labelMore = QLabel("...more controls will be placed here in coming exercises.")
        self.vboxCamControls.addWidget(self.labelMore)
        # End of Prepre camera_controls

        self.tabControl.layout.addStretch()
        
        self.tabControl.setLayout(self.tabControl.layout)
        #=== Tab WhiteBalance===
        self.cbAwbEnable = QCheckBox("AwbEnable")
        self.cbAwbEnable.stateChanged.connect(self.cbAwbEnable_stateChanged)
        
        self.tabWB_option_layout = QHBoxLayout()
        self.cbWB_Auto = QCheckBox("Auto")
        self.cbWB_Auto.setChecked(True)
        self.cbWB_Tungsten = QCheckBox("Tungsten")
        self.cbWB_Fluorescent = QCheckBox("Fluorescent")
        self.cbWB_Indoor = QCheckBox("Indoor")
        self.cbWB_Daylight = QCheckBox("Daylight")
        self.cbWB_Cloudy = QCheckBox("Cloudy")
        self.cbWB_Custom = QCheckBox("Custom")
        
        self.bg_WB_option = QButtonGroup()
        self.bg_WB_option.addButton(self.cbWB_Auto, 1)
        self.bg_WB_option.addButton(self.cbWB_Tungsten, 2)        
        self.bg_WB_option.addButton(self.cbWB_Fluorescent, 3)
        self.bg_WB_option.addButton(self.cbWB_Indoor, 4)
        self.bg_WB_option.addButton(self.cbWB_Daylight, 5)
        self.bg_WB_option.addButton(self.cbWB_Cloudy, 6)
        self.bg_WB_option.addButton(self.cbWB_Custom, 7)
        self.bg_WB_option.buttonClicked.connect(self.bg_WB_option_clicked)
        
        self.tabWB_option_layout.addWidget(self.cbWB_Auto)
        self.tabWB_option_layout.addWidget(self.cbWB_Tungsten)
        self.tabWB_option_layout.addWidget(self.cbWB_Fluorescent)
        self.tabWB_option_layout.addWidget(self.cbWB_Indoor)
        self.tabWB_option_layout.addWidget(self.cbWB_Daylight)
        self.tabWB_option_layout.addWidget(self.cbWB_Cloudy)
        self.tabWB_option_layout.addWidget(self.cbWB_Custom)
        
        self.labelColourGains_red = QLabel("ColourGains: red gain")
        self.labelColourGains_blue = QLabel("ColourGains: blue gain")


        self.tabWB.layout = QVBoxLayout()
        self.tabWB.layout.addWidget(self.cbAwbEnable)
        self.tabWB.layout.addLayout(self.tabWB_option_layout)
        self.tabWB.layout.addWidget(self.labelColourGains_red)
        self.tabWB.layout.addWidget(self.labelColourGains_blue)
        
        self.panelColourGains_panel = self.ColourGains_panel(self)
        self.tabWB.layout.addLayout(self.panelColourGains_panel.layout)
        
        self.tabWB.layout.addStretch()
        self.tabWB.setLayout(self.tabWB.layout)
        
        #=== Tab Info ===
        self.tabInfo.layout = QVBoxLayout()
        
        infoGridLayout = QGridLayout()
        
        rowSpan = 1
        columnSpan0 = 1
        columnSpan1 = 5
        infoGridLayout.addWidget(QLabel('Python', self), 0, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel(platform.python_version(), self), 0, 1, rowSpan, columnSpan1)
        
        infoGridLayout.addWidget(QLabel(picamera2_name, self), 1, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel(version(picamera2_name), self), 1, 1, rowSpan, columnSpan1)
        
        infoGridLayout.addWidget(QLabel(' ', self), 2, 0, rowSpan, columnSpan0)        
        infoGridLayout.addWidget(QLabel('Camera Module:', self), 3, 0, rowSpan, columnSpan0)
        
        cam_properties = picam2.camera_properties
        cam_Model = cam_properties['Model']
        infoGridLayout.addWidget(QLabel('Model', self), 4, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel(cam_Model, self), 4, 1, rowSpan, columnSpan1)
        cam_PixelArraySize = str(cam_properties['PixelArraySize'][0]) + " x " + str(cam_properties['PixelArraySize'][1])
        infoGridLayout.addWidget(QLabel('PixelArraySize', self), 5, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel(cam_PixelArraySize, self), 5, 1, rowSpan, columnSpan1)
        
        infoGridLayout.addWidget(QLabel(' ', self), 6, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel('Machine:', self), 7, 0, rowSpan, columnSpan0)
        infoGridLayout.addWidget(QLabel('Board', self), 8, 0, rowSpan, columnSpan0, Qt.AlignTop)
        board_def = "/proc/device-tree/model"
        board_info = self.read_f("/proc/device-tree/model") +"\n(" + board_def +")"
        infoGridLayout.addWidget(QLabel(board_info, self), 8, 1, rowSpan, columnSpan0)
        
        infoGridLayout.addWidget(QLabel('OS', self), 9, 0, rowSpan, columnSpan0, Qt.AlignTop)
        
        os_info = self.read_pretty_name() + "\n" + os.uname()[3] +"\n" + os.uname()[4] \
                  + (" (64-bit)" if sys.maxsize > 2**32 else " (32-bit)")
        infoGridLayout.addWidget(QLabel(os_info, self), 9, 1, rowSpan, columnSpan1)
        
        self.tabInfo.layout.addLayout(infoGridLayout)
        self.tabInfo.layout.addStretch()
        
        self.tabInfo.setLayout(self.tabInfo.layout)
        
        #==================================
        # Add tabs to widget
        self.layout.addWidget(self.tabs)
        self.setLayout(self.layout)
        
        picam2.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

Comments

Popular posts from this blog

MicroPython/ESP32-C3 + 1.8" 128x160 TFT ST7735 SPI, using boochow/MicroPython-ST7735 library.

CameraWebServe: ESP32-S3 (arduino-esp32) + OV5640 camera module