Skip to content

Commit f861412

Browse files
Camera app: different settings for QR scanning
1 parent 054ac74 commit f861412

File tree

3 files changed

+108
-62
lines changed

3 files changed

+108
-62
lines changed

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

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@
1414

1515
class CameraApp(Activity):
1616

17-
DEFAULT_WIDTH = 320 # 240 would be better but webcam doesn't support this (yet)
18-
DEFAULT_HEIGHT = 240
1917
PACKAGE = "com.micropythonos.camera"
2018
CONFIGFILE = "config.json"
2119
SCANQR_CONFIG = "config_scanqr_mode.json"
2220

2321
button_width = 60
2422
button_height = 45
25-
colormode = False
2623

2724
status_label_text = "No camera found."
2825
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,28 +29,28 @@ class CameraApp(Activity):
3229
current_cam_buffer = None # Holds the current memoryview to prevent garba
3330
width = None
3431
height = None
32+
colormode = False
3533

36-
image = None
3734
image_dsc = None
38-
scanqr_mode = None
35+
scanqr_mode = False
36+
scanqr_intent = False
3937
use_webcam = False
40-
keepliveqrdecoding = False
41-
4238
capture_timer = None
39+
40+
prefs = None # regular prefs
41+
scanqr_prefs = None # qr code scanning prefs
4342

4443
# Widgets:
4544
main_screen = None
45+
image = None
4646
qr_label = None
4747
qr_button = None
4848
snap_button = None
4949
status_label = None
5050
status_label_cont = None
5151

5252
def onCreate(self):
53-
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
54-
from mpos.config import SharedPreferences
55-
self.prefs = SharedPreferences(self.PACKAGE, filename=self.SCANQR_CONFIG if self.scanqr_mode else self.CONFIGFILE)
56-
53+
self.scanqr_intent = self.getIntent().extras.get("scanqr_intent")
5754
self.main_screen = lv.obj()
5855
self.main_screen.set_style_pad_all(1, 0)
5956
self.main_screen.set_style_border_width(0, 0)
@@ -118,13 +115,31 @@ def onCreate(self):
118115
self.setContentView(self.main_screen)
119116

120117
def onResume(self, screen):
121-
self.parse_camera_init_preferences()
118+
self.load_settings_cached()
119+
self.start_cam()
120+
if not self.cam and self.scanqr_mode:
121+
print("No camera found, stopping camera app")
122+
self.finish()
123+
# Camera is running and refreshing
124+
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
125+
if self.scanqr_mode:
126+
self.start_qr_decoding()
127+
else:
128+
self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN)
129+
self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN)
130+
131+
def onPause(self, screen):
132+
print("camera app backgrounded, cleaning up...")
133+
self.stop_cam()
134+
print("camera app cleanup done.")
135+
136+
def start_cam(self):
122137
# Init camera:
123138
self.cam = self.init_internal_cam(self.width, self.height)
124139
if self.cam:
125140
self.image.set_rotation(900) # internal camera is rotated 90 degrees
126141
# Apply saved camera settings, only for internal camera for now:
127-
self.apply_camera_settings(self.cam, self.use_webcam) # needs to be done AFTER the camera is initialized
142+
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
128143
else:
129144
print("camera app: no internal camera found, trying webcam on /dev/video0")
130145
try:
@@ -139,19 +154,8 @@ def onResume(self, screen):
139154
print("Camera app initialized, continuing...")
140155
self.update_preview_image()
141156
self.capture_timer = lv.timer_create(self.try_capture, 100, None)
142-
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
143-
if self.scanqr_mode or self.keepliveqrdecoding:
144-
self.start_qr_decoding()
145-
else:
146-
self.qr_button.remove_flag(lv.obj.FLAG.HIDDEN)
147-
self.snap_button.remove_flag(lv.obj.FLAG.HIDDEN)
148-
else:
149-
print("No camera found, stopping camera app")
150-
if self.scanqr_mode:
151-
self.finish()
152157

