-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcamera.py
More file actions
162 lines (135 loc) · 5.91 KB
/
Copy pathcamera.py
File metadata and controls
162 lines (135 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import os
import sys
import signal
import threading
# ---------------------------------------------------------------------------
# Qt platform plugin — must be set before any Qt import.
#
# On Pi OS Lite (no X11 / Wayland desktop), Qt's xcb plugin can't connect
# to a display server and eglfs_kms fails on the Pi 5 GPU stack.
# 'linuxfb' drives the framebuffer directly and works reliably on Pi OS Lite.
# Users can override by setting QT_QPA_PLATFORM in the environment beforehand.
if 'QT_QPA_PLATFORM' not in os.environ:
os.environ['QT_QPA_PLATFORM'] = 'linuxfb'
# Enable evdev touch input on linuxfb (no desktop display server to handle it).
# Qt will auto-detect the touch device; override QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS
# in the environment if you need to pin a specific device path, e.g.:
# export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event0
os.environ.setdefault('QT_QPA_GENERIC_PLUGINS', 'evdevtouch')
# Also suppress noisy libcamera log output.
os.environ.setdefault('LIBCAMERA_LOG_LEVELS', '3')
# ---------------------------------------------------------------------------
# Hide the VT console cursor and blank the terminal so it doesn't bleed
# through the framebuffer while Qt is initialising. Restored on exit.
try:
with open('/sys/class/graphics/fbcon/cursor_blink', 'w') as _f:
_f.write('0') # disable fbcon hardware cursor blink
except Exception:
pass
try:
with open('/dev/tty1', 'wb') as _tty:
_tty.write(b'\033[?25l') # hide cursor
_tty.write(b'\033[2J') # clear screen
_tty.write(b'\033[H') # move to home position
except Exception:
pass
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
# QApplication must be the very first Qt object created — before any import
# that touches picamera2's Qt previews, qtawesome, or any QWidget/QColor/QFont.
_app = QApplication(sys.argv)
# ---------------------------------------------------------------------------
# Signal handling
#
# Qt's C++ event loop blocks Python's bytecode evaluator, so SIGINT (Ctrl+C)
# is received by the OS but KeyboardInterrupt is never raised — the signal
# just disappears. Fix: a 200 ms null-timer lets Python regain the GIL
# briefly every tick so it can dispatch any pending signals.
_signal_timer = QTimer()
_signal_timer.start(200)
_signal_timer.timeout.connect(lambda: None)
def _request_quit(signum=None, frame=None):
"""Ask Qt to exit its event loop cleanly."""
_app.quit()
signal.signal(signal.SIGINT, _request_quit)
signal.signal(signal.SIGTERM, _request_quit)
# ---------------------------------------------------------------------------
from functions import Echo, Console
from controls.remote import Remote
from controls.touchscreen import Touchscreen
import globals
version = '2026.04.24'
# ---------------------------------------------------------------------------
globals.initialize()
console = Console()
echo = Echo()
# Detect autofocus capability by inspecting available controls.
# Works before the camera is started — no set_controls() needed here.
if 'AfMode' in globals.primary.module.camera_controls:
globals.primary.hasAutofocus = True
console.info('Camera 1 supports autofocus.')
else:
console.info('Camera 1 does not support autofocus.')
if globals.cameras.count > 1 and globals.secondary is not None:
if 'AfMode' in globals.secondary.module.camera_controls:
globals.secondary.hasAutofocus = True
console.info('Camera 2 supports autofocus.')
else:
console.info('Camera 2 does not support autofocus.')
# ---------------------------------------------------------------------------
# Cleanup — runs when QApplication.quit() is called (before event loop exits).
def _stop_unit(unit):
"""Stop a single camera unit — run on a thread so it can be abandoned."""
try:
if unit.isRecording:
unit.module.stop_recording()
unit.module.stop()
except Exception:
pass
def _on_quit():
"""Stop camera hardware so it doesn't hold resources after exit.
Each camera is stopped on its own thread and we wait at most 5 s total
so a hung picamera2.stop() never delays systemd's shutdown sequence.
"""
threads = []
for unit_name in ('primary', 'secondary'):
unit = getattr(globals, unit_name, None)
if unit is None:
continue
t = threading.Thread(target=_stop_unit, args=(unit,), daemon=True)
t.start()
threads.append(t)
for t in threads:
t.join(timeout=5)
_app.aboutToQuit.connect(_on_quit)
# ---------------------------------------------------------------------------
# Remote control runs in a daemon thread so it is killed automatically when
# the main thread exits. If it ran after Touchscreen() it would block forever
# because Remote.__init__ contains an evdev read_loop().
threading.Thread(target=Remote, daemon=True, name='remote').start()
# Camera configure + start is deferred to CameraWindow.__init__ so that
# QPicamera2 registers its frame callback before frames start arriving.
# This call blocks until QApplication.quit() is invoked (e.g. Ctrl+C).
touchscreen = Touchscreen()
# ---------------------------------------------------------------------------
# Post-exit framebuffer blank
#
# linuxfb leaves the last rendered frame painted on the display after the
# process exits — the image just sits there, frozen. Writing zeros to
# /dev/fb0 blanks the screen so it doesn't look like the app is still running.
try:
with open('/dev/fb0', 'wb') as _fb:
_fb.write(b'\x00' * (800 * 480 * 4))
except Exception:
pass
# Restore the VT cursor so the terminal is usable if dropped back to console.
try:
with open('/sys/class/graphics/fbcon/cursor_blink', 'w') as _f:
_f.write('1') # re-enable fbcon cursor blink
except Exception:
pass
try:
with open('/dev/tty1', 'wb') as _tty:
_tty.write(b'\033[?25h') # show cursor
except Exception:
pass