Improved I/O Test screen

This commit is contained in:
kdmukai
2022-04-03 16:52:30 -05:00
parent 16a01ea4bd
commit 520cb61369
6 changed files with 156 additions and 69 deletions
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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
+2 -2
View File
@@ -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()
+37 -31
View File
@@ -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
+113 -28
View File
@@ -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)
+1 -6
View File
@@ -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)