153-
def onPause(self, screen):
154-
print("camera app backgrounded, cleaning up...")
158+
def stop_cam(self):
155159
if self.capture_timer:
156160
self.capture_timer.delete()
157161
if self.use_webcam:
@@ -172,20 +176,24 @@ def onPause(self, screen):
172176
i2c.writeto(camera_addr, bytes([reg_high, reg_low, power_off_command]))
173177
except Exception as e:
174178
print(f"Warning: powering off camera got exception: {e}")
175-
print("camera app cleanup done.")
179+
print("emptying self.current_cam_buffer...")
180+
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
176181

177-
def parse_camera_init_preferences(self):
178-
resolution_str = self.prefs.get_string("resolution", f"{self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}")
179-
self.colormode = self.prefs.get_bool("colormode", False)
180-
try:
181-
width_str, height_str = resolution_str.split('x')
182-
self.width = int(width_str)
183-
self.height = int(height_str)
184-
print(f"Camera resolution loaded: {self.width}x{self.height}")
185-
except Exception as e:
186-
print(f"Error parsing resolution '{resolution_str}': {e}, using default 320x240")
187-
self.width = self.DEFAULT_WIDTH
188-
self.height = self.DEFAULT_HEIGHT
182+
def load_settings_cached(self):
183+
from mpos.config import SharedPreferences
184+
if self.scanqr_mode:
185+
print("loading scanqr settings...")
186+
if not self.scanqr_prefs:
187+
self.scanqr_prefs = SharedPreferences(self.PACKAGE, filename=self.SCANQR_CONFIG)
188+
self.width = self.scanqr_prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_SCANQR_WIDTH)
189+
self.height = self.scanqr_prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_SCANQR_HEIGHT)
190+
self.colormode = self.scanqr_prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_SCANQR_COLORMODE)
191+
else:
192+
if not self.prefs:
193+
self.prefs = SharedPreferences(self.PACKAGE)
194+
self.width = self.prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_WIDTH)
195+
self.height = self.prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_HEIGHT)
196+
self.colormode = self.prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_COLORMODE)
189197

190198
def update_preview_image(self):
191199
self.image_dsc = lv.image_dsc_t({
@@ -238,7 +246,7 @@ def qrdecode_one(self):
238246
result = self.print_qr_buffer(result)
239247
print(f"QR decoding found: {result}")
240248
self.stop_qr_decoding()
241-
if self.scanqr_mode:
249+
if self.scanqr_intent:
242250
self.setResult(True, result)
243251
self.finish()
244252
else:
@@ -270,21 +278,40 @@ def snap_button_click(self, e):
270278

271279
def start_qr_decoding(self):
272280
print("Activating live QR decoding...")
273-
self.keepliveqrdecoding = True
281+
self.scanqr_mode = True
282+
oldwidth = self.width
283+
oldheight = self.height
284+
oldcolormode = self.colormode
285+
# Activate QR mode settings
286+
self.load_settings_cached()
287+
# Check if it's necessary to restart the camera:
288+
if self.width != oldwidth or self.height != oldheight or self.colormode != oldcolormode:
289+
self.stop_cam()
290+
self.start_cam()
274291
self.qr_label.set_text(lv.SYMBOL.EYE_CLOSE)
275292
self.status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN)
276293
self.status_label.set_text(self.status_label_text_searching)
277294

278295
def stop_qr_decoding(self):
279296
print("Deactivating live QR decoding...")
280-
self.keepliveqrdecoding = False
297+
self.scanqr_mode = False
281298
self.qr_label.set_text(lv.SYMBOL.EYE_OPEN)
282299
self.status_label_text = self.status_label.get_text()
283300
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
284301
self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN)
302+
# Check if it's necessary to restart the camera:
303+
oldwidth = self.width
304+
oldheight = self.height
305+
oldcolormode = self.colormode
306+
# Activate non-QR mode settings
307+
self.load_settings_cached()
308+
# Check if it's necessary to restart the camera:
309+
if self.width != oldwidth or self.height != oldheight or self.colormode != oldcolormode:
310+
self.stop_cam()
311+
self.start_cam()
285312

