diff --git a/internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py b/internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py index cc55e10e..5249c2d1 100644 --- a/internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py +++ b/internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py @@ -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 diff --git a/internal_filesystem/apps/com.micropythonos.camera/assets/camera_settings.py b/internal_filesystem/apps/com.micropythonos.camera/assets/camera_settings.py index ce502bc4..36eeac4f 100644 --- a/internal_filesystem/apps/com.micropythonos.camera/assets/camera_settings.py +++ b/internal_filesystem/apps/com.micropythonos.camera/assets/camera_settings.py @@ -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] diff --git a/internal_filesystem/lib/mpos/config.py b/internal_filesystem/lib/mpos/config.py index 99821c31..dd626d67 100644 --- a/internal_filesystem/lib/mpos/config.py +++ b/internal_filesystem/lib/mpos/config.py @@ -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 = {}