mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-10-05 15:08:25 +00:00
Improved I/O Test screen
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@ embit==0.4.10
|
|||||||
numpy==1.21.1
|
numpy==1.21.1
|
||||||
picamera==1.13
|
picamera==1.13
|
||||||
Pillow==8.2.0
|
Pillow==8.2.0
|
||||||
-e git+https://github.com/kdmukai/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
|
-e git+https://github.com/seedsigner/pyzbar.git@c3c237821c6a20b17953efe59b90df0b514a1c03#egg=pyzbar
|
||||||
qrcode==7.3.1
|
qrcode==7.3.1
|
||||||
RPi.GPIO==0.7.0
|
RPi.GPIO==0.7.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ class TextArea(BaseComponent):
|
|||||||
screen_x: int = 0
|
screen_x: int = 0
|
||||||
screen_y: int = 0
|
screen_y: int = 0
|
||||||
min_text_x: int = None
|
min_text_x: int = None
|
||||||
background_color: str = "black"
|
background_color: str = GUIConstants.BACKGROUND_COLOR
|
||||||
font_name: str = GUIConstants.BODY_FONT_NAME
|
font_name: str = GUIConstants.BODY_FONT_NAME
|
||||||
font_size: int = GUIConstants.BODY_FONT_SIZE
|
font_size: int = GUIConstants.BODY_FONT_SIZE
|
||||||
font_color: str = GUIConstants.BODY_FONT_COLOR
|
font_color: str = GUIConstants.BODY_FONT_COLOR
|
||||||
@@ -1168,6 +1168,7 @@ class IconButton(Button):
|
|||||||
icon_size: int = GUIConstants.ICON_INLINE_FONT_SIZE
|
icon_size: int = GUIConstants.ICON_INLINE_FONT_SIZE
|
||||||
text: str = None
|
text: str = None
|
||||||
is_icon_inline: bool = False
|
is_icon_inline: bool = False
|
||||||
|
is_text_centered: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ class Renderer(ConfigurableSingleton):
|
|||||||
buttons = None
|
buttons = None
|
||||||
canvas_width = 0
|
canvas_width = 0
|
||||||
canvas_height = 0
|
canvas_height = 0
|
||||||
canvas: Image = None
|
canvas: Image.Image = None
|
||||||
draw: ImageDraw = None
|
draw: ImageDraw.ImageDraw = None
|
||||||
disp = None
|
disp = None
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class ScanScreen(BaseScreen):
|
|||||||
instructions_text: str = "Scan a QR code"
|
instructions_text: str = "Scan a QR code"
|
||||||
resolution: Tuple[int,int] = (480, 480)
|
resolution: Tuple[int,int] = (480, 480)
|
||||||
framerate: int = 12
|
framerate: int = 12
|
||||||
auto_deactivate_buttons: bool = False # Used by the I/O test screen
|
render_rect: Tuple[int,int,int,int] = None
|
||||||
|
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
from seedsigner.hardware.camera import Camera
|
from seedsigner.hardware.camera import Camera
|
||||||
@@ -36,25 +37,35 @@ class ScanScreen(BaseScreen):
|
|||||||
decoder=self.decoder,
|
decoder=self.decoder,
|
||||||
renderer=self.renderer,
|
renderer=self.renderer,
|
||||||
instructions_text=self.instructions_text,
|
instructions_text=self.instructions_text,
|
||||||
components=self.components,
|
render_rect=self.render_rect,
|
||||||
auto_deactivate_buttons=self.auto_deactivate_buttons,
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
class LivePreviewThread(BaseThread):
|
class LivePreviewThread(BaseThread):
|
||||||
def __init__(self, camera: Camera, decoder: DecodeQR, renderer: renderer, instructions_text: str, components: List[BaseComponent], auto_deactivate_buttons: bool):
|
def __init__(self, camera: Camera, decoder: DecodeQR, renderer: renderer.Renderer, instructions_text: str, render_rect: Tuple[int,int,int,int]):
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
self.decoder = decoder
|
self.decoder = decoder
|
||||||
self.renderer = renderer
|
self.renderer = renderer
|
||||||
self.instructions_text = instructions_text
|
self.instructions_text = instructions_text
|
||||||
self.components = components
|
if render_rect:
|
||||||
self.auto_deactivate_buttons = auto_deactivate_buttons
|
self.render_rect = render_rect
|
||||||
|
else:
|
||||||
|
self.render_rect = (0, 0, self.renderer.canvas_width, self.renderer.canvas_height)
|
||||||
|
self.render_width = self.render_rect[2] - self.render_rect[0]
|
||||||
|
self.render_height = self.render_rect[3] - self.render_rect[1]
|
||||||
|
|
||||||
|
print(f"render_width: {self.render_width}")
|
||||||
|
print(f"render_height: {self.render_height}")
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
from timeit import default_timer as timer
|
||||||
|
|
||||||
instructions_font = Fonts.get_font(GUIConstants.BODY_FONT_NAME, GUIConstants.BUTTON_FONT_SIZE)
|
instructions_font = Fonts.get_font(GUIConstants.BODY_FONT_NAME, GUIConstants.BUTTON_FONT_SIZE)
|
||||||
while self.keep_running:
|
while self.keep_running:
|
||||||
|
start = timer()
|
||||||
frame = self.camera.read_video_stream(as_image=True)
|
frame = self.camera.read_video_stream(as_image=True)
|
||||||
if frame is not None:
|
if frame is not None:
|
||||||
scan_text = self.instructions_text
|
scan_text = self.instructions_text
|
||||||
@@ -62,40 +73,35 @@ class ScanScreen(BaseScreen):
|
|||||||
scan_text = str(self.decoder.get_percent_complete()) + "% Complete"
|
scan_text = str(self.decoder.get_percent_complete()) + "% Complete"
|
||||||
|
|
||||||
with self.renderer.lock:
|
with self.renderer.lock:
|
||||||
if frame.width > self.renderer.canvas_width or frame.height > self.renderer.canvas_height:
|
if frame.width > self.render_width or frame.height > self.render_height:
|
||||||
frame = frame.resize(
|
frame = frame.resize(
|
||||||
(self.renderer.canvas_width, self.renderer.canvas_height)
|
(self.render_width, self.render_height)
|
||||||
)
|
)
|
||||||
self.renderer.canvas.paste(
|
self.renderer.canvas.paste(
|
||||||
frame,
|
frame,
|
||||||
(0, 0)
|
(self.render_rect[0], self.render_rect[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.renderer.draw.text(
|
if scan_text:
|
||||||
xy=(
|
self.renderer.draw.text(
|
||||||
int(self.renderer.canvas_width/2),
|
xy=(
|
||||||
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
|
int(self.renderer.canvas_width/2),
|
||||||
),
|
self.renderer.canvas_height - GUIConstants.EDGE_PADDING
|
||||||
text=scan_text,
|
),
|
||||||
fill=GUIConstants.BODY_FONT_COLOR,
|
text=scan_text,
|
||||||
font=instructions_font,
|
fill=GUIConstants.BODY_FONT_COLOR,
|
||||||
stroke_width=4,
|
font=instructions_font,
|
||||||
stroke_fill=GUIConstants.BACKGROUND_COLOR,
|
stroke_width=4,
|
||||||
anchor="ms"
|
stroke_fill=GUIConstants.BACKGROUND_COLOR,
|
||||||
)
|
anchor="ms"
|
||||||
|
)
|
||||||
# Need to re-render all onscreen UI components since we just overwrote
|
|
||||||
# the screen.
|
|
||||||
for component in self.components:
|
|
||||||
component.render()
|
|
||||||
if self.auto_deactivate_buttons and type(component) in [Button, IconButton]:
|
|
||||||
if component.is_selected:
|
|
||||||
# deactivate for the next round
|
|
||||||
component.is_selected = False
|
|
||||||
|
|
||||||
self.renderer.show_image()
|
self.renderer.show_image()
|
||||||
|
|
||||||
time.sleep(0.1) # turn this up or down to tune performance while decoding psbt
|
end = timer()
|
||||||
|
# print(f"{1.0/(end - start)} fps") # Time in seconds, e.g. 5.38091952400282
|
||||||
|
|
||||||
|
time.sleep(0.05) # turn this up or down to tune performance while decoding psbt
|
||||||
if self.camera._video_stream is None:
|
if self.camera._video_stream is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from PIL.ImageOps import autocontrast
|
||||||
from typing import List
|
from typing import List
|
||||||
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, GUIConstants, IconButton, TextArea
|
from seedsigner.gui.components import Button, CheckboxButton, CheckedSelectionButton, FontAwesomeIconConstants, Fonts, GUIConstants, Icon, IconButton, IconTextLine, TextArea
|
||||||
from seedsigner.gui.screens.scan_screens import ScanScreen
|
from seedsigner.gui.screens.scan_screens import ScanScreen
|
||||||
|
|
||||||
from seedsigner.gui.screens.screen import BaseTopNavScreen, ButtonListScreen
|
from seedsigner.gui.screens.screen import BaseScreen, BaseTopNavScreen, ButtonListScreen
|
||||||
from seedsigner.hardware.buttons import HardwareButtonsConstants
|
from seedsigner.hardware.buttons import HardwareButtonsConstants
|
||||||
|
from seedsigner.hardware.camera import Camera
|
||||||
from seedsigner.models.settings import SettingsConstants
|
from seedsigner.models.settings import SettingsConstants
|
||||||
|
|
||||||
|
|
||||||
@@ -48,14 +50,18 @@ class SettingsEntryUpdateSelectionScreen(ButtonListScreen):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IOTestScreen(ScanScreen):
|
class IOTestScreen(BaseTopNavScreen):
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
self.title = "I/O Test"
|
||||||
|
self.show_back_button = False
|
||||||
|
self.resolution = (96, 96)
|
||||||
|
self.framerate = 10
|
||||||
|
self.instructions_text = None
|
||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
|
|
||||||
# D-pad pictogram
|
# D-pad pictogram
|
||||||
input_button_width = GUIConstants.BUTTON_HEIGHT
|
input_button_width = GUIConstants.BUTTON_HEIGHT + 2
|
||||||
input_button_height = input_button_width
|
input_button_height = input_button_width + 2
|
||||||
|
|
||||||
dpad_center_x = GUIConstants.EDGE_PADDING + input_button_width + GUIConstants.COMPONENT_PADDING
|
dpad_center_x = GUIConstants.EDGE_PADDING + input_button_width + GUIConstants.COMPONENT_PADDING
|
||||||
dpad_center_y = int((self.canvas_height - input_button_height)/2)
|
dpad_center_y = int((self.canvas_height - input_button_height)/2)
|
||||||
|
|
||||||
@@ -73,7 +79,6 @@ class IOTestScreen(ScanScreen):
|
|||||||
self.joystick_up_button = IconButton(
|
self.joystick_up_button = IconButton(
|
||||||
icon_name=FontAwesomeIconConstants.ANGLE_UP,
|
icon_name=FontAwesomeIconConstants.ANGLE_UP,
|
||||||
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
|
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
|
||||||
is_text_centered=True,
|
|
||||||
width=input_button_width,
|
width=input_button_width,
|
||||||
height=input_button_height,
|
height=input_button_height,
|
||||||
screen_x=dpad_center_x,
|
screen_x=dpad_center_x,
|
||||||
@@ -116,22 +121,30 @@ class IOTestScreen(ScanScreen):
|
|||||||
)
|
)
|
||||||
self.components.append(self.joystick_right_button)
|
self.components.append(self.joystick_right_button)
|
||||||
|
|
||||||
key_button_width = int(1.75*GUIConstants.BUTTON_HEIGHT)
|
# Hardware keys UI
|
||||||
key_button_height = int(0.85*GUIConstants.BUTTON_HEIGHT)
|
font = Fonts.get_font(GUIConstants.BUTTON_FONT_NAME, GUIConstants.BUTTON_FONT_SIZE)
|
||||||
|
(left, top, text_width, bottom) = font.getbbox(text="Clear", anchor="ls")
|
||||||
|
icon = Icon(
|
||||||
|
icon_name=FontAwesomeIconConstants.CAMERA,
|
||||||
|
icon_size=GUIConstants.ICON_INLINE_FONT_SIZE,
|
||||||
|
)
|
||||||
|
key_button_width = text_width + 2*GUIConstants.COMPONENT_PADDING + GUIConstants.EDGE_PADDING
|
||||||
|
key_button_height = icon.height + int(1.5*GUIConstants.COMPONENT_PADDING)
|
||||||
key2_y = int(self.canvas_height/2) - int(key_button_height/2)
|
key2_y = int(self.canvas_height/2) - int(key_button_height/2)
|
||||||
|
|
||||||
self.key2_button = Button(
|
self.key2_button = Button(
|
||||||
text=" ",
|
text="Clear", # Initialize with text to set vertical centering
|
||||||
width=key_button_width,
|
width=key_button_width,
|
||||||
height=key_button_height,
|
height=key_button_height,
|
||||||
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
|
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
|
||||||
screen_y=key2_y,
|
screen_y=key2_y,
|
||||||
outline_color=GUIConstants.ACCENT_COLOR,
|
outline_color=GUIConstants.ACCENT_COLOR,
|
||||||
)
|
)
|
||||||
|
self.key2_button.text = " " # but default state is empty
|
||||||
self.components.append(self.key2_button)
|
self.components.append(self.key2_button)
|
||||||
|
|
||||||
self.key1_button = Button(
|
self.key1_button = IconButton(
|
||||||
text=" ",
|
icon_name=FontAwesomeIconConstants.CAMERA,
|
||||||
width=key_button_width,
|
width=key_button_width,
|
||||||
height=key_button_height,
|
height=key_button_height,
|
||||||
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
|
screen_x=self.canvas_width - key_button_width + GUIConstants.EDGE_PADDING,
|
||||||
@@ -153,10 +166,90 @@ class IOTestScreen(ScanScreen):
|
|||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
cur_selected_button = self.key1_button
|
cur_selected_button = self.key1_button
|
||||||
|
msg_height = GUIConstants.ICON_LARGE_BUTTON_SIZE + 2*GUIConstants.COMPONENT_PADDING
|
||||||
|
camera_message = TextArea(
|
||||||
|
text="Capturing image...",
|
||||||
|
font_size=GUIConstants.TOP_NAV_TITLE_FONT_SIZE,
|
||||||
|
is_text_centered=True,
|
||||||
|
height=msg_height,
|
||||||
|
screen_y=int((self.canvas_height - msg_height)/ 2),
|
||||||
|
)
|
||||||
while True:
|
while True:
|
||||||
input = self.hw_inputs.wait_for(keys=HardwareButtonsConstants.ALL_KEYS, check_release=False)
|
input = self.hw_inputs.wait_for(keys=HardwareButtonsConstants.ALL_KEYS, check_release=False)
|
||||||
|
|
||||||
if input == HardwareButtonsConstants.KEY_PRESS:
|
if input == HardwareButtonsConstants.KEY1:
|
||||||
|
cur_selected_button = self.key1_button
|
||||||
|
|
||||||
|
with self.renderer.lock:
|
||||||
|
cur_selected_button.is_selected = True
|
||||||
|
cur_selected_button.render()
|
||||||
|
camera_message.render()
|
||||||
|
# Render edges around message box
|
||||||
|
self.image_draw.rectangle(
|
||||||
|
(
|
||||||
|
-1, int((self.canvas_height - msg_height)/ 2) - 1,
|
||||||
|
self.canvas_width + 1, int((self.canvas_height + msg_height)/ 2) + 1
|
||||||
|
),
|
||||||
|
outline=GUIConstants.ACCENT_COLOR,
|
||||||
|
width=1,
|
||||||
|
)
|
||||||
|
self.renderer.show_image()
|
||||||
|
|
||||||
|
# Snap a pic, render it as the background, re-render all onscreen elements
|
||||||
|
camera = Camera.get_instance()
|
||||||
|
try:
|
||||||
|
camera.start_single_frame_mode(resolution=(self.canvas_width, self.canvas_height))
|
||||||
|
|
||||||
|
# Reset the button state
|
||||||
|
with self.renderer.lock:
|
||||||
|
cur_selected_button.is_selected = False
|
||||||
|
cur_selected_button.render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
|
||||||
|
time.sleep(0.25)
|
||||||
|
background_frame = camera.capture_frame()
|
||||||
|
display_version = autocontrast(
|
||||||
|
background_frame,
|
||||||
|
cutoff=2
|
||||||
|
)
|
||||||
|
with self.renderer.lock:
|
||||||
|
self.canvas.paste(display_version, (0, self.top_nav.height))
|
||||||
|
self.key2_button.text = "Clear"
|
||||||
|
for component in self.components:
|
||||||
|
component.render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
finally:
|
||||||
|
camera.stop_single_frame_mode()
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif input == HardwareButtonsConstants.KEY2:
|
||||||
|
cur_selected_button = self.key2_button
|
||||||
|
|
||||||
|
# Clear the background
|
||||||
|
with self.renderer.lock:
|
||||||
|
cur_selected_button.is_selected = True
|
||||||
|
self._render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
|
||||||
|
# And then re-render Key2 in its initial state
|
||||||
|
self.key2_button.text = " "
|
||||||
|
cur_selected_button.is_selected = False
|
||||||
|
cur_selected_button.render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif input == HardwareButtonsConstants.KEY3:
|
||||||
|
# Exit
|
||||||
|
cur_selected_button = self.key3_button
|
||||||
|
cur_selected_button.is_selected = True
|
||||||
|
with self.renderer.lock:
|
||||||
|
cur_selected_button.render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
return
|
||||||
|
|
||||||
|
elif input == HardwareButtonsConstants.KEY_PRESS:
|
||||||
cur_selected_button = self.joystick_click_button
|
cur_selected_button = self.joystick_click_button
|
||||||
|
|
||||||
elif input == HardwareButtonsConstants.KEY_UP:
|
elif input == HardwareButtonsConstants.KEY_UP:
|
||||||
@@ -171,23 +264,15 @@ class IOTestScreen(ScanScreen):
|
|||||||
elif input == HardwareButtonsConstants.KEY_RIGHT:
|
elif input == HardwareButtonsConstants.KEY_RIGHT:
|
||||||
cur_selected_button = self.joystick_right_button
|
cur_selected_button = self.joystick_right_button
|
||||||
|
|
||||||
elif input == HardwareButtonsConstants.KEY1:
|
with self.renderer.lock:
|
||||||
cur_selected_button = self.key1_button
|
|
||||||
|
|
||||||
elif input == HardwareButtonsConstants.KEY2:
|
|
||||||
cur_selected_button = self.key2_button
|
|
||||||
|
|
||||||
elif input == HardwareButtonsConstants.KEY3:
|
|
||||||
# Exit
|
|
||||||
self.camera.stop_video_stream_mode()
|
|
||||||
cur_selected_button = self.key3_button
|
|
||||||
cur_selected_button.is_selected = True
|
cur_selected_button.is_selected = True
|
||||||
with self.renderer.lock:
|
cur_selected_button.render()
|
||||||
cur_selected_button.render()
|
self.renderer.show_image()
|
||||||
self.renderer.show_image()
|
|
||||||
return
|
|
||||||
|
|
||||||
cur_selected_button.is_selected = True
|
with self.renderer.lock:
|
||||||
|
cur_selected_button.is_selected = False
|
||||||
|
cur_selected_button.render()
|
||||||
|
self.renderer.show_image()
|
||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|||||||
@@ -178,12 +178,7 @@ class SettingsEntryUpdateSelectionView(View):
|
|||||||
****************************************************************************"""
|
****************************************************************************"""
|
||||||
class IOTestView(View):
|
class IOTestView(View):
|
||||||
def run(self):
|
def run(self):
|
||||||
settings_screens.IOTestScreen(
|
settings_screens.IOTestScreen().display()
|
||||||
instructions_text="Live camera feed",
|
|
||||||
resolution=(240,240),
|
|
||||||
framerate=24,
|
|
||||||
auto_deactivate_buttons=True,
|
|
||||||
).display()
|
|
||||||
|
|
||||||
return Destination(SettingsMenuView)
|
return Destination(SettingsMenuView)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user