You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Camera app: different settings for QR scanning
This commit is contained in:
@@ -14,15 +14,12 @@ from camera_settings import CameraSettingsActivity
|
||||
|
||||
class CameraApp(Activity):
|
||||
|
||||
DEFAULT_WIDTH = 320 # 240 would be better but webcam doesn't support this (yet)
|
||||
DEFAULT_HEIGHT = 240
|
||||
PACKAGE = "com.micropythonos.camera"
|
||||
CONFIGFILE = "config.json"
|
||||
SCANQR_CONFIG = "config_scanqr_mode.json"
|
||||
|
||||
button_width = 60
|
||||
button_height = 45
|
||||
colormode = False
|
||||
|
||||
status_label_text = "No camera found."
|
||||
status_label_text_searching = "Searching QR codes...\n\nHold still and try varying scan distance (10-25cm) and make the QR code big (4-12cm). Ensure proper lighting."
|
||||
@@ -32,17 +29,20 @@ class CameraApp(Activity):
|
||||
current_cam_buffer = None # Holds the current memoryview to prevent garba
|
||||
width = None
|
||||
height = None
|
||||
colormode = False
|
||||
|
||||
image = None
|
||||
image_dsc = None
|
||||
scanqr_mode = None
|
||||
scanqr_mode = False
|
||||
scanqr_intent = False
|
||||
use_webcam = False
|
||||
keepliveqrdecoding = False
|
||||
|
||||
capture_timer = None
|
||||
|
||||
prefs = None # regular prefs
|
||||
scanqr_prefs = None # qr code scanning prefs
|
||||
|
||||
# Widgets:
|
||||
main_screen = None
|
||||
image = None
|
||||
qr_label = None
|
||||
qr_button = None
|
||||
snap_button = None
|
||||
@@ -50,10 +50,7 @@ class CameraApp(Activity):
|
||||
status_label_cont = None
|
||||
|
||||
def onCreate(self):
|
||||
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
|
||||
from mpos.config import SharedPreferences
|
||||
self.prefs = SharedPreferences(self.PACKAGE, filename=self.SCANQR_CONFIG if self.scanqr_mode else self.CONFIGFILE)
|
||||
|
||||
self.scanqr_intent = self.getIntent().extras.get("scanqr_intent")
|
||||
self.main_screen = lv.obj()
|
||||
self.main_screen.set_style_pad_all(1, 0)
|
||||
self.main_screen.set_style_border_width(0, 0)
|
||||
@@ -118,13 +115,31 @@ class CameraApp(Activity):
|
||||
self.setContentView(self.main_screen)
|
||||
|
||||
def onResume(self, screen):
|
||||
self.parse_camera_init_preferences()
|
||||
self.load_settings_cached()
|
||||
self.start_cam()
|
||||
if not self.cam and self.scanqr_mode:
|
||||
print("No camera found, stopping camera app")
|
||||
self.finish()
|
||||
# Camera is running and refreshing
|
||||
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
if self.scanqr_mode:
|
||||
self.start_qr_decoding()
|
||||
else:
|
||||
self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
|
||||
def onPause(self, screen):
|
||||
print("camera app backgrounded, cleaning up...")
|
||||
self.stop_cam()
|
||||
print("camera app cleanup done.")
|
||||
|
||||
def start_cam(self):
|
||||
# Init camera:
|
||||
self.cam = self.init_internal_cam(self.width, self.height)
|
||||
if self.cam:
|
||||
self.image.set_rotation(900) # internal camera is rotated 90 degrees
|
||||
# Apply saved camera settings, only for internal camera for now:
|
||||
self.apply_camera_settings(self.cam, self.use_webcam) # needs to be done AFTER the camera is initialized
|
||||
self.apply_camera_settings(self.scanqr_prefs if self.scanqr_mode else self.prefs, self.cam, self.use_webcam) # needs to be done AFTER the camera is initialized
|
||||
else:
|
||||
print("camera app: no internal camera found, trying webcam on /dev/video0")
|
||||
try:
|
||||
@@ -139,19 +154,8 @@ class CameraApp(Activity):
|
||||
print("Camera app initialized, continuing...")
|
||||
self.update_preview_image()
|
||||
self.capture_timer = lv.timer_create(self.try_capture, 100, None)
|
||||
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
if self.scanqr_mode or self.keepliveqrdecoding:
|
||||
self.start_qr_decoding()
|
||||
else:
|
||||
self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
else:
|
||||
print("No camera found, stopping camera app")
|
||||
if self.scanqr_mode:
|
||||
self.finish()
|
||||
|
||||
def onPause(self, screen):
|
||||
print("camera app backgrounded, cleaning up...")
|
||||
def stop_cam(self):
|
||||
if self.capture_timer:
|
||||
self.capture_timer.delete()
|
||||
if self.use_webcam:
|
||||
@@ -172,20 +176,24 @@ class CameraApp(Activity):
|
||||
i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command]))
|
||||
except Exception as e:
|
||||
print(f"Warning: powering off camera got exception: {e}")
|
||||
print("camera app cleanup done.")
|
||||
print("emptying self.current_cam_buffer...")
|
||||
self.image_dsc.data = None # it's important to delete the image when stopping the camera, otherwise LVGL might try to display it and crash
|
||||
|
||||
def parse_camera_init_preferences(self):
|
||||
resolution_str = self.prefs.get_string("resolution", f"{self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}")
|
||||
self.colormode = self.prefs.get_bool("colormode", False)
|
||||
try:
|
||||
width_str, height_str = resolution_str.split('x')
|
||||
self.width = int(width_str)
|
||||
self.height = int(height_str)
|
||||
print(f"Camera resolution loaded: {self.width}x{self.height}")
|
||||
except Exception as e:
|
||||
print(f"Error parsing resolution '{resolution_str}': {e}, using default 320x240")
|
||||
self.width = self.DEFAULT_WIDTH
|
||||
self.height = self.DEFAULT_HEIGHT
|
||||
def load_settings_cached(self):
|
||||
from mpos.config import SharedPreferences
|
||||
if self.scanqr_mode:
|
||||
print("loading scanqr settings...")
|
||||
if not self.scanqr_prefs:
|
||||
self.scanqr_prefs = SharedPreferences(self.PACKAGE, filename=self.SCANQR_CONFIG)
|
||||
self.width = self.scanqr_prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_SCANQR_WIDTH)
|
||||
self.height = self.scanqr_prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_SCANQR_HEIGHT)
|
||||
self.colormode = self.scanqr_prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_SCANQR_COLORMODE)
|
||||
else:
|
||||
if not self.prefs:
|
||||
self.prefs = SharedPreferences(self.PACKAGE)
|
||||
self.width = self.prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_WIDTH)
|
||||
self.height = self.prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_HEIGHT)
|
||||
self.colormode = self.prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_COLORMODE)
|
||||
|
||||
def update_preview_image(self):
|
||||
self.image_dsc = lv.image_dsc_t({
|
||||
@@ -238,7 +246,7 @@ class CameraApp(Activity):
|
||||
result = self.print_qr_buffer(result)
|
||||
print(f"QR decoding found: {result}")
|
||||
self.stop_qr_decoding()
|
||||
if self.scanqr_mode:
|
||||
if self.scanqr_intent:
|
||||
self.setResult(True, result)
|
||||
self.finish()
|
||||
else:
|
||||
@@ -270,21 +278,40 @@ class CameraApp(Activity):
|
||||
|
||||
def start_qr_decoding(self):
|
||||
print("Activating live QR decoding...")
|
||||
self.keepliveqrdecoding = True
|
||||
self.scanqr_mode = True
|
||||
oldwidth = self.width
|
||||
oldheight = self.height
|
||||
oldcolormode = self.colormode
|
||||
# Activate QR mode settings
|
||||
self.load_settings_cached()
|
||||
# Check if it's necessary to restart the camera:
|
||||
if self.width != oldwidth or self.height != oldheight or self.colormode != oldcolormode:
|
||||
self.stop_cam()
|
||||
self.start_cam()
|
||||
self.qr_label.set_text(lv.SYMBOL.EYE_CLOSE)
|
||||
self.status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN)
|
||||
self.status_label.set_text(self.status_label_text_searching)
|
||||
|
||||
def stop_qr_decoding(self):
|
||||
print("Deactivating live QR decoding...")
|
||||
self.keepliveqrdecoding = False
|
||||
self.scanqr_mode = False
|
||||
self.qr_label.set_text(lv.SYMBOL.EYE_OPEN)
|
||||
self.status_label_text = self.status_label.get_text()
|
||||
if self.status_label_text not in (self.status_label_text_searching or self.status_label_text_found): # if it found a QR code, leave it
|
||||
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
|
||||
# Check if it's necessary to restart the camera:
|
||||
oldwidth = self.width
|
||||
oldheight = self.height
|
||||
oldcolormode = self.colormode
|
||||
# Activate non-QR mode settings
|
||||
self.load_settings_cached()
|
||||
# Check if it's necessary to restart the camera:
|
||||
if self.width != oldwidth or self.height != oldheight or self.colormode != oldcolormode:
|
||||
self.stop_cam()
|
||||
self.start_cam()
|
||||
|
||||
def qr_button_click(self, e):
|
||||
if not self.keepliveqrdecoding:
|
||||
if not self.scanqr_mode:
|
||||
self.start_qr_decoding()
|
||||
else:
|
||||
self.stop_qr_decoding()
|
||||
@@ -311,11 +338,10 @@ class CameraApp(Activity):
|
||||
print(f"self.cam.set_res_raw returned {result}")
|
||||
|
||||
def open_settings(self):
|
||||
intent = Intent(activity_class=CameraSettingsActivity, extras={"prefs": self.prefs, "use_webcam": self.use_webcam, "scanqr_mode": self.scanqr_mode})
|
||||
intent = Intent(activity_class=CameraSettingsActivity, extras={"prefs": self.prefs if not self.scanqr_mode else self.scanqr_prefs, "use_webcam": self.use_webcam, "scanqr_mode": self.scanqr_mode})
|
||||
self.startActivity(intent)
|
||||
|
||||
def try_capture(self, event):
|
||||
#print("capturing camera frame")
|
||||
try:
|
||||
if self.use_webcam:
|
||||
self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565" if self.colormode else "grayscale")
|
||||
@@ -328,7 +354,7 @@ class CameraApp(Activity):
|
||||
self.image_dsc.data = self.current_cam_buffer
|
||||
#self.image.invalidate() # does not work so do this:
|
||||
self.image.set_src(self.image_dsc)
|
||||
if self.keepliveqrdecoding:
|
||||
if self.scanqr_mode:
|
||||
self.qrdecode_one()
|
||||
if not self.use_webcam:
|
||||
self.cam.free_buffer() # After QR decoding, free the old buffer, otherwise the camera doesn't provide a new one
|
||||
|
||||
@@ -6,12 +6,15 @@ from mpos.apps import Activity
|
||||
from mpos.config import SharedPreferences
|
||||
from mpos.content.intent import Intent
|
||||
|
||||
#from camera_app import CameraApp
|
||||
|
||||
class CameraSettingsActivity(Activity):
|
||||
"""Settings activity for comprehensive camera configuration."""
|
||||
|
||||
PACKAGE = "com.micropythonos.camera"
|
||||
DEFAULT_WIDTH = 320 # 240 would be better but webcam doesn't support this (yet)
|
||||
DEFAULT_HEIGHT = 240
|
||||
DEFAULT_COLORMODE = True
|
||||
DEFAULT_SCANQR_WIDTH = 960
|
||||
DEFAULT_SCANQR_HEIGHT = 960
|
||||
DEFAULT_SCANQR_COLORMODE = False
|
||||
|
||||
# Original: { 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }
|
||||
# Worked for digital zoom in C: { 2560, 1920, 0, 0, 2623, 1951, 992, 736, 2844, 1968 }
|
||||
@@ -69,8 +72,8 @@ class CameraSettingsActivity(Activity):
|
||||
|
||||
# These are taken from the Intent:
|
||||
use_webcam = False
|
||||
scanqr_mode = False
|
||||
prefs = None
|
||||
scanqr_mode = False
|
||||
|
||||
# Widgets:
|
||||
button_cont = None
|
||||
@@ -84,9 +87,9 @@ class CameraSettingsActivity(Activity):
|
||||
self.resolutions = []
|
||||
|
||||
def onCreate(self):
|
||||
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
|
||||
self.use_webcam = self.getIntent().extras.get("use_webcam")
|
||||
self.prefs = self.getIntent().extras.get("prefs")
|
||||
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
|
||||
if self.use_webcam:
|
||||
self.resolutions = self.WEBCAM_RESOLUTIONS
|
||||
print("Using webcam resolutions")
|
||||
@@ -228,23 +231,29 @@ class CameraSettingsActivity(Activity):
|
||||
button_cont.set_style_border_width(0, 0)
|
||||
|
||||
save_button = lv.button(button_cont)
|
||||
save_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
|
||||
save_button.set_size(lv.SIZE_CONTENT, lv.SIZE_CONTENT)
|
||||
save_button.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
|
||||
save_button.add_event_cb(lambda e: self.save_and_close(), lv.EVENT.CLICKED, None)
|
||||
save_label = lv.label(save_button)
|
||||
save_label.set_text("Save")
|
||||
savetext = "Save"
|
||||
if self.scanqr_mode:
|
||||
savetext += " QR tweaks"
|
||||
save_label.set_text(savetext)
|
||||
save_label.center()
|
||||
|
||||
cancel_button = lv.button(button_cont)
|
||||
cancel_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
|
||||
cancel_button.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
if self.scanqr_mode:
|
||||
cancel_button.align(lv.ALIGN.BOTTOM_MID, mpos.ui.pct_of_display_width(10), 0)
|
||||
else:
|
||||
cancel_button.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
cancel_button.add_event_cb(lambda e: self.finish(), lv.EVENT.CLICKED, None)
|
||||
cancel_label = lv.label(cancel_button)
|
||||
cancel_label.set_text("Cancel")
|
||||
cancel_label.center()
|
||||
|
||||
erase_button = lv.button(button_cont)
|
||||
erase_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
|
||||
erase_button.set_size(mpos.ui.pct_of_display_width(20), lv.SIZE_CONTENT)
|
||||
erase_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
|
||||
erase_button.add_event_cb(lambda e: self.erase_and_close(), lv.EVENT.CLICKED, None)
|
||||
erase_label = lv.label(erase_button)
|
||||
@@ -259,16 +268,22 @@ class CameraSettingsActivity(Activity):
|
||||
tab.set_style_pad_all(1, 0)
|
||||
|
||||
# Color Mode
|
||||
colormode = prefs.get_bool("colormode", False)
|
||||
colormode = prefs.get_bool("colormode", False if self.scanqr_mode else True)
|
||||
checkbox, cont = self.create_checkbox(tab, "Color Mode (slower)", colormode, "colormode")
|
||||
self.ui_controls["colormode"] = checkbox
|
||||
|
||||
# Resolution dropdown
|
||||
current_resolution = prefs.get_string("resolution", "320x240")
|
||||
print(f"self.scanqr_mode: {self.scanqr_mode}")
|
||||
current_resolution_width = prefs.get_string("resolution_width", self.DEFAULT_SCANQR_WIDTH if self.scanqr_mode else self.DEFAULT_WIDTH)
|
||||
current_resolution_height = prefs.get_string("resolution_width", self.DEFAULT_SCANQR_HEIGHT if self.scanqr_mode else self.DEFAULT_HEIGHT)
|
||||
dropdown_value = f"{current_resolution_width}x{current_resolution_height}"
|
||||
print(f"looking for {dropdown_value}")
|
||||
resolution_idx = 0
|
||||
for idx, (_, value) in enumerate(self.resolutions):
|
||||
if value == current_resolution:
|
||||
print(f"got {value}")
|
||||
if value == dropdown_value:
|
||||
resolution_idx = idx
|
||||
print(f"found it! {idx}")
|
||||
break
|
||||
|
||||
dropdown, cont = self.create_dropdown(tab, "Resolution:", self.resolutions, resolution_idx, "resolution")
|
||||
@@ -520,7 +535,7 @@ class CameraSettingsActivity(Activity):
|
||||
self.add_buttons(tab)
|
||||
|
||||
def erase_and_close(self):
|
||||
SharedPreferences(self.PACKAGE).edit().remove_all().commit()
|
||||
self.prefs.edit().remove_all().commit()
|
||||
self.setResult(True, {"settings_changed": True})
|
||||
self.finish()
|
||||
|
||||
@@ -550,9 +565,14 @@ class CameraSettingsActivity(Activity):
|
||||
selected_idx = control.get_selected()
|
||||
option_values = metadata.get("option_values", [])
|
||||
if pref_key == "resolution":
|
||||
# Resolution stored as string
|
||||
value = option_values[selected_idx]
|
||||
editor.put_string(pref_key, value)
|
||||
try:
|
||||
# Resolution stored as 2 ints
|
||||
value = option_values[selected_idx]
|
||||
width_str, height_str = value.split('x')
|
||||
editor.put_int("resolution_width", int(width_str))
|
||||
editor.put_int("resolution_height", int(height_str))
|
||||
except Exception as e:
|
||||
print(f"Error parsing resolution '{value}': {e}")
|
||||
else:
|
||||
# Other dropdowns store integer enum values
|
||||
value = option_values[selected_idx]
|
||||
|
||||
@@ -28,7 +28,7 @@ class SharedPreferences:
|
||||
try:
|
||||
with open(self.filepath, 'r') as f:
|
||||
self.data = ujson.load(f)
|
||||
print(f"load: Loaded preferences: {self.data}")
|
||||
print(f"load: Loaded preferences from {self.filepath}: {self.data}")
|
||||
except Exception as e:
|
||||
print(f"SharedPreferences.load didn't find preferences: {e}")
|
||||
self.data = {}
|
||||
|
||||
Reference in New Issue
Block a user