286313
def qr_button_click(self, e):
287-
if not self.keepliveqrdecoding:
314+
if not self.scanqr_mode:
288315
self.start_qr_decoding()
289316
else:
290317
self.stop_qr_decoding()
@@ -311,11 +338,10 @@ def zoom_button_click(self, e):
311338
print(f"self.cam.set_res_raw returned {result}")
312339

313340
def open_settings(self):
314-
intent = Intent(activity_class=CameraSettingsActivity, extras={"prefs": self.prefs, "use_webcam": self.use_webcam, "scanqr_mode": self.scanqr_mode})
341+
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})
315342
self.startActivity(intent)
316343

317344
def try_capture(self, event):
318-
#print("capturing camera frame")
319345
try:
320346
if self.use_webcam:
321347
self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565" if self.colormode else "grayscale")
@@ -328,7 +354,7 @@ def try_capture(self, event):
328354
self.image_dsc.data = self.current_cam_buffer
329355
#self.image.invalidate() # does not work so do this:
330356
self.image.set_src(self.image_dsc)
331-
if self.keepliveqrdecoding:
357+
if self.scanqr_mode:
332358
self.qrdecode_one()
333359
if not self.use_webcam:
334360
self.cam.free_buffer() # After QR decoding, free the old buffer, otherwise the camera doesn't provide a new one

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

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from mpos.config import SharedPreferences
77
from mpos.content.intent import Intent
88

9-
#from camera_app import CameraApp
10-
119
class CameraSettingsActivity(Activity):
1210
"""Settings activity for comprehensive camera configuration."""
1311

14-
PACKAGE = "com.micropythonos.camera"
12+
DEFAULT_WIDTH = 320 # 240 would be better but webcam doesn't support this (yet)
13+
DEFAULT_HEIGHT = 240
14+
DEFAULT_COLORMODE = True
15+
DEFAULT_SCANQR_WIDTH = 960
16+
DEFAULT_SCANQR_HEIGHT = 960
17+
DEFAULT_SCANQR_COLORMODE = False
1518

1619
# Original: { 2560, 1920, 0, 0, 2623, 1951, 32, 16, 2844, 1968 }
1720
# Worked for digital zoom in C: { 2560, 1920, 0, 0, 2623, 1951, 992, 736, 2844, 1968 }
@@ -69,8 +72,8 @@ class CameraSettingsActivity(Activity):
6972

7073
# These are taken from the Intent:
7174
use_webcam = False
72-
scanqr_mode = False
7375
prefs = None
76+
scanqr_mode = False
7477

7578
# Widgets:
7679
button_cont = None
@@ -84,9 +87,9 @@ def __init__(self):
8487
self.resolutions = []
8588

8689
def onCreate(self):
87-
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
8890
self.use_webcam = self.getIntent().extras.get("use_webcam")
8991
self.prefs = self.getIntent().extras.get("prefs")
92+
self.scanqr_mode = self.getIntent().extras.get("scanqr_mode")
9093
if self.use_webcam:
9194
self.resolutions = self.WEBCAM_RESOLUTIONS
9295
print("Using webcam resolutions")
@@ -228,23 +231,29 @@ def add_buttons(self, parent):
228231
button_cont.set_style_border_width(0, 0)
229232

230233
save_button = lv.button(button_cont)
231-
save_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
234+
save_button.set_size(lv.SIZE_CONTENT, lv.SIZE_CONTENT)
232235
save_button.align(lv.ALIGN.BOTTOM_LEFT, 0, 0)
233236
save_button.add_event_cb(lambda e: self.save_and_close(), lv.EVENT.CLICKED, None)
234237
save_label = lv.label(save_button)
235-
save_label.set_text("Save")
238+
savetext = "Save"
239+
if self.scanqr_mode:
240+
savetext += " QR tweaks"
241+
save_label.set_text(savetext)
236242
save_label.center()
237243

