diff --git a/c_mpos/src/webcam.c b/c_mpos/src/webcam.c index 3aeca890..1e6fad50 100644 --- a/c_mpos/src/webcam.c +++ b/c_mpos/src/webcam.c @@ -13,11 +13,12 @@ #define WIDTH 640 #define HEIGHT 480 -#define NUM_BUFFERS 4 +#define NUM_BUFFERS 1 # more buffers doesnt seem to help so one is enough #define OUTPUT_WIDTH 240 #define OUTPUT_HEIGHT 240 -// Forward declaration of the webcam type +#define WEBCAM_DEBUG_PRINT(...) mp_printf(&mp_plat_print, __VA_ARGS__) + static const mp_obj_type_t webcam_type; typedef struct _webcam_obj_t { @@ -26,19 +27,26 @@ typedef struct _webcam_obj_t { void *buffers[NUM_BUFFERS]; size_t buffer_length; int frame_count; - unsigned char *gray_buffer; // Persistent buffer for memoryview + unsigned char *gray_buffer; } webcam_obj_t; static void yuyv_to_grayscale_240x240(unsigned char *yuyv, unsigned char *gray, int in_width, int in_height) { - float x_ratio = (float)in_width / OUTPUT_WIDTH; - float y_ratio = (float)in_height / OUTPUT_HEIGHT; + // Crop to 480x480 centered region + int crop_size = 480; + int crop_x_offset = (in_width - crop_size) / 2; // Center the crop: (640 - 480) / 2 = 80 + int crop_y_offset = (in_height - crop_size) / 2; // Center the crop: (480 - 480) / 2 = 0 + + // Downscale ratios from 480x480 to 240x240 + float x_ratio = (float)crop_size / OUTPUT_WIDTH; // 480 / 240 = 2.0 + float y_ratio = (float)crop_size / OUTPUT_HEIGHT; // 480 / 240 = 2.0 for (int y = 0; y < OUTPUT_HEIGHT; y++) { for (int x = 0; x < OUTPUT_WIDTH; x++) { - int src_x = (int)(x * x_ratio); - int src_y = (int)(y * y_ratio); - int src_index = (src_y * in_width + src_x) * 2; - gray[y * OUTPUT_WIDTH + x] = yuyv[src_index]; + // Map output pixel to cropped region + int src_x = (int)(x * x_ratio) + crop_x_offset; // Adjust for crop offset + int src_y = (int)(y * y_ratio) + crop_y_offset; // Adjust for crop offset + int src_index = (src_y * in_width + src_x) * 2; // YUYV uses 2 bytes per pixel + gray[y * OUTPUT_WIDTH + x] = yuyv[src_index]; // Extract Y channel } } } @@ -46,7 +54,7 @@ static void yuyv_to_grayscale_240x240(unsigned char *yuyv, unsigned char *gray, static void save_raw(const char *filename, unsigned char *data, int width, int height) { FILE *fp = fopen(filename, "wb"); if (!fp) { - fprintf(stderr, "Cannot open file %s: %s\n", filename, strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot open file %s: %s\n", filename, strerror(errno)); return; } fwrite(data, 1, width * height, fp); @@ -56,7 +64,7 @@ static void save_raw(const char *filename, unsigned char *data, int width, int h static int init_webcam(webcam_obj_t *self, const char *device) { self->fd = open(device, O_RDWR); if (self->fd < 0) { - fprintf(stderr, "Cannot open device: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot open device: %s\n", strerror(errno)); return -1; } @@ -67,7 +75,7 @@ static int init_webcam(webcam_obj_t *self, const char *device) { fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field = V4L2_FIELD_ANY; if (ioctl(self->fd, VIDIOC_S_FMT, &fmt) < 0) { - fprintf(stderr, "Cannot set format: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot set format: %s\n", strerror(errno)); close(self->fd); return -1; } @@ -77,7 +85,7 @@ static int init_webcam(webcam_obj_t *self, const char *device) { req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(self->fd, VIDIOC_REQBUFS, &req) < 0) { - fprintf(stderr, "Cannot request buffers: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot request buffers: %s\n", strerror(errno)); close(self->fd); return -1; } @@ -88,14 +96,14 @@ static int init_webcam(webcam_obj_t *self, const char *device) { buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(self->fd, VIDIOC_QUERYBUF, &buf) < 0) { - fprintf(stderr, "Cannot query buffer: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot query buffer: %s\n", strerror(errno)); close(self->fd); return -1; } self->buffer_length = buf.length; self->buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, buf.m.offset); if (self->buffers[i] == MAP_FAILED) { - fprintf(stderr, "Cannot map buffer: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot map buffer: %s\n", strerror(errno)); close(self->fd); return -1; } @@ -107,21 +115,21 @@ static int init_webcam(webcam_obj_t *self, const char *device) { buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(self->fd, VIDIOC_QBUF, &buf) < 0) { - fprintf(stderr, "Cannot queue buffer: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot queue buffer: %s\n", strerror(errno)); return -1; } } enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(self->fd, VIDIOC_STREAMON, &type) < 0) { - fprintf(stderr, "Cannot start streaming: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot start streaming: %s\n", strerror(errno)); return -1; } self->frame_count = 0; self->gray_buffer = (unsigned char *)malloc(OUTPUT_WIDTH * OUTPUT_HEIGHT); if (!self->gray_buffer) { - fprintf(stderr, "Cannot allocate gray buffer: %s\n", strerror(errno)); + WEBCAM_DEBUG_PRINT("Cannot allocate gray buffer: %s\n", strerror(errno)); close(self->fd); return -1; } @@ -178,7 +186,7 @@ static mp_obj_t capture_frame(webcam_obj_t *self) { // snprintf(filename, sizeof(filename), "frame_%03d.raw", self->frame_count++); // save_raw(filename, self->gray_buffer, OUTPUT_WIDTH, OUTPUT_HEIGHT); - mp_obj_t result = mp_obj_new_bytes(self->gray_buffer, OUTPUT_WIDTH * OUTPUT_HEIGHT); + mp_obj_t result = mp_obj_new_memoryview('b', OUTPUT_WIDTH * OUTPUT_HEIGHT, self->gray_buffer); if (ioctl(self->fd, VIDIOC_QBUF, &buf) < 0) { mp_raise_OSError(MP_EIO); @@ -189,7 +197,7 @@ static mp_obj_t capture_frame(webcam_obj_t *self) { static mp_obj_t webcam_init(size_t n_args, const mp_obj_t *args) { mp_arg_check_num(n_args, 0, 0, 1, false); - const char *device = "/dev/video0"; // Default device + const char *device = "/dev/video0"; if (n_args == 1) { device = mp_obj_str_get_str(args[0]); } diff --git a/draft_code/image_tests.py b/draft_code/image_tests.py new file mode 100644 index 00000000..3d21261e --- /dev/null +++ b/draft_code/image_tests.py @@ -0,0 +1,42 @@ +width=240 +height=240 + +import webcam +import time + +cam = webcam.init("/dev/video0") # Initialize webcam with device path +memview = webcam.capture_frame(cam) # Returns memoryview +time.sleep_ms(1000) +static_bytes_obj = bytes(memview) + + +image = lv.image(lv.screen_active()) +image.align(lv.ALIGN.LEFT_MID, 0, 0) +image.set_rotation(900) +# Create image descriptor once +image_dsc = lv.image_dsc_t({ + "header": { + "magic": lv.IMAGE_HEADER_MAGIC, + "w": width, + "h": height, + "stride": width , + "cf": lv.COLOR_FORMAT.L8 + }, + 'data_size': width * height, + 'data': static_bytes_obj # Will be updated per frame +}) +image.set_src(image_dsc) + +for i in range(300): + print(f"iteration {i}") + webcam.recapture_frame(cam) #refresh memview + bytes_obj = bytes(memview) + #print(f"got bytes: {len(bytes_obj)}") + #image_dsc.data = static_bytes_obj + image_dsc.data = bytes_obj + #image.set_src(image_dsc) + image.invalidate() + time.sleep_ms(10) # seems to need more than 0 or 1 ms + +print("cleanup") +webcam.deinit(cam) # Deinitializes webcam diff --git a/draft_code/saved_functions.py b/draft_code/saved_functions.py index 86056951..9164790b 100644 --- a/draft_code/saved_functions.py +++ b/draft_code/saved_functions.py @@ -602,7 +602,7 @@ def log_callback(level, log_str): # Convert log_str to string if it's a bytes object log_str = log_str.decode() if isinstance(log_str, bytes) else log_str # Optional: Print for debugging - print(f"Level: {level}, Log: {log_str}") + #print(f"Level: {level}, Log: {log_str}") # Log message format: "sysmon: 25 FPS (refr_cnt: 8 | redraw_cnt: 1), ..." if "sysmon:" in log_str and "FPS" in log_str: try: @@ -628,14 +628,18 @@ def get_fps(): # Main loop -for _ in range(10): - import time - fps = get_fps() - if fps > 0: # Only print when FPS is updated - print("Current FPS:", fps) - time.sleep(1) +def print_fps(): + for _ in range(100): + import time + fps = get_fps() + if fps > 0: # Only print when FPS is updated + print("Current FPS:", fps) + time.sleep(1) +import _thread +_thread.stack_size(12*1024) +_thread.start_new_thread(print_fps, ()) # crash: diff --git a/internal_filesystem/apps/com.example.camtest/assets/camtest.py b/internal_filesystem/apps/com.example.camtest/assets/camtest.py index 9446c808..22f80716 100644 --- a/internal_filesystem/apps/com.example.camtest/assets/camtest.py +++ b/internal_filesystem/apps/com.example.camtest/assets/camtest.py @@ -2,6 +2,7 @@ import time import webcam appscreen = lv.screen_active() +th.disable() keepgoing = True keepliveqrdecoding = False @@ -15,6 +16,7 @@ image = None qr_label = None use_webcam = False +memview = None def print_qr_buffer(buffer): try: @@ -59,9 +61,12 @@ def close_button_click(e): def snap_button_click(e): print("Picture taken!") + import os try: - import os os.mkdir("data") + except OSError: + pass + try: os.mkdir("data/com.example.camtest") except OSError: pass @@ -93,89 +98,24 @@ def qr_button_click(e): qr_label.set_text(lv.SYMBOL.EYE_OPEN) def try_capture(): - global current_cam_buffer, image_dsc, image - use_webcam = True # Set to True for webcam module - if use_webcam: - new_cam_buffer = None - new_cam_buffer = webcam.capture_frame(cam) # Returns bytes - else: - new_cam_buffer = cam.capture() # Returns memoryview for other camera - if new_cam_buffer and len(new_cam_buffer) == 240 * 240: - if current_cam_buffer is not None: - if use_webcam: - #pass - webcam.free_buffer(cam) # Explicitly free webcam buffer - else: - cam.free_buffer() # Free other camera buffer - else: - pass - print("current_cam_buffer is None, not freeing...") - current_cam_buffer = None - current_cam_buffer = new_cam_buffer # Store new buffer reference - # Update image descriptor with new buffer - # Set image source to update LVGL - #image_dsc.data = None # this doesnt help - #image.set_src(None) #this crashes it - oldsrc = image.get_src() - if oldsrc: - oldsrc.delete() - #image_dsc.data = current_cam_buffer - image_dsc = lv.image_dsc_t({ - "header": { - "magic": lv.IMAGE_HEADER_MAGIC, - "w": width, - "h": height, - "stride": width , - #"cf": lv.COLOR_FORMAT.RGB565 - "cf": lv.COLOR_FORMAT.L8 - }, - 'data_size': width * height, - 'data': current_cam_buffer # Will be updated per frame - }) - #image.invalidate() - image.set_src(image_dsc) - else: - print("Invalid buffer size:", len(new_cam_buffer)) - if use_webcam: - pass - #webcam.free_buffer(cam) - else: - cam.free_buffer() - -def try_capture_again(): - global current_cam_buffer, image_dsc, image - new_cam_buffer = webcam.capture_frame(cam) # Returns memoryview - if new_cam_buffer and len(new_cam_buffer) == 240 * 240: - image_dsc.data = new_cam_buffer # Update image descriptor - image.set_src(image_dsc) # Update LVGL image - current_cam_buffer = None # Clear reference to allow GC - else: - print("Invalid buffer size:", len(new_cam_buffer)) - -def try_capture_old(): global current_cam_buffer, image_dsc, image, use_webcam if use_webcam: - new_cam_buffer = webcam.capture_frame(cam) + current_cam_buffer = webcam.capture_frame(cam) elif cam.frame_available(): - new_cam_buffer = cam.capture() # Returns memoryview - if new_cam_buffer and len(new_cam_buffer): - # print("Invalid buffer size:", len(new_cam_buffer)) - # cam.free_buffer() - # return - # Update image descriptor with new memoryview - image_dsc.data = new_cam_buffer - # Set image source to update LVGL (implicitly invalidates widget) + current_cam_buffer = cam.capture() # Returns memoryview + 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) - #current_cam_buffer = None # Clear reference to allow GC - #image.invalidate() #does not work - # Free the previous buffer (if any) after setting new data - if current_cam_buffer is not None and not use_webcam: + if not use_webcam: cam.free_buffer() # Free the old buffer - current_cam_buffer = new_cam_buffer # Store new buffer reference + else: + print("No image received from camera, ignoring...") + return def build_ui(): - global image, image_dsc,qr_label + global image, image_dsc,qr_label, cam cont = lv.obj(appscreen) cont.set_style_pad_all(0, 0) cont.set_style_border_width(0, 0) @@ -205,7 +145,8 @@ def build_ui(): # Initialize LVGL image widget image = lv.image(cont) image.align(lv.ALIGN.LEFT_MID, 0, 0) - image.set_rotation(900) + if not use_webcam: + image.set_rotation(900) # Create image descriptor once image_dsc = lv.image_dsc_t({ "header": { @@ -217,14 +158,13 @@ def build_ui(): "cf": lv.COLOR_FORMAT.L8 }, 'data_size': width * height, - 'data': None # Will be updated per frame + 'data': None # Will be updated per frame }) image.set_src(image_dsc) def init_cam(): try: - # time.sleep(1) doesn't help from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling cam = Camera( data_pins=[12,13,15,11,14,10,7,2], @@ -260,22 +200,23 @@ def init_cam(): cam = init_cam() if not cam: - print("init cam failed, retrying with webcam...") + print("camtest.py: no internal camera found, trying webcam on /dev/video0") try: cam = webcam.init("/dev/video0") # Initialize webcam with device path use_webcam = True except Exception as e: print(f"camtest.py: webcam exception: {e}") -if cam or use_webcam: +if cam: build_ui() count=0 while appscreen == lv.screen_active() and keepgoing is True: - print(f"capture nr {count}") - count += 1 try_capture() - time.sleep_ms(100) # Allow for the MicroPython REPL to still work. Reducing it doesn't seem to affect the on-display FPS. - print("App backgrounded, deinitializing camera...") + # Task handler needs to be updated from the same thread, otherwise it causes concurrency issues: + lv.task_handler() + time.sleep_ms(1) + lv.tick_inc(1) + print("camtest.py: stopping...") if use_webcam: webcam.deinit(cam) # Deinitializes webcam else: @@ -283,6 +224,7 @@ if cam or use_webcam: else: print("No camera found, exiting...") +th.enable() show_launcher() diff --git a/internal_filesystem/apps/com.example.camtestnew/META-INF/MANIFEST.JSON b/internal_filesystem/apps/com.example.camtestnew/META-INF/MANIFEST.JSON new file mode 100644 index 00000000..b3e9c5f1 --- /dev/null +++ b/internal_filesystem/apps/com.example.camtestnew/META-INF/MANIFEST.JSON @@ -0,0 +1,13 @@ +{ +"name": "CamTestNew", +"publisher": "ACME Inc", +"short_description": "Simple test of the camera", +"long_description": "A simple test of the camera makes it possible to validate the hardware.", +"icon_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk_icon_64x64.png", +"download_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk", +"fullname": "com.example.camtest", +"version": "0.0.2", +"entrypoint": "assets/camtestnew.py", +"category": "camera" +} + diff --git a/internal_filesystem/apps/com.example.camtestnew/assets/camtest.py_tries b/internal_filesystem/apps/com.example.camtestnew/assets/camtest.py_tries new file mode 100644 index 00000000..9446c808 --- /dev/null +++ b/internal_filesystem/apps/com.example.camtestnew/assets/camtest.py_tries @@ -0,0 +1,288 @@ +import time +import webcam + +appscreen = lv.screen_active() + +keepgoing = True +keepliveqrdecoding = False +width = 240 +height = 240 + +# Variable to hold the current memoryview to prevent garbage collection +current_cam_buffer = None +image_dsc = None +image = None +qr_label = None +use_webcam = False + + +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_live(): + # Image dimensions + buffer_size = width * height # 240 * 240 = 57600 bytes + while keepgoing and keepliveqrdecoding: + try: + import qrdecode + result = qrdecode.qrdecode(current_cam_buffer, width, height) + result = remove_bom(result) + result = print_qr_buffer(result) + print(f"QR decoding found: {result}") + except Exception as e: + print("QR decode error: ", e) + time.sleep_ms(500) + + +def close_button_click(e): + global keepgoing + print("Close button clicked") + keepgoing = False + + +def snap_button_click(e): + print("Picture taken!") + try: + import os + os.mkdir("data") + 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 qr_button_click(e): + global keepliveqrdecoding, qr_label + if not keepliveqrdecoding: + print("Activating live QR decoding...") + keepliveqrdecoding = True + qr_label.set_text(lv.SYMBOL.EYE_CLOSE) + try: + import _thread + _thread.stack_size(12*1024) # 16KB is too much + _thread.start_new_thread(qrdecode_live, ()) + except Exception as e: + print("Could not start live QR decoding thread: ", e) + else: + print("Deactivating live QR decoding...") + keepliveqrdecoding = False + qr_label.set_text(lv.SYMBOL.EYE_OPEN) + +def try_capture(): + global current_cam_buffer, image_dsc, image + use_webcam = True # Set to True for webcam module + if use_webcam: + new_cam_buffer = None + new_cam_buffer = webcam.capture_frame(cam) # Returns bytes + else: + new_cam_buffer = cam.capture() # Returns memoryview for other camera + if new_cam_buffer and len(new_cam_buffer) == 240 * 240: + if current_cam_buffer is not None: + if use_webcam: + #pass + webcam.free_buffer(cam) # Explicitly free webcam buffer + else: + cam.free_buffer() # Free other camera buffer + else: + pass + print("current_cam_buffer is None, not freeing...") + current_cam_buffer = None + current_cam_buffer = new_cam_buffer # Store new buffer reference + # Update image descriptor with new buffer + # Set image source to update LVGL + #image_dsc.data = None # this doesnt help + #image.set_src(None) #this crashes it + oldsrc = image.get_src() + if oldsrc: + oldsrc.delete() + #image_dsc.data = current_cam_buffer + image_dsc = lv.image_dsc_t({ + "header": { + "magic": lv.IMAGE_HEADER_MAGIC, + "w": width, + "h": height, + "stride": width , + #"cf": lv.COLOR_FORMAT.RGB565 + "cf": lv.COLOR_FORMAT.L8 + }, + 'data_size': width * height, + 'data': current_cam_buffer # Will be updated per frame + }) + #image.invalidate() + image.set_src(image_dsc) + else: + print("Invalid buffer size:", len(new_cam_buffer)) + if use_webcam: + pass + #webcam.free_buffer(cam) + else: + cam.free_buffer() + +def try_capture_again(): + global current_cam_buffer, image_dsc, image + new_cam_buffer = webcam.capture_frame(cam) # Returns memoryview + if new_cam_buffer and len(new_cam_buffer) == 240 * 240: + image_dsc.data = new_cam_buffer # Update image descriptor + image.set_src(image_dsc) # Update LVGL image + current_cam_buffer = None # Clear reference to allow GC + else: + print("Invalid buffer size:", len(new_cam_buffer)) + +def try_capture_old(): + global current_cam_buffer, image_dsc, image, use_webcam + if use_webcam: + new_cam_buffer = webcam.capture_frame(cam) + elif cam.frame_available(): + new_cam_buffer = cam.capture() # Returns memoryview + if new_cam_buffer and len(new_cam_buffer): + # print("Invalid buffer size:", len(new_cam_buffer)) + # cam.free_buffer() + # return + # Update image descriptor with new memoryview + image_dsc.data = new_cam_buffer + # Set image source to update LVGL (implicitly invalidates widget) + image.set_src(image_dsc) + #current_cam_buffer = None # Clear reference to allow GC + #image.invalidate() #does not work + # Free the previous buffer (if any) after setting new data + if current_cam_buffer is not None and not use_webcam: + cam.free_buffer() # Free the old buffer + current_cam_buffer = new_cam_buffer # Store new buffer reference + + +def build_ui(): + global image, image_dsc,qr_label + cont = lv.obj(appscreen) + cont.set_style_pad_all(0, 0) + cont.set_style_border_width(0, 0) + cont.set_size(lv.pct(100), lv.pct(100)) + cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) + close_button = lv.button(cont) + 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(cont) + snap_button.set_size(60, 60) + snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0) + 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(cont) + qr_button.set_size(60, 60) + 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(cont) + image.align(lv.ALIGN.LEFT_MID, 0, 0) + image.set_rotation(900) + # Create image descriptor once + image_dsc = lv.image_dsc_t({ + "header": { + "magic": lv.IMAGE_HEADER_MAGIC, + "w": width, + "h": height, + "stride": width , + #"cf": lv.COLOR_FORMAT.RGB565 + "cf": lv.COLOR_FORMAT.L8 + }, + 'data_size': width * height, + 'data': None # Will be updated per frame + }) + image.set_src(image_dsc) + + +def init_cam(): + try: + # time.sleep(1) doesn't help + 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 + + + + + + +cam = init_cam() +if not cam: + print("init cam failed, retrying with webcam...") + try: + cam = webcam.init("/dev/video0") # Initialize webcam with device path + use_webcam = True + except Exception as e: + print(f"camtest.py: webcam exception: {e}") + +if cam or use_webcam: + build_ui() + count=0 + while appscreen == lv.screen_active() and keepgoing is True: + print(f"capture nr {count}") + count += 1 + try_capture() + time.sleep_ms(100) # Allow for the MicroPython REPL to still work. Reducing it doesn't seem to affect the on-display FPS. + print("App backgrounded, deinitializing camera...") + if use_webcam: + webcam.deinit(cam) # Deinitializes webcam + else: + cam.deinit() +else: + print("No camera found, exiting...") + +show_launcher() + + diff --git a/internal_filesystem/apps/com.example.camtestnew/assets/camtestnew.py b/internal_filesystem/apps/com.example.camtestnew/assets/camtestnew.py new file mode 100644 index 00000000..a1d6e6da --- /dev/null +++ b/internal_filesystem/apps/com.example.camtestnew/assets/camtestnew.py @@ -0,0 +1,62 @@ +# This works with copy-paste +# It also works like this: +# import mpos +# mpos.apps.execute_script("apps/com.example.camtestnew/assets/camtestnew.py", True, False, True) +# import mpos +# mpos.apps.execute_script_new_thread("apps/com.example.camtestnew/assets/camtestnew.py", True, False, True) + + +width=240 +height=240 + +import webcam +import time + +th.disable() + +cam = webcam.init("/dev/video0") # Initialize webcam with device path +#memview = None +#memview = webcam.capture_frame(cam) # Returns memoryview +#time.sleep_ms(1000) +#static_bytes_obj = bytes(memview) + + +image = lv.image(lv.screen_active()) +image.align(lv.ALIGN.LEFT_MID, 0, 0) +#image.set_rotation(900) +# Create image descriptor once +image_dsc = lv.image_dsc_t({ + "header": { + "magic": lv.IMAGE_HEADER_MAGIC, + "w": width, + "h": height, + "stride": width , + "cf": lv.COLOR_FORMAT.L8 + }, + 'data_size': width * height, + 'data': None # Will be updated per frame +}) +image.set_src(image_dsc) + +#memview = webcam.capture_frame(cam) # Returns memoryview + +for i in range(100): + print(f"iteration {i}") + webcam.recapture_frame(cam) #refresh memview + #memview = + #bytes_obj = bytes(memview) + #print(f"got bytes: {len(bytes_obj)}") + #image_dsc.data = bytes_obj + #image_dsc.data = static_bytes_obj + image_dsc.data = webcam.capture_frame(cam) # Returns memoryview + image.set_src(image_dsc) + #image.invalidate() + lv.task_handler() + time.sleep_ms(5) # seems to need more than 0 or 1 ms, otherwise there's almost never a new image... + lv.tick_inc(5) + +print("cleanup") +webcam.deinit(cam) # Deinitializes webcam + + +th.enable() diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index 04f21ecc..e600fe00 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -35,6 +35,7 @@ def execute_script(script_source, is_file, is_launcher, is_graphical): lv.screen_load(newscreen) script_globals = { 'lv': lv, + 'th': mpos.ui.th, 'NOTIFICATION_BAR_HEIGHT': mpos.ui.NOTIFICATION_BAR_HEIGHT, # for apps that want to leave space for notification bar 'appscreen': newscreen, 'start_app': start_app, # for launcher apps diff --git a/internal_filesystem/lib/mpos/ui.py b/internal_filesystem/lib/mpos/ui.py index ba0857de..dcc9d10d 100644 --- a/internal_filesystem/lib/mpos/ui.py +++ b/internal_filesystem/lib/mpos/ui.py @@ -1,6 +1,8 @@ import lvgl as lv import mpos.apps +th = None + NOTIFICATION_BAR_HEIGHT=24 CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough diff --git a/internal_filesystem/main.py b/internal_filesystem/main.py index 64cd1253..ae47df98 100644 --- a/internal_filesystem/main.py +++ b/internal_filesystem/main.py @@ -1,10 +1,11 @@ import task_handler -th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop + import mpos.ui mpos.ui.create_rootscreen() mpos.ui.create_notification_bar() mpos.ui.create_drawer(display) +mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop try: import freezefs_mount_builtin