From 8a0eac09b8b9a564cd93da212f6781773c4e609d Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Tue, 3 Jun 2025 11:34:10 +0200 Subject: [PATCH] Piggy: call camera app using Intent, use same class Also add fallback to class name. --- .../com.example.camtest/assets/camtest.py | 307 +----------- .../assets/captureqr.py | 455 ++++++++---------- .../assets/displaywallet.py | 7 +- internal_filesystem/lib/mpos/apps.py | 28 +- 4 files changed, 230 insertions(+), 567 deletions(-) mode change 100644 => 120000 internal_filesystem/apps/com.example.camtest/assets/camtest.py diff --git a/internal_filesystem/apps/com.example.camtest/assets/camtest.py b/internal_filesystem/apps/com.example.camtest/assets/camtest.py deleted file mode 100644 index f0e6ae6f..00000000 --- a/internal_filesystem/apps/com.example.camtest/assets/camtest.py +++ /dev/null @@ -1,306 +0,0 @@ -# This code grabs images from the camera in RGB565 format (2 bytes per pixel) -# and sends that to the QR decoder if QR decoding is enabled. -# The QR decoder then converts the RGB565 to grayscale, as that's what quirc operates on. -# It would be slightly more efficient to capture the images from the camera in L8/grayscale format, -# or in YUV format and discarding the U and V planes, but then the image will be gray (not great UX) -# and the performance impact of converting RGB565 to grayscale is probably minimal anyway. - -import lvgl as lv -import time - -try: - import webcam -except Exception as e: - print(f"Info: could not import webcam module: {e}") - - -import mpos.apps -import mpos.ui - -# screens: -main_screen = None - -keepliveqrdecoding = False -width = 240 -height = 240 - -cam = None -# Variable to hold the current memoryview to prevent garbage collection -current_cam_buffer = None -image_dsc = None -image = None -qr_label = None -scanqr_callback = None - -use_webcam = False -qr_button = None -snap_button = None - -capture_timer = None - -status_label = None -status_label_cont = None -status_label_text = "No camera found." -status_label_text_searching = "Searching QR codes...\n\nHold still and make them big!\n10cm for simple QR codes,\n20cm for complex." -status_label_text_found = "Decoding QR..." - -def print_qr_buffer(buffer): - try: - # Try to decode buffer as a UTF-8 string - result = buffer.decode('utf-8') - # Check if the string is printable (ASCII printable characters) - if all(32 <= ord(c) <= 126 for c in result): - return result - except Exception as e: - pass - # If not a valid string or not printable, convert to hex - hex_str = ' '.join([f'{b:02x}' for b in buffer]) - return hex_str.lower() - -# Byte-Order-Mark is added sometimes -def remove_bom(buffer): - bom = b'\xEF\xBB\xBF' - if buffer.startswith(bom): - return buffer[3:] - return buffer - -def qrdecode_one(): - global status_label, status_label_text, scanqr_callback - try: - import qrdecode - result = qrdecode.qrdecode_rgb565(current_cam_buffer, width, height) - #result = bytearray("INSERT_QR_HERE", "utf-8") - if not result: - status_label.set_text(status_label_text_searching) - else: - stop_qr_decoding() - result = remove_bom(result) - result = print_qr_buffer(result) - print(f"QR decoding found: {result}") - if scanqr_callback: - scanqr_callback(True,result) - mpos.ui.back_screen() - else: - status_label.set_text(result) # in the future, the status_label text should be copy-paste-able - except ValueError as e: - print("QR ValueError: ", e) - status_label.set_text(status_label_text_searching) - except TypeError as e: - print("QR TypeError: ", e) - status_label.set_text(status_label_text_found) - except Exception as e: - print("QR got other error: ", e) - - -def close_button_click(e): - print("Close button clicked") - mpos.ui.back_screen() - - -def snap_button_click(e): - print("Picture taken!") - import os - try: - os.mkdir("data") - except OSError: - pass - try: - os.mkdir("data/com.example.camtest") - except OSError: - pass - if current_cam_buffer is not None: - filename="data/com.example.camtest/capture.raw" - try: - with open(filename, 'wb') as f: - f.write(current_cam_buffer) - print(f"Successfully wrote current_cam_buffer to {filename}") - except OSError as e: - print(f"Error writing to file: {e}") - - -def start_qr_decoding(): - global qr_label, keepliveqrdecoding, status_label_cont, status_label - print("Activating live QR decoding...") - keepliveqrdecoding = True - qr_label.set_text(lv.SYMBOL.EYE_CLOSE) - status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN) - status_label.set_text(status_label_text_searching) - -def stop_qr_decoding(): - global qr_label, keepliveqrdecoding - print("Deactivating live QR decoding...") - keepliveqrdecoding = False - qr_label.set_text(lv.SYMBOL.EYE_OPEN) - status_label_text = status_label.get_text() - if status_label_text == status_label_text_searching or status_label_text == status_label_text_found: # if it found a QR code, then leave it - status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) - - -def qr_button_click(e): - global keepliveqrdecoding - if not keepliveqrdecoding: - start_qr_decoding() - else: - stop_qr_decoding() - -def try_capture(event): - #print("capturing camera frame") - global current_cam_buffer, image_dsc, image, use_webcam, cam - try: - if use_webcam: - current_cam_buffer = webcam.capture_frame(cam, "rgb565") - elif cam.frame_available(): - current_cam_buffer = cam.capture() - if current_cam_buffer and len(current_cam_buffer): - image_dsc.data = current_cam_buffer - #image.invalidate() # does not work so do this: - image.set_src(image_dsc) - if not use_webcam: - cam.free_buffer() # Free the old buffer - if keepliveqrdecoding: - qrdecode_one() - except Exception as e: - print(f"Camera capture exception: {e}") - - -def build_ui(): - global image, image_dsc,qr_label, status_label, cam, use_webcam, qr_button, snap_button, status_label_cont, main_screen - main_screen = lv.obj() - main_screen.set_style_pad_all(0, 0) - main_screen.set_style_border_width(0, 0) - main_screen.set_size(lv.pct(100), lv.pct(100)) - main_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) - close_button = lv.button(main_screen) - close_button.set_size(60,60) - close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0) - close_label = lv.label(close_button) - close_label.set_text(lv.SYMBOL.CLOSE) - close_label.center() - close_button.add_event_cb(close_button_click,lv.EVENT.CLICKED,None) - snap_button = lv.button(main_screen) - snap_button.set_size(60, 60) - snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0) - snap_button.add_flag(lv.obj.FLAG.HIDDEN) - snap_label = lv.label(snap_button) - snap_label.set_text(lv.SYMBOL.OK) - snap_label.center() - snap_button.add_event_cb(snap_button_click,lv.EVENT.CLICKED,None) - qr_button = lv.button(main_screen) - qr_button.set_size(60, 60) - qr_button.add_flag(lv.obj.FLAG.HIDDEN) - qr_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0) - qr_label = lv.label(qr_button) - qr_label.set_text(lv.SYMBOL.EYE_OPEN) - qr_label.center() - qr_button.add_event_cb(qr_button_click,lv.EVENT.CLICKED,None) - # Initialize LVGL image widget - image = lv.image(main_screen) - image.align(lv.ALIGN.LEFT_MID, 0, 0) - # Create image descriptor once - image_dsc = lv.image_dsc_t({ - "header": { - "magic": lv.IMAGE_HEADER_MAGIC, - "w": width, - "h": height, - "stride": width * 2, - "cf": lv.COLOR_FORMAT.RGB565 - #"cf": lv.COLOR_FORMAT.L8 - }, - 'data_size': width * height * 2, - 'data': None # Will be updated per frame - }) - image.set_src(image_dsc) - status_label_cont = lv.obj(main_screen) - status_label_cont.set_size(lv.pct(66),lv.pct(60)) - status_label_cont.align(lv.ALIGN.LEFT_MID, lv.pct(5), 0) - status_label_cont.set_style_bg_color(lv.color_white(), 0) - status_label_cont.set_style_bg_opa(66, 0) - status_label_cont.set_style_border_width(0, 0) - status_label = lv.label(status_label_cont) - status_label.set_text("No camera found.") - status_label.set_long_mode(lv.label.LONG.WRAP) - status_label.set_style_text_color(lv.color_white(), 0) - status_label.set_width(lv.pct(100)) - status_label.center() - - -def init_cam(): - try: - from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling - cam = Camera( - data_pins=[12,13,15,11,14,10,7,2], - vsync_pin=6, - href_pin=4, - sda_pin=21, - scl_pin=16, - pclk_pin=9, - xclk_pin=8, - xclk_freq=20000000, - powerdown_pin=-1, - reset_pin=-1, - pixel_format=PixelFormat.RGB565, - #pixel_format=PixelFormat.GRAYSCALE, - frame_size=FrameSize.R240X240, - grab_mode=GrabMode.LATEST - ) - #cam.init() automatically done when creating the Camera() - #cam.reconfigure(frame_size=FrameSize.HVGA) - #frame_size=FrameSize.HVGA, # 480x320 - #frame_size=FrameSize.QVGA, # 320x240 - #frame_size=FrameSize.QQVGA # 160x120 - cam.set_vflip(True) - return cam - except Exception as e: - print(f"init_cam exception: {e}") - return None - - - -def check_running(timer): - if lv.screen_active() != main_screen: - print("camtest.py backgrounded, cleaning up...") - check_running_timer.delete() - if capture_timer: - capture_timer.delete() - if use_webcam: - webcam.deinit(cam) - elif cam: - cam.deinit() - print("camtest.py cleanup done.") - - -def init(scanqr_cb=None): - global scanqr_callback, cam, use_webcam, check_running_timer, status_label_cont, capture_timer, main_screen - scanqr_callback = scanqr_cb - build_ui() - cam = init_cam() - if cam: - image.set_rotation(900) # internal camera is rotated 90 degrees - else: - print("camtest.py: no internal camera found, trying webcam on /dev/video0") - try: - cam = webcam.init("/dev/video0") - use_webcam = True - except Exception as e: - print(f"camtest.py: webcam exception: {e}") - if cam: - print("Camera initialized, continuing...") - check_running_timer = lv.timer_create(check_running, 500, None) - capture_timer = lv.timer_create(try_capture, 100, None) - status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) - if scanqr_callback: - start_qr_decoding() - else: - qr_button.remove_flag(lv.obj.FLAG.HIDDEN) - snap_button.remove_flag(lv.obj.FLAG.HIDDEN) - return main_screen - else: - print("No camera found, stopping camtest.py") - if scanqr_callback: - scanqr_callback(False,"") - return None - - -if __name__ == '__main__': - print("camera started as __main__") - mpos.ui.load_screen(init()) diff --git a/internal_filesystem/apps/com.example.camtest/assets/camtest.py b/internal_filesystem/apps/com.example.camtest/assets/camtest.py new file mode 120000 index 00000000..fcf9a9cb --- /dev/null +++ b/internal_filesystem/apps/com.example.camtest/assets/camtest.py @@ -0,0 +1 @@ +/home/user/sources/PiggyOS/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py \ No newline at end of file diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py index f0e6ae6f..f3e673a3 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/captureqr.py @@ -6,225 +6,221 @@ # and the performance impact of converting RGB565 to grayscale is probably minimal anyway. import lvgl as lv -import time try: import webcam except Exception as e: print(f"Info: could not import webcam module: {e}") +from mpos.apps import Activity -import mpos.apps -import mpos.ui - -# screens: -main_screen = None - -keepliveqrdecoding = False width = 240 height = 240 -cam = None -# Variable to hold the current memoryview to prevent garbage collection -current_cam_buffer = None -image_dsc = None -image = None -qr_label = None -scanqr_callback = None - -use_webcam = False -qr_button = None -snap_button = None - -capture_timer = None - -status_label = None -status_label_cont = None status_label_text = "No camera found." status_label_text_searching = "Searching QR codes...\n\nHold still and make them big!\n10cm for simple QR codes,\n20cm for complex." status_label_text_found = "Decoding QR..." -def print_qr_buffer(buffer): - try: - # Try to decode buffer as a UTF-8 string - result = buffer.decode('utf-8') - # Check if the string is printable (ASCII printable characters) - if all(32 <= ord(c) <= 126 for c in result): - return result - except Exception as e: - pass - # If not a valid string or not printable, convert to hex - hex_str = ' '.join([f'{b:02x}' for b in buffer]) - return hex_str.lower() +class CameraActivity(Activity): -# Byte-Order-Mark is added sometimes -def remove_bom(buffer): - bom = b'\xEF\xBB\xBF' - if buffer.startswith(bom): - return buffer[3:] - return buffer + def __init__(self): + self.cam = None + self.current_cam_buffer = None # Variable to hold the current memoryview to prevent garbage collection + self.image_dsc = None + self.image = None + self.qr_label = None + self.scanqr_callback = None + self.use_webcam = False + self.qr_button = None + self.snap_button = None + self.capture_timer = None + self.status_label = None + self.status_label_cont = None + self.keepliveqrdecoding = False -def qrdecode_one(): - global status_label, status_label_text, scanqr_callback - try: - import qrdecode - result = qrdecode.qrdecode_rgb565(current_cam_buffer, width, height) - #result = bytearray("INSERT_QR_HERE", "utf-8") - if not result: - status_label.set_text(status_label_text_searching) + def onCreate(self): + self.scanqr_callback = self.getIntent().extras.get("scanqr_callback") + main_screen = lv.obj() + main_screen.set_style_pad_all(0, 0) + main_screen.set_style_border_width(0, 0) + main_screen.set_size(lv.pct(100), lv.pct(100)) + main_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + close_button = lv.button(main_screen) + close_button.set_size(60,60) + close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0) + close_label = lv.label(close_button) + close_label.set_text(lv.SYMBOL.CLOSE) + close_label.center() + close_button.add_event_cb(lambda e: self.finish(),lv.EVENT.CLICKED,None) + self.snap_button = lv.button(main_screen) + self.snap_button.set_size(60, 60) + self.snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0) + self.snap_button.add_flag(lv.obj.FLAG.HIDDEN) + self.snap_button.add_event_cb(self.snap_button_click,lv.EVENT.CLICKED,None) + snap_label = lv.label(self.snap_button) + snap_label.set_text(lv.SYMBOL.OK) + snap_label.center() + self.qr_button = lv.button(main_screen) + self.qr_button.set_size(60, 60) + self.qr_button.add_flag(lv.obj.FLAG.HIDDEN) + self.qr_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0) + self.qr_button.add_event_cb(self.qr_button_click,lv.EVENT.CLICKED,None) + self.qr_label = lv.label(self.qr_button) + self.qr_label.set_text(lv.SYMBOL.EYE_OPEN) + self.qr_label.center() + # Initialize LVGL image widget + self.image = lv.image(main_screen) + self.image.align(lv.ALIGN.LEFT_MID, 0, 0) + # Create image descriptor once + self.image_dsc = lv.image_dsc_t({ + "header": { + "magic": lv.IMAGE_HEADER_MAGIC, + "w": width, + "h": height, + "stride": width * 2, + "cf": lv.COLOR_FORMAT.RGB565 + #"cf": lv.COLOR_FORMAT.L8 + }, + 'data_size': width * height * 2, + 'data': None # Will be updated per frame + }) + self.image.set_src(self.image_dsc) + self.status_label_cont = lv.obj(main_screen) + self.status_label_cont.set_size(lv.pct(66),lv.pct(60)) + self.status_label_cont.align(lv.ALIGN.LEFT_MID, lv.pct(5), 0) + self.status_label_cont.set_style_bg_color(lv.color_white(), 0) + self.status_label_cont.set_style_bg_opa(66, 0) + self.status_label_cont.set_style_border_width(0, 0) + self.status_label = lv.label(self.status_label_cont) + self.status_label.set_text("No camera found.") + self.status_label.set_long_mode(lv.label.LONG.WRAP) + self.status_label.set_style_text_color(lv.color_white(), 0) + self.status_label.set_width(lv.pct(100)) + self.status_label.center() + self.setContentView(main_screen) + + def onResume(self, screen): + self.cam = init_internal_cam() + if self.cam: + self.image.set_rotation(900) # internal camera is rotated 90 degrees else: - stop_qr_decoding() - result = remove_bom(result) - result = print_qr_buffer(result) - print(f"QR decoding found: {result}") - if scanqr_callback: - scanqr_callback(True,result) - mpos.ui.back_screen() + print("camtest.py: no internal camera found, trying webcam on /dev/video0") + try: + self.cam = webcam.init("/dev/video0") + self.use_webcam = True + except Exception as e: + print(f"camtest.py: webcam exception: {e}") + if self.cam: + print("Camera initialized, continuing...") + self.capture_timer = lv.timer_create(self.try_capture, 100, None) + self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) + if self.scanqr_callback: + self.start_qr_decoding() else: - status_label.set_text(result) # in the future, the status_label text should be copy-paste-able - except ValueError as e: - print("QR ValueError: ", e) - status_label.set_text(status_label_text_searching) - except TypeError as e: - print("QR TypeError: ", e) - status_label.set_text(status_label_text_found) - except Exception as e: - print("QR got other error: ", e) + self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN) + self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN) + else: + print("No camera found, stopping camtest.py") + if self.scanqr_callback: + self.scanqr_callback(False,"") + def onStop(self, screen): + print("camtest.py backgrounded, cleaning up...") + if self.capture_timer: + self.capture_timer.delete() + if self.use_webcam: + webcam.deinit(self.cam) + elif self.cam: + self.cam.deinit() + print("camtest.py cleanup done.") -def close_button_click(e): - print("Close button clicked") - mpos.ui.back_screen() - - -def snap_button_click(e): - print("Picture taken!") - import os - try: - os.mkdir("data") - except OSError: - pass - try: - os.mkdir("data/com.example.camtest") - except OSError: - pass - if current_cam_buffer is not None: - filename="data/com.example.camtest/capture.raw" + def qrdecode_one(self): try: - with open(filename, 'wb') as f: - f.write(current_cam_buffer) - print(f"Successfully wrote current_cam_buffer to {filename}") - except OSError as e: - print(f"Error writing to file: {e}") + import qrdecode + result = qrdecode.qrdecode_rgb565(self.current_cam_buffer, width, height) + #result = bytearray("INSERT_QR_HERE", "utf-8") + if not result: + self.status_label.set_text(status_label_text_searching) + else: + self.stop_qr_decoding() + result = remove_bom(result) + result = print_qr_buffer(result) + print(f"QR decoding found: {result}") + if self.scanqr_callback: + self.scanqr_callback(True,result) + self.finish() + else: + self.status_label.set_text(result) # in the future, the status_label text should be copy-paste-able + except ValueError as e: + print("QR ValueError: ", e) + self.status_label.set_text(status_label_text_searching) + except TypeError as e: + print("QR TypeError: ", e) + self.status_label.set_text(status_label_text_found) + except Exception as e: + print("QR got other error: ", e) + def snap_button_click(self, e): + print("Picture taken!") + import os + try: + os.mkdir("data") + except OSError: + pass + try: + os.mkdir("data/com.example.camtest") + except OSError: + pass + if self.current_cam_buffer is not None: + filename="data/com.example.camtest/capture.raw" + try: + with open(filename, 'wb') as f: + f.write(self.current_cam_buffer) + print(f"Successfully wrote current_cam_buffer to {filename}") + except OSError as e: + print(f"Error writing to file: {e}") + + def start_qr_decoding(self): + print("Activating live QR decoding...") + self.keepliveqrdecoding = True + self.qr_label.set_text(lv.SYMBOL.EYE_CLOSE) + self.status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN) + self.status_label.set_text(status_label_text_searching) + + def stop_qr_decoding(self): + print("Deactivating live QR decoding...") + self.keepliveqrdecoding = False + self.qr_label.set_text(lv.SYMBOL.EYE_OPEN) + status_label_text = self.status_label.get_text() + if status_label_text == status_label_text_searching or status_label_text == status_label_text_found: # if it found a QR code, leave it + self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) + + def qr_button_click(self, e): + if not self.keepliveqrdecoding: + self.start_qr_decoding() + else: + self.stop_qr_decoding() + + def try_capture(self, event): + #print("capturing camera frame") + try: + if self.use_webcam: + self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565") + elif self.cam.frame_available(): + self.current_cam_buffer = self.cam.capture() + if self.current_cam_buffer and len(self.current_cam_buffer): + self.image_dsc.data = self.current_cam_buffer + #image.invalidate() # does not work so do this: + self.image.set_src(self.image_dsc) + if not self.use_webcam: + self.cam.free_buffer() # Free the old buffer + if self.keepliveqrdecoding: + self.qrdecode_one() + except Exception as e: + print(f"Camera capture exception: {e}") -def start_qr_decoding(): - global qr_label, keepliveqrdecoding, status_label_cont, status_label - print("Activating live QR decoding...") - keepliveqrdecoding = True - qr_label.set_text(lv.SYMBOL.EYE_CLOSE) - status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN) - status_label.set_text(status_label_text_searching) - -def stop_qr_decoding(): - global qr_label, keepliveqrdecoding - print("Deactivating live QR decoding...") - keepliveqrdecoding = False - qr_label.set_text(lv.SYMBOL.EYE_OPEN) - status_label_text = status_label.get_text() - if status_label_text == status_label_text_searching or status_label_text == status_label_text_found: # if it found a QR code, then leave it - status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) - - -def qr_button_click(e): - global keepliveqrdecoding - if not keepliveqrdecoding: - start_qr_decoding() - else: - stop_qr_decoding() - -def try_capture(event): - #print("capturing camera frame") - global current_cam_buffer, image_dsc, image, use_webcam, cam - try: - if use_webcam: - current_cam_buffer = webcam.capture_frame(cam, "rgb565") - elif cam.frame_available(): - current_cam_buffer = cam.capture() - if current_cam_buffer and len(current_cam_buffer): - image_dsc.data = current_cam_buffer - #image.invalidate() # does not work so do this: - image.set_src(image_dsc) - if not use_webcam: - cam.free_buffer() # Free the old buffer - if keepliveqrdecoding: - qrdecode_one() - except Exception as e: - print(f"Camera capture exception: {e}") - - -def build_ui(): - global image, image_dsc,qr_label, status_label, cam, use_webcam, qr_button, snap_button, status_label_cont, main_screen - main_screen = lv.obj() - main_screen.set_style_pad_all(0, 0) - main_screen.set_style_border_width(0, 0) - main_screen.set_size(lv.pct(100), lv.pct(100)) - main_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) - close_button = lv.button(main_screen) - close_button.set_size(60,60) - close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0) - close_label = lv.label(close_button) - close_label.set_text(lv.SYMBOL.CLOSE) - close_label.center() - close_button.add_event_cb(close_button_click,lv.EVENT.CLICKED,None) - snap_button = lv.button(main_screen) - snap_button.set_size(60, 60) - snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0) - snap_button.add_flag(lv.obj.FLAG.HIDDEN) - snap_label = lv.label(snap_button) - snap_label.set_text(lv.SYMBOL.OK) - snap_label.center() - snap_button.add_event_cb(snap_button_click,lv.EVENT.CLICKED,None) - qr_button = lv.button(main_screen) - qr_button.set_size(60, 60) - qr_button.add_flag(lv.obj.FLAG.HIDDEN) - qr_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0) - qr_label = lv.label(qr_button) - qr_label.set_text(lv.SYMBOL.EYE_OPEN) - qr_label.center() - qr_button.add_event_cb(qr_button_click,lv.EVENT.CLICKED,None) - # Initialize LVGL image widget - image = lv.image(main_screen) - image.align(lv.ALIGN.LEFT_MID, 0, 0) - # Create image descriptor once - image_dsc = lv.image_dsc_t({ - "header": { - "magic": lv.IMAGE_HEADER_MAGIC, - "w": width, - "h": height, - "stride": width * 2, - "cf": lv.COLOR_FORMAT.RGB565 - #"cf": lv.COLOR_FORMAT.L8 - }, - 'data_size': width * height * 2, - 'data': None # Will be updated per frame - }) - image.set_src(image_dsc) - status_label_cont = lv.obj(main_screen) - status_label_cont.set_size(lv.pct(66),lv.pct(60)) - status_label_cont.align(lv.ALIGN.LEFT_MID, lv.pct(5), 0) - status_label_cont.set_style_bg_color(lv.color_white(), 0) - status_label_cont.set_style_bg_opa(66, 0) - status_label_cont.set_style_border_width(0, 0) - status_label = lv.label(status_label_cont) - status_label.set_text("No camera found.") - status_label.set_long_mode(lv.label.LONG.WRAP) - status_label.set_style_text_color(lv.color_white(), 0) - status_label.set_width(lv.pct(100)) - status_label.center() - - -def init_cam(): +# Non-class functions: +def init_internal_cam(): try: from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling cam = Camera( @@ -254,53 +250,22 @@ def init_cam(): print(f"init_cam exception: {e}") return None +def print_qr_buffer(buffer): + try: + # Try to decode buffer as a UTF-8 string + result = buffer.decode('utf-8') + # Check if the string is printable (ASCII printable characters) + if all(32 <= ord(c) <= 126 for c in result): + return result + except Exception as e: + pass + # If not a valid string or not printable, convert to hex + hex_str = ' '.join([f'{b:02x}' for b in buffer]) + return hex_str.lower() - -def check_running(timer): - if lv.screen_active() != main_screen: - print("camtest.py backgrounded, cleaning up...") - check_running_timer.delete() - if capture_timer: - capture_timer.delete() - if use_webcam: - webcam.deinit(cam) - elif cam: - cam.deinit() - print("camtest.py cleanup done.") - - -def init(scanqr_cb=None): - global scanqr_callback, cam, use_webcam, check_running_timer, status_label_cont, capture_timer, main_screen - scanqr_callback = scanqr_cb - build_ui() - cam = init_cam() - if cam: - image.set_rotation(900) # internal camera is rotated 90 degrees - else: - print("camtest.py: no internal camera found, trying webcam on /dev/video0") - try: - cam = webcam.init("/dev/video0") - use_webcam = True - except Exception as e: - print(f"camtest.py: webcam exception: {e}") - if cam: - print("Camera initialized, continuing...") - check_running_timer = lv.timer_create(check_running, 500, None) - capture_timer = lv.timer_create(try_capture, 100, None) - status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) - if scanqr_callback: - start_qr_decoding() - else: - qr_button.remove_flag(lv.obj.FLAG.HIDDEN) - snap_button.remove_flag(lv.obj.FLAG.HIDDEN) - return main_screen - else: - print("No camera found, stopping camtest.py") - if scanqr_callback: - scanqr_callback(False,"") - return None - - -if __name__ == '__main__': - print("camera started as __main__") - mpos.ui.load_screen(init()) +# Byte-Order-Mark is added sometimes +def remove_bom(buffer): + bom = b'\xEF\xBB\xBF' + if buffer.startswith(bom): + return buffer[3:] + return buffer diff --git a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py index d5e0be12..8981614c 100644 --- a/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py +++ b/internal_filesystem/apps/com.lightningpiggy.displaywallet/assets/displaywallet.py @@ -3,7 +3,7 @@ import mpos.config import mpos.ui from wallet import LNBitsWallet, NWCWallet - +from captureqr import CameraActivity class MainActivity(Activity): @@ -328,10 +328,7 @@ class SettingActivity(Activity): def cambutton_cb(self, event): print("cambutton clicked!") - import captureqr - qr_scanner_screen = captureqr.init(self.gotqr_callback) - if qr_scanner_screen: - mpos.ui.load_screen(qr_scanner_screen) + self.startActivity(Intent(activity_class=CameraActivity).putExtra("scanqr_callback", self.gotqr_callback)) def save_setting(self, setting): if setting["key"] == "wallet_type" and self.radio_container: diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index 9693601c..232793d1 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -7,7 +7,6 @@ import uos import _thread import traceback -import mpos.apps import mpos.info import mpos.ui @@ -41,16 +40,23 @@ def execute_script(script_source, is_file, cwd=None): compiled_script = compile(script_source, compile_name, 'exec') exec(compiled_script, script_globals) # Introspect globals - classes = {k: v for k, v in script_globals.items() if isinstance(v, type)} - functions = {k: v for k, v in script_globals.items() if callable(v) and not isinstance(v, type)} - variables = {k: v for k, v in script_globals.items() if not callable(v)} - print("Classes:", classes.keys()) - print("Functions:", functions.keys()) - print("Variables:", variables.keys()) - MainActivity = script_globals.get("MainActivity") - if MainActivity: - loaded_activity = MainActivity() - loaded_activity.onCreate() # Call lifecycle method + #classes = {k: v for k, v in script_globals.items() if isinstance(v, type)} + #functions = {k: v for k, v in script_globals.items() if callable(v) and not isinstance(v, type)} + #variables = {k: v for k, v in script_globals.items() if not callable(v)} + #print("Classes:", classes.keys()) + #print("Functions:", functions.keys()) + #print("Variables:", variables.keys()) + main_activity = script_globals.get("MainActivity") + if not main_activity: # Fallback to taking the first non-generic Activity class, but that's slower + for k, v in script_globals.items(): + if k != "Activity" and isinstance(v, type): + main_activity = v # first one is the 'Activity' from which it inherits so take the second one + break + print(f"Got main_activity: {main_activity}") + if main_activity: + Activity.startActivity(None, Intent(activity_class=main_activity)) + else: + print("Warning: could not find main_activity") except Exception as e: print(f"Thread {thread_id}: exception during execution:") # Print stack trace with exception type, value, and traceback