238244
cancel_button = lv.button(button_cont)
239245
cancel_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
240-
cancel_button.align(lv.ALIGN.BOTTOM_MID, 0, 0)
246+
if self.scanqr_mode:
247+
cancel_button.align(lv.ALIGN.BOTTOM_MID, mpos.ui.pct_of_display_width(10), 0)
248+
else:
249+
cancel_button.align(lv.ALIGN.BOTTOM_MID, 0, 0)
241250
cancel_button.add_event_cb(lambda e: self.finish(), lv.EVENT.CLICKED, None)
242251
cancel_label = lv.label(cancel_button)
243252
cancel_label.set_text("Cancel")
244253
cancel_label.center()
245254

246255
erase_button = lv.button(button_cont)
247-
erase_button.set_size(mpos.ui.pct_of_display_width(25), lv.SIZE_CONTENT)
256+
erase_button.set_size(mpos.ui.pct_of_display_width(20), lv.SIZE_CONTENT)
248257
erase_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
249258
erase_button.add_event_cb(lambda e: self.erase_and_close(), lv.EVENT.CLICKED, None)
250259
erase_label = lv.label(erase_button)
@@ -259,16 +268,22 @@ def create_basic_tab(self, tab, prefs):
259268
tab.set_style_pad_all(1, 0)
260269

261270
# Color Mode
262-
colormode = prefs.get_bool("colormode", False)
271+
colormode = prefs.get_bool("colormode", False if self.scanqr_mode else True)
263272
checkbox, cont = self.create_checkbox(tab, "Color Mode (slower)", colormode, "colormode")
264273
self.ui_controls["colormode"] = checkbox
265274

266275
# Resolution dropdown
267-
current_resolution = prefs.get_string("resolution", "320x240")
276+
print(f"self.scanqr_mode: {self.scanqr_mode}")
277+
current_resolution_width = prefs.get_string("resolution_width", self.DEFAULT_SCANQR_WIDTH if self.scanqr_mode else self.DEFAULT_WIDTH)
278+
current_resolution_height = prefs.get_string("resolution_width", self.DEFAULT_SCANQR_HEIGHT if self.scanqr_mode else self.DEFAULT_HEIGHT)
279+
dropdown_value = f"{current_resolution_width}x{current_resolution_height}"
280+
print(f"looking for {dropdown_value}")
268281
resolution_idx = 0
269282
for idx, (_, value) in enumerate(self.resolutions):
270-
if value == current_resolution:
283+
print(f"got {value}")
284+
if value == dropdown_value:
271285
resolution_idx = idx
286+
print(f"found it! {idx}")
272287
break
273288

274289
dropdown, cont = self.create_dropdown(tab, "Resolution:", self.resolutions, resolution_idx, "resolution")
@@ -520,7 +535,7 @@ def create_raw_tab(self, tab, prefs):
520535
self.add_buttons(tab)
521536

522537
def erase_and_close(self):
523-
SharedPreferences(self.PACKAGE).edit().remove_all().commit()
538+
self.prefs.edit().remove_all().commit()
524539
self.setResult(True, {"settings_changed": True})
525540
self.finish()
526541

@@ -550,9 +565,14 @@ def save_and_close(self):
550565
selected_idx = control.get_selected()
551566
option_values = metadata.get("option_values", [])
552567
if pref_key == "resolution":
553-
# Resolution stored as string
554-
value = option_values[selected_idx]
555-
editor.put_string(pref_key, value)
568+
try:
569+
# Resolution stored as 2 ints
570+
value = option_values[selected_idx]
571+
width_str, height_str = value.split('x')
572+
editor.put_int("resolution_width", int(width_str))
573+
editor.put_int("resolution_height", int(height_str))
574+
except Exception as e:
575+
print(f"Error parsing resolution '{value}': {e}")
556576
else:
557577
# Other dropdowns store integer enum values
558578
value = option_values[selected_idx]

internal_filesystem/lib/mpos/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def load(self):
2828
try:
2929
with open(self.filepath, 'r') as f:
3030
self.data = ujson.load(f)
31-
print(f"load: Loaded preferences: {self.data}")
31+
print(f"load: Loaded preferences from {self.filepath}: {self.data}")
3232
except Exception as e:
3333
print(f"SharedPreferences.load didn't find preferences: {e}")
3434
self.data = {}

0 commit comments

Comments
 (0)