Skip to content

Commit 4a7ef48

Browse files
committed
Improves async camera wrapper
Adds property-based attribute delegation in the async camera wrapper class to work around a MicroPython subclassing bug, ensuring consistent attribute access. Refines UI styling in the HTML example for better alignment and updates documentation for clarity, including deprecation notes for legacy method prefixes. Removes runtime deprecation warnings from native getters/setters to streamline output, as needed for workaround.
1 parent 458d09c commit 4a7ef48

File tree

5 files changed

+211
-9
lines changed

5 files changed

+211
-9
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ If you want to play arround with AI, take a look at the [micropython binding for
1313

1414
## Content
1515

16-
- [Precompiled firmware (the easy way)](#Precompiled-firmware-the-easy-way)
16+
- [Precompiled firmware (the easy way)](#precompiled-firmware-the-easy-way)
1717
- [Using the API](#using-the-api)
1818
- [Importing the camera module](#importing-the-camera-module)
1919
- [Creating a camera object](#creating-a-camera-object)
@@ -22,7 +22,7 @@ If you want to play arround with AI, take a look at the [micropython binding for
2222
- [Camera reconfiguration](#camera-reconfiguration)
2323
- [Freeing the buffer](#freeing-the-buffer)
2424
- [Is a frame available](#is-frame-available)
25-
- [Additional methods](#additional-methods)
25+
- [Additional methods and examples](#additional-methods-and-examples)
2626
- [I2C Integration](#i2c-integration)
2727
- [Additional information](#additional-information)
2828
- [Build your custom firmware](#build-your-custom-firmware)
@@ -202,8 +202,8 @@ camera.vflip = True #Enable vertical flip
202202
```
203203

204204
See autocompletions in Thonny in order to see the list of methods.
205-
If you want more insights in the methods and what they actually do, you can find a very good documentation [here](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html).
206-
Note: "get_" and "set_" prefixed methods are deprecated.
205+
If you want more insights in the methods and what they actually do, you can find a very good documentation [in circuitpython](https://docs.circuitpython.org/en/latest/shared-bindings/espcamera/index.html).
206+
Note: "get_" and "set_" prefixed methods are deprecated and will be removed in a future release.
207207

208208
Take also a look in the examples folder.
209209

@@ -332,6 +332,7 @@ Example for Xiao sense:
332332
#define MICROPY_CAMERA_GRAB_MODE (1) // 0=WHEN_EMPTY (might have old data, but less resources), 1=LATEST (best, but more resources)
333333

334334
```
335+
335336
#### Customize additional camera settings
336337

337338
If you want to customize additional camera setting or reduce the firmware size by removing support for unused camera sensors, then take a look at the kconfig file of the esp32-camera driver and specify these on the sdkconfig file of your board.
@@ -385,6 +386,7 @@ Using fb_count=2 theoretically can double the FPS (see JPEG with fb_count=2). Th
385386
## Troubleshooting
386387

387388
You can find information on the following sites:
389+
388390
- [ESP-FAQ](https://docs.espressif.com/projects/esp-faq/en/latest/application-solution/camera-application.html)
389391
- [ChatGPT](https://chatgpt.com/)
390392
- [Issues in here](https://github.com/cnadler86/micropython-camera-API/issues?q=is%3Aissue)

examples/CameraSettings.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,22 @@
3232
}
3333
.setting {
3434
margin-bottom: 10px;
35+
display: flex;
36+
align-items: center;
37+
justify-content: space-between;
3538
}
3639
.setting label {
37-
display: block;
3840
font-size: 14px;
39-
margin-bottom: 3px;
41+
flex: 1;
4042
}
4143
.setting input[type="range"],
4244
.setting select {
4345
width: 100%;
4446
}
47+
.setting input[type="checkbox"] {
48+
width: auto;
49+
margin-left: 10px;
50+
}
4551
.hidden {
4652
display: none;
4753
}

examples/CameraSettings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
while not station.isconnected():
1616
time.sleep(1)
1717

18-
print(f'Connected! IP: {station.ifconfig()[0]}. Open this IP in your browser')
18+
print(f'Connected! IP: http://{station.ifconfig()[0]}. Open it in your browser')
1919

2020
try:
2121
with open("CameraSettings.html", 'r') as file:

src/acamera.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,206 @@
33
from camera import FrameSize, PixelFormat, GainCeiling, GrabMode
44

55
class Camera(_Camera):
6+
"""
7+
Async wrapper for Camera class.
8+
9+
Workaround for MicroPython bug: Python subclasses of native types with attr handlers
10+
cannot properly set attributes. The attr handler is bypassed and values are stored in
11+
the instance __dict__ instead. We work around this by explicitly defining properties
12+
that delegate to the get_*/set_* methods which are in locals_dict and work correctly.
13+
14+
See: https://github.com/micropython/micropython/issues/18592
15+
"""
16+
617
async def acapture(self):
718
self.free_buffer() # Free the buffer so the camera task grabs a new frame
819
while not self.frame_available():
920
await asyncio.sleep(0) # Yield control to the event loop
1021
return self.capture()
1122

23+
# Workaround: Define properties that delegate to get_/set_ methods
24+
# These methods are in locals_dict and work correctly with subclasses
25+
26+
@property
27+
def frame_size(self):
28+
return self.get_frame_size()
29+
@frame_size.setter
30+
def frame_size(self, value):
31+
self.set_frame_size(value)
32+
33+
@property
34+
def contrast(self):
35+
return self.get_contrast()
36+
@contrast.setter
37+
def contrast(self, value):
38+
self.set_contrast(value)
39+
40+
@property
41+
def brightness(self):
42+
return self.get_brightness()
43+
@brightness.setter
44+
def brightness(self, value):
45+
self.set_brightness(value)
46+
47+
@property
48+
def saturation(self):
49+
return self.get_saturation()
50+
@saturation.setter
51+
def saturation(self, value):
52+
self.set_saturation(value)
53+
54+
@property
55+
def sharpness(self):
56+
return self.get_sharpness()
57+
@sharpness.setter
58+
def sharpness(self, value):
59+
self.set_sharpness(value)
60+
61+
@property
62+
def denoise(self):
63+
return self.get_denoise()
64+
@denoise.setter
65+
def denoise(self, value):
66+
self.set_denoise(value)
67+
68+
@property
69+
def gainceiling(self):
70+
return self.get_gainceiling()
71+
@gainceiling.setter
72+
def gainceiling(self, value):
73+
self.set_gainceiling(value)
74+
75+
@property
76+
def quality(self):
77+
return self.get_quality()
78+
@quality.setter
79+
def quality(self, value):
80+
self.set_quality(value)
81+
82+
@property
83+
def colorbar(self):
84+
return self.get_colorbar()
85+
@colorbar.setter
86+
def colorbar(self, value):
87+
self.set_colorbar(value)
88+
89+
@property
90+
def whitebal(self):
91+
return self.get_whitebal()
92+
@whitebal.setter
93+
def whitebal(self, value):
94+
self.set_whitebal(value)
95+
96+
@property
97+
def gain_ctrl(self):
98+
return self.get_gain_ctrl()
99+
@gain_ctrl.setter
100+
def gain_ctrl(self, value):
101+
self.set_gain_ctrl(value)
102+
103+
@property
104+
def exposure_ctrl(self):
105+
return self.get_exposure_ctrl()
106+
@exposure_ctrl.setter
107+
def exposure_ctrl(self, value):
108+
self.set_exposure_ctrl(value)
109+
110+
@property
111+
def hmirror(self):
112+
return self.get_hmirror()
113+
@hmirror.setter
114+
def hmirror(self, value):
115+
self.set_hmirror(value)
116+
117+
@property
118+
def vflip(self):
119+
return self.get_vflip()
120+
@vflip.setter
121+
def vflip(self, value):
122+
self.set_vflip(value)
123+
124+
@property
125+
def aec2(self):
126+
return self.get_aec2()
127+
@aec2.setter
128+
def aec2(self, value):
129+
self.set_aec2(value)
130+
131+
@property
132+
def awb_gain(self):
133+
return self.get_awb_gain()
134+
@awb_gain.setter
135+
def awb_gain(self, value):
136+
self.set_awb_gain(value)
137+
138+
@property
139+
def agc_gain(self):
140+
return self.get_agc_gain()
141+
@agc_gain.setter
142+
def agc_gain(self, value):
143+
self.set_agc_gain(value)
144+
145+
@property
146+
def aec_value(self):
147+
return self.get_aec_value()
148+
@aec_value.setter
149+
def aec_value(self, value):
150+
self.set_aec_value(value)
151+
152+
@property
153+
def special_effect(self):
154+
return self.get_special_effect()
155+
@special_effect.setter
156+
def special_effect(self, value):
157+
self.set_special_effect(value)
158+
159+
@property
160+
def wb_mode(self):
161+
return self.get_wb_mode()
162+
@wb_mode.setter
163+
def wb_mode(self, value):
164+
self.set_wb_mode(value)
165+
166+
@property
167+
def ae_level(self):
168+
return self.get_ae_level()
169+
@ae_level.setter
170+
def ae_level(self, value):
171+
self.set_ae_level(value)
172+
173+
@property
174+
def dcw(self):
175+
return self.get_dcw()
176+
@dcw.setter
177+
def dcw(self, value):
178+
self.set_dcw(value)
179+
180+
@property
181+
def bpc(self):
182+
return self.get_bpc()
183+
@bpc.setter
184+
def bpc(self, value):
185+
self.set_bpc(value)
186+
187+
@property
188+
def wpc(self):
189+
return self.get_wpc()
190+
@wpc.setter
191+
def wpc(self, value):
192+
self.set_wpc(value)
193+
194+
@property
195+
def raw_gma(self):
196+
return self.get_raw_gma()
197+
@raw_gma.setter
198+
def raw_gma(self, value):
199+
self.set_raw_gma(value)
200+
201+
@property
202+
def lenc(self):
203+
return self.get_lenc()
204+
@lenc.setter
205+
def lenc(self, value):
206+
self.set_lenc(value)
207+
12208
__all__ = ['Camera', 'FrameSize', 'PixelFormat', 'GainCeiling', 'GrabMode']

src/modcamera_api.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,15 +518,13 @@ static void camera_obj_property(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
518518
// Camera sensor property functions
519519
#define CREATE_GETTER(property, get_function) \
520520
static mp_obj_t camera_get_##property(const mp_obj_t self_in) { \
521-
mp_warning(NULL, "get_" #property "() is deprecated. Use the " #property " property instead."); \
522521
mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \
523522
return get_function(mp_camera_hal_get_##property(self)); \
524523
} \
525524
static MP_DEFINE_CONST_FUN_OBJ_1(camera_get_##property##_obj, camera_get_##property);
526525

527526
#define CREATE_SETTER(property, set_conversion) \
528527
static mp_obj_t camera_set_##property(const mp_obj_t self_in, const mp_obj_t arg) { \
529-
mp_warning(NULL, "set_" #property "() is deprecated. Use the " #property " property instead."); \
530528
mp_camera_obj_t *self = MP_OBJ_TO_PTR(self_in); \
531529
mp_camera_hal_set_##property(self, set_conversion(arg)); \
532530
if (mp_camera_hal_get_##property(self) != set_conversion(arg)) { \

0 commit comments

Comments
 (0)