Skip to content

Commit 21afc8e

Browse files
Remove .micropython/lib symlink workaround
1 parent 00f51af commit 21afc8e

File tree

3 files changed

+282
-7
lines changed

3 files changed

+282
-7
lines changed

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,6 @@ or
123123
~/sources/MicroPythonOS/scripts/build_lvgl_micropython.sh macOS
124124
```
125125

126-
To run it, it's recommended to symlink your ~/.micropython/lib folder into this project's lib:
127-
128-
```
129-
ln -sf $(readlink -f internal_filesystem/lib) ~/.micropython/lib
130-
```
131-
132126
Then to run it, do:
133127

134128
```

internal_filesystem/apps/com.micropythonos.camera/assets/camera.py

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# This code grabs images from the camera in RGB565 format (2 bytes per pixel)
2+
# and sends that to the QR decoder if QR decoding is enabled.
3+
# The QR decoder then converts the RGB565 to grayscale, as that's what quirc operates on.
4+
# It would be slightly more efficient to capture the images from the camera in L8/grayscale format,
5+
# or in YUV format and discarding the U and V planes, but then the image will be gray (not great UX)
6+
# and the performance impact of converting RGB565 to grayscale is probably minimal anyway.
7+
8+
import lvgl as lv
9+
10+
try:
11+
import webcam
12+
except Exception as e:
13+
print(f"Info: could not import webcam module: {e}")
14+
15+
from mpos.apps import Activity
16+
17+
class Camera(Activity):
18+
19+
width = 240
20+
height = 240
21+
22+
status_label_text = "No camera found."
23+
status_label_text_searching = "Searching QR codes...\n\nHold still and make them big!\n10cm for simple QR codes,\n20cm for complex."
24+
status_label_text_found = "Decoding QR..."
25+
26+
cam = None
27+
current_cam_buffer = None # Holds the current memoryview to prevent garbage collection
28+
29+
image = None
30+
image_dsc = None
31+
scanqr_mode = None
32+
use_webcam = False
33+
keepliveqrdecoding = False
34+
35+
capture_timer = None
36+
37+
# Widgets:
38+
qr_label = None
39+
qr_button = None
40+
snap_button = None
41+
status_label = None
42+
status_label_cont = None
43+
44+
def onCreate(self):
45+
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
46+
main_screen = lv.obj()
47+
main_screen.set_style_pad_all(0, 0)
48+
main_screen.set_style_border_width(0, 0)
49+
main_screen.set_size(lv.pct(100), lv.pct(100))
50+
main_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
51+
close_button = lv.button(main_screen)
52+
close_button.set_size(60,60)
53+
close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0)
54+
close_label = lv.label(close_button)
55+
close_label.set_text(lv.SYMBOL.CLOSE)
56+
close_label.center()
57+
close_button.add_event_cb(lambda e: self.finish(),lv.EVENT.CLICKED,None)
58+
self.snap_button = lv.button(main_screen)
59+
self.snap_button.set_size(60, 60)
60+
self.snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0)
61+
self.snap_button.add_flag(lv.obj.FLAG.HIDDEN)
62+
self.snap_button.add_event_cb(self.snap_button_click,lv.EVENT.CLICKED,None)
63+
snap_label = lv.label(self.snap_button)
64+
snap_label.set_text(lv.SYMBOL.OK)
65+
snap_label.center()
66+
self.qr_button = lv.button(main_screen)
67+
self.qr_button.set_size(60, 60)
68+
self.qr_button.add_flag(lv.obj.FLAG.HIDDEN)
69+
self.qr_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
70+
self.qr_button.add_event_cb(self.qr_button_click,lv.EVENT.CLICKED,None)
71+
self.qr_label = lv.label(self.qr_button)
72+
self.qr_label.set_text(lv.SYMBOL.EYE_OPEN)
73+
self.qr_label.center()
74+
# Initialize LVGL image widget
75+
self.image = lv.image(main_screen)
76+
self.image.align(lv.ALIGN.LEFT_MID, 0, 0)
77+
# Create image descriptor once
78+
self.image_dsc = lv.image_dsc_t({
79+
"header": {
80+
"magic": lv.IMAGE_HEADER_MAGIC,
81+
"w": self.width,
82+
"h": self.height,
83+
"stride": self.width * 2,
84+
"cf": lv.COLOR_FORMAT.RGB565
85+
#"cf": lv.COLOR_FORMAT.L8
86+
},
87+
'data_size': self.width * self.height * 2,
88+
'data': None # Will be updated per frame
89+
})
90+
self.image.set_src(self.image_dsc)
91+
self.status_label_cont = lv.obj(main_screen)
92+
self.status_label_cont.set_size(lv.pct(66),lv.pct(60))
93+
self.status_label_cont.align(lv.ALIGN.LEFT_MID, lv.pct(5), 0)
94+
self.status_label_cont.set_style_bg_color(lv.color_white(), 0)
95+
self.status_label_cont.set_style_bg_opa(66, 0)
96+
self.status_label_cont.set_style_border_width(0, 0)
97+
self.status_label = lv.label(self.status_label_cont)
98+
self.status_label.set_text("No camera found.")
99+
self.status_label.set_long_mode(lv.label.LONG.WRAP)
100+
self.status_label.set_style_text_color(lv.color_white(), 0)
101+
self.status_label.set_width(lv.pct(100))
102+
self.status_label.center()
103+
self.setContentView(main_screen)
104+
105+
def onResume(self, screen):
106+
self.cam = init_internal_cam()
107+
if self.cam:
108+
self.image.set_rotation(900) # internal camera is rotated 90 degrees
109+
else:
110+
print("camtest.py: no internal camera found, trying webcam on /dev/video0")
111+
try:
112+
self.cam = webcam.init("/dev/video0")
113+
self.use_webcam = True
114+
except Exception as e:
115+
print(f"camtest.py: webcam exception: {e}")
116+
if self.cam:
117+
print("Camera initialized, continuing...")
118+
self.capture_timer = lv.timer_create(self.try_capture, 100, None)
119+
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
120+
if self.scanqr_mode:
121+
self.start_qr_decoding()
122+
else:
123+
self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN)
124+
self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN)
125+
else:
126+
print("No camera found, stopping camtest.py")
127+
if self.scanqr_mode:
128+
self.finish()
129+
130+
131+
def onStop(self, screen):
132+
print("camtest.py backgrounded, cleaning up...")
133+
if self.capture_timer:
134+
self.capture_timer.delete()
135+
if self.use_webcam:
136+
webcam.deinit(self.cam)
137+
elif self.cam:
138+
self.cam.deinit()
139+
print("camtest.py cleanup done.")
140+
141+
def qrdecode_one(self):
142+
try:
143+
import qrdecode
144+
result = qrdecode.qrdecode_rgb565(self.current_cam_buffer, self.width, self.height)
145+
#result = bytearray("INSERT_QR_HERE", "utf-8")
146+
if not result:
147+
self.status_label.set_text(self.status_label_text_searching)
148+
else:
149+
self.stop_qr_decoding()
150+
result = remove_bom(result)
151+
result = print_qr_buffer(result)
152+
print(f"QR decoding found: {result}")
153+
if self.scanqr_mode:
154+
self.setResult(True, result)
155+
self.finish()
156+
else:
157+
self.status_label.set_text(result) # in the future, the status_label text should be copy-paste-able
158+
except ValueError as e:
159+
print("QR ValueError: ", e)
160+
self.status_label.set_text(self.status_label_text_searching)
161+
except TypeError as e:
162+
print("QR TypeError: ", e)
163+
self.status_label.set_text(self.status_label_text_found)
164+
except Exception as e:
165+
print("QR got other error: ", e)
166+
167+
def snap_button_click(self, e):
168+
print("Picture taken!")
169+
import os
170+
try:
171+
os.mkdir("data")
172+
except OSError:
173+
pass
174+
try:
175+
os.mkdir("data/com.example.camtest")
176+
except OSError:
177+
pass
178+
if self.current_cam_buffer is not None:
179+
filename="data/com.example.camtest/capture.raw"
180+
try:
181+
with open(filename, 'wb') as f:
182+
f.write(self.current_cam_buffer)
183+
print(f"Successfully wrote current_cam_buffer to {filename}")
184+
except OSError as e:
185+
print(f"Error writing to file: {e}")
186+
187+
def start_qr_decoding(self):
188+
print("Activating live QR decoding...")
189+
self.keepliveqrdecoding = True
190+
self.qr_label.set_text(lv.SYMBOL.EYE_CLOSE)
191+
self.status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN)
192+
self.status_label.set_text(self.status_label_text_searching)
193+
194+
def stop_qr_decoding(self):
195+
print("Deactivating live QR decoding...")
196+
self.keepliveqrdecoding = False
197+
self.qr_label.set_text(lv.SYMBOL.EYE_OPEN)
198+
self.status_label_text = self.status_label.get_text()
199+
if self.status_label_text in (self.status_label_text_searching or self.status_label_text_found): # if it found a QR code, leave it
200+
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
201+
202+
def qr_button_click(self, e):
203+
if not self.keepliveqrdecoding:
204+
self.start_qr_decoding()
205+
else:
206+
self.stop_qr_decoding()
207+
208+
def try_capture(self, event):
209+
#print("capturing camera frame")
210+
try:
211+
if self.use_webcam:
212+
self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565")
213+
elif self.cam.frame_available():
214+
self.current_cam_buffer = self.cam.capture()
215+
if self.current_cam_buffer and len(self.current_cam_buffer):
216+
self.image_dsc.data = self.current_cam_buffer
217+
#image.invalidate() # does not work so do this:
218+
self.image.set_src(self.image_dsc)
219+
if not self.use_webcam:
220+
self.cam.free_buffer() # Free the old buffer
221+
if self.keepliveqrdecoding:
222+
self.qrdecode_one()
223+
except Exception as e:
224+
print(f"Camera capture exception: {e}")
225+
226+
227+
228+
# Non-class functions:
229+
def init_internal_cam():
230+
try:
231+
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
232+
cam = Camera(
233+
data_pins=[12,13,15,11,14,10,7,2],
234+
vsync_pin=6,
235+
href_pin=4,
236+
sda_pin=21,
237+
scl_pin=16,
238+
pclk_pin=9,
239+
xclk_pin=8,
240+
xclk_freq=20000000,
241+
powerdown_pin=-1,
242+
reset_pin=-1,
243+
pixel_format=PixelFormat.RGB565,
244+
#pixel_format=PixelFormat.GRAYSCALE,
245+
frame_size=FrameSize.R240X240,
246+
grab_mode=GrabMode.LATEST
247+
)
248+
#cam.init() automatically done when creating the Camera()
249+
#cam.reconfigure(frame_size=FrameSize.HVGA)
250+
#frame_size=FrameSize.HVGA, # 480x320
251+
#frame_size=FrameSize.QVGA, # 320x240
252+
#frame_size=FrameSize.QQVGA # 160x120
253+
cam.set_vflip(True)
254+
return cam
255+
except Exception as e:
256+
print(f"init_cam exception: {e}")
257+
return None
258+
259+
def print_qr_buffer(buffer):
260+
try:
261+
# Try to decode buffer as a UTF-8 string
262+
result = buffer.decode('utf-8')
263+
# Check if the string is printable (ASCII printable characters)
264+
if all(32 <= ord(c) <= 126 for c in result):
265+
return result
266+
except Exception as e:
267+
pass
268+
# If not a valid string or not printable, convert to hex
269+
hex_str = ' '.join([f'{b:02x}' for b in buffer])
270+
return hex_str.lower()
271+
272+
# Byte-Order-Mark is added sometimes
273+
def remove_bom(buffer):
274+
bom = b'\xEF\xBB\xBF'
275+
if buffer.startswith(bom):
276+
return buffer[3:]
277+
return buffer

internal_filesystem/boot_unix.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import lvgl as lv
55
import sdl_display
66

7+
# Add lib/ to the path for modules, otherwise it will only search in ~/.micropython/lib and /usr/lib/micropython
8+
import sys
9+
sys.path.append('lib/')
10+
711
import mpos.ui
812

913
#TFT_HOR_RES=640
@@ -32,5 +36,6 @@
3236

3337
#keyboard.add_event_cb(keyboard_cb, lv.EVENT.ALL, None)
3438

39+
3540
print("boot_unix.py finished")
3641

0 commit comments

Comments
 (0)