Skip to content

Commit e7d68e6

Browse files
committed
config: rewrite Settings update API
1 parent 17d056f commit e7d68e6

File tree

12 files changed

+155
-185
lines changed

12 files changed

+155
-185
lines changed

examples/AsyncCam/AsyncCam.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
#include <esp32cam.h>
99

1010
extern esp32cam::Resolution initialResolution;
11-
extern esp32cam::Resolution currentResolution;
1211

1312
extern AsyncWebServer server;
1413

examples/AsyncCam/AsyncCam.ino

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ static const char* WIFI_SSID = "my-ssid";
55
static const char* WIFI_PASS = "my-pass";
66

77
esp32cam::Resolution initialResolution;
8-
esp32cam::Resolution currentResolution;
98

109
AsyncWebServer server(80);
1110

@@ -31,7 +30,6 @@ setup() {
3130
using namespace esp32cam;
3231

3332
initialResolution = Resolution::find(1024, 768);
34-
currentResolution = initialResolution;
3533

3634
Config cfg;
3735
cfg.setPins(pins::AiThinker);

examples/AsyncCam/handlers.cpp

Lines changed: 52 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,12 @@ static const char FRONTPAGE[] = R"EOT(
66
<title>esp32cam AsyncCam example</title>
77
<body>
88
<h1>esp32cam AsyncCam example</h1>
9-
<form><p>
10-
Resolution
11-
<select id="resolution" required>%RESOLUTION_OPTIONS%</select>
12-
<input type="submit" value="change">
9+
<form id="update"><p>
10+
<select name="resolution" required>%resolution%</select>
11+
%hmirror%
12+
%vflip%
13+
<input type="submit" value="update">
1314
</p></form>
14-
<p id="toggles">
15-
<label><input type="checkbox" data-act="hmirror">hmirror</label>
16-
<label><input type="checkbox" data-act="vflip">vflip</label>
17-
</p>
1815
<p id="controls">
1916
<button data-act="mjpeg">show Motion JPEG stream</button>
2017
<button data-act="jpg">show still JPEG image</button>
@@ -32,36 +29,18 @@ async function fetchText(uri, init) {
3229
}
3330
3431
const $display = document.querySelector("#display");
35-
const $resolution = document.querySelector("#resolution");
36-
37-
$resolution.form.addEventListener("submit", async (evt) => {
32+
document.querySelector("#update").addEventListener("submit", async (evt) => {
3833
evt.preventDefault();
39-
const [width, height] = $resolution.value.split("x");
4034
try {
41-
await fetchText("/change-resolution.cgi", {
35+
await fetchText("/update.cgi", {
4236
method: "POST",
43-
body: new URLSearchParams({ width, height }),
37+
body: new URLSearchParams(new FormData(evt.target)),
4438
});
4539
} catch (err) {
4640
$display.textContent = err.toString();
4741
}
4842
});
4943
50-
for (const $ctrl of document.querySelectorAll("#toggles input[type=checkbox]")) {
51-
$ctrl.addEventListener("change", async (evt) => {
52-
evt.preventDefault();
53-
const act = evt.target.getAttribute("data-act");
54-
try {
55-
await fetchText(`/set-${act}.cgi`, {
56-
method: "POST",
57-
body: new URLSearchParams({ enable: Number(evt.target.checked) }),
58-
});
59-
} catch (err) {
60-
$display.textContent = err.toString();
61-
}
62-
});
63-
}
64-
6544
for (const $ctrl of document.querySelectorAll("#controls button")) {
6645
$ctrl.addEventListener("click", (evt) => {
6746
evt.preventDefault();
@@ -83,78 +62,66 @@ for (const $ctrl of document.querySelectorAll("#controls button")) {
8362
)EOT";
8463

8564
static String
86-
rewriteFrontpage(const String& var) {
65+
rewriteFrontpage(const esp32cam::Settings& settings, const String& var) {
8766
StreamString b;
88-
if (var == "RESOLUTION_OPTIONS") {
67+
68+
if (var == "resolution") {
8969
for (const auto& r : esp32cam::Camera.listResolutions()) {
90-
b.print("<option");
91-
if (r == currentResolution) {
92-
b.print(" selected");
93-
}
94-
if (r > initialResolution) {
95-
b.print(" disabled");
96-
}
97-
b.print('>');
70+
b.printf("<option value=\"%d\"%s>", r.as<int>(),
71+
r > initialResolution ? " disabled"
72+
: r == settings.resolution ? " selected"
73+
: "");
9874
b.print(r);
9975
b.print("</option>");
10076
}
10177
}
102-
return b;
103-
}
10478

105-
void
106-
addRequestHandlers() {
107-
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
108-
request->send(200, "text/html", reinterpret_cast<const uint8_t*>(FRONTPAGE), sizeof(FRONTPAGE),
109-
rewriteFrontpage);
110-
});
79+
#define SETTING_BOOL(MEM) \
80+
else if (var == #MEM) { \
81+
b.printf("<label><input type=\"checkbox\" name=\"" #MEM "\" value=\"1\"%s>" #MEM "</label>", \
82+
settings.MEM ? " checked" : ""); \
83+
}
11184

112-
server.on("/robots.txt", HTTP_GET, [](AsyncWebServerRequest* request) {
113-
request->send(200, "text/plain", "User-Agent: *\nDisallow: /\n");
114-
});
85+
SETTING_BOOL(hmirror)
86+
SETTING_BOOL(vflip)
11587

116-
server.on("/change-resolution.cgi", HTTP_POST, [](AsyncWebServerRequest* request) {
117-
long width = request->arg("width").toInt();
118-
long height = request->arg("height").toInt();
119-
auto r = esp32cam::Camera.listResolutions().find(width, height);
120-
if (!(r.isValid() && r.getWidth() == width && r.getHeight() == height)) {
121-
request->send(404, "text/plain", "non-existent resolution\n");
122-
return;
123-
}
88+
#undef SETTING_BOOL
12489

125-
if (!esp32cam::Camera.changeResolution(r, 0)) {
126-
Serial.printf("changeResolution(%ld,%ld) failure\n", width, height);
127-
request->send(500, "text/plain", "changeResolution error\n");
128-
return;
129-
}
90+
return b;
91+
}
13092

131-
currentResolution = r;
132-
Serial.printf("changeResolution(%ld,%ld) success\n", width, height);
133-
StreamString b;
134-
b.print(currentResolution);
135-
request->send(200, "text/plain", static_cast<String>(b));
136-
});
93+
static void
94+
handleFrontpage(AsyncWebServerRequest* request) {
95+
auto settings = esp32cam::Camera.status();
96+
request->send(200, "text/html", reinterpret_cast<const uint8_t*>(FRONTPAGE), sizeof(FRONTPAGE),
97+
[=](const String& var) { return rewriteFrontpage(settings, var); });
98+
}
13799

138-
server.on("/set-hmirror.cgi", HTTP_POST, [](AsyncWebServerRequest* request) {
139-
long enable = request->arg("enable").toInt();
140-
if (!esp32cam::Camera.update(esp32cam::SetHmirror(enable != 0))) {
141-
request->send(500, "text/plain", "SetHmirror error\n");
142-
return;
143-
}
144-
Serial.printf("SetHmirror(%ld) success\n", enable);
145-
request->send(200, "text/plain", "SetHmirror success\n");
100+
static void
101+
handleUpdate(AsyncWebServerRequest* request) {
102+
bool ok = esp32cam::Camera.update([=](esp32cam::Settings& settings) {
103+
settings.resolution =
104+
esp32cam::Resolution(static_cast<int>(request->arg("resolution").toInt()));
105+
settings.hmirror = request->arg("hmirror").toInt() != 0;
106+
settings.vflip = request->arg("vflip").toInt() != 0;
146107
});
147108

148-
server.on("/set-vflip.cgi", HTTP_POST, [](AsyncWebServerRequest* request) {
149-
long enable = request->arg("enable").toInt();
150-
if (!esp32cam::Camera.update(esp32cam::SetVflip(enable != 0))) {
151-
request->send(500, "text/plain", "SetVflip error\n");
152-
return;
153-
}
154-
Serial.printf("SetVflip(%ld) success\n", enable);
155-
request->send(200, "text/plain", "SetVflip success\n");
109+
if (!ok) {
110+
request->send(500, "text/plain", "update settings error\n");
111+
return;
112+
}
113+
request->send(204);
114+
}
115+
116+
void
117+
addRequestHandlers() {
118+
server.on("/robots.txt", HTTP_GET, [](AsyncWebServerRequest* request) {
119+
request->send(200, "text/plain", "User-Agent: *\nDisallow: /\n");
156120
});
157121

122+
server.on("/", HTTP_GET, handleFrontpage);
123+
server.on("/update.cgi", HTTP_POST, handleUpdate);
124+
158125
server.on("/cam.jpg", esp32cam::asyncweb::handleStill);
159126
server.on("/cam.mjpeg", esp32cam::asyncweb::handleMjpeg);
160127
}

examples/WifiCam/WifiCam.ino

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ void
1212
setup() {
1313
Serial.begin(115200);
1414
Serial.println();
15+
esp32cam::setLogger(Serial);
1516
delay(1000);
1617

1718
WiFi.persistent(false);

examples/WifiCam/handlers.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ addRequestHandlers() {
115115
return;
116116
}
117117

118-
if (!esp32cam::Camera.changeResolution(r)) {
119-
Serial.printf("changeResolution(%ld,%ld) failure\n", width, height);
120-
server.send(500, "text/plain", "changeResolution error\n");
118+
if (!esp32cam::Camera.update([=](esp32cam::Settings& settings) { settings.resolution = r; },
119+
500)) {
120+
server.send(500, "text/plain", "update resolution error\n");
121+
return;
121122
}
122-
Serial.printf("changeResolution(%ld,%ld) success\n", width, height);
123123

124124
if (format == "bmp") {
125125
serveStill(true);

src/esp32cam/camera.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
namespace esp32cam {
77

8+
// extern ESP32CAM_SPECIALIZE_SENSOR_SETTING(framesize_t, framesize_t);
9+
10+
// static SensorSetting<framesize_t, framesize_t> frameSize(ESP32CAM_SENSOR_OFFSETS(framesize));
11+
812
Print* LogOutput = nullptr;
913
CameraClass Camera;
1014

@@ -54,15 +58,6 @@ CameraClass::changeResolution(const Resolution& resolution, int sleepFor) {
5458
return true;
5559
}
5660

57-
bool
58-
CameraClass::update(const SensorSetter& setter) {
59-
sensor_t* sensor = esp_camera_sensor_get();
60-
if (sensor == nullptr) {
61-
return false;
62-
}
63-
return setter(sensor);
64-
}
65-
6661
std::unique_ptr<Frame>
6762
CameraClass::capture() {
6863
camera_fb_t* fb = esp_camera_fb_get();

src/esp32cam/camera.hpp

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include "config.hpp"
55
#include "mjpeg.hpp"
6-
#include "setters.hpp"
6+
#include <functional>
77

88
namespace esp32cam {
99

@@ -32,20 +32,41 @@ class CameraClass {
3232
* @pre Camera is enabled.
3333
* @param resolution new resolution; must be no higher than initial resolution.
3434
* @param sleepFor how long to wait for stabilization (millis).
35+
* @deprecated Use @c update instead.
3536
*/
3637
bool changeResolution(const Resolution& resolution, int sleepFor = 500);
3738

3839
/**
39-
* @brief Update camera configuration.
40-
* @param setter See `setters.hpp`.
40+
* @brief Retrieve runtime settings.
41+
* @pre Camera is enabled.
42+
*/
43+
Settings status() const;
44+
45+
/**
46+
* @brief Update runtime settings.
47+
* @param settings updated settings.
48+
* @param sleepFor how long to wait for stabilization (millis).
49+
* @pre Camera is enabled.
50+
* @post Camera may be left in inconsistent state in case of failure.
51+
* @return whether success.
52+
*/
53+
bool update(const Settings& settings, int sleepFor = 0);
54+
55+
/**
56+
* @brief Update runtime settings using modifier function.
57+
* @param modifier function to modify settings.
58+
* @param sleepFor how long to wait for stabilization (millis).
59+
* @pre Camera is enabled.
60+
* @post Camera may be left in inconsistent state in case of failure.
4161
* @return whether success.
42-
*
43-
* Example:
44-
* @code
45-
* bool ok = Camera.update(SetVflip(true));
46-
* @endcode
4762
*/
48-
bool update(const SensorSetter& setter);
63+
template<typename Fn>
64+
std::enable_if_t<std::is_assignable_v<std::function<void(Settings&)>, Fn>, bool> //
65+
update(const Fn& modifier, int sleepFor = 0) {
66+
Settings settings = status();
67+
modifier(settings);
68+
return update(settings, sleepFor);
69+
}
4970

5071
/**
5172
* @brief Capture a frame of picture.

src/esp32cam/config.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#include "config.hpp"
2+
#include "camera.hpp"
3+
#include "logger.hpp"
24

35
#include <algorithm>
46
#include <cstring>
@@ -90,4 +92,53 @@ Config::setJpeg(int quality) {
9092
return *this;
9193
}
9294

95+
Settings
96+
CameraClass::status() const {
97+
Settings result;
98+
sensor_t* sensor = esp_camera_sensor_get();
99+
if (sensor == nullptr) {
100+
return result;
101+
}
102+
103+
result.resolution = Resolution(sensor->status.framesize);
104+
result.hmirror = sensor->status.hmirror != 0;
105+
result.vflip = sensor->status.vflip != 0;
106+
return result;
107+
}
108+
109+
bool
110+
CameraClass::update(const Settings& settings, int sleepFor) {
111+
sensor_t* sensor = esp_camera_sensor_get();
112+
if (sensor == nullptr) {
113+
return false;
114+
}
115+
116+
#define UPDATE(STATUS_MEM, SETTING_MEM, SETTER_TYP) \
117+
do { \
118+
auto prev = sensor->status.STATUS_MEM; \
119+
if (prev != static_cast<decltype(sensor->status.STATUS_MEM)>(settings.SETTING_MEM)) { \
120+
int res = sensor->set_##STATUS_MEM(sensor, static_cast<SETTER_TYP>(settings.SETTING_MEM)); \
121+
ESP32CAM_LOG("update " #STATUS_MEM " %d => %d %s", static_cast<int>(prev), \
122+
static_cast<int>(settings.SETTING_MEM), res == 0 ? "success" : "failure"); \
123+
if (res != 0) { \
124+
return false; \
125+
} \
126+
} \
127+
} while (false)
128+
129+
#define UPDATE1(MEM) UPDATE(MEM, MEM, int)
130+
131+
UPDATE(framesize, resolution.as<framesize_t>(), framesize_t);
132+
UPDATE1(hmirror);
133+
UPDATE1(vflip);
134+
135+
#undef UPDATE
136+
#undef UPDATE1
137+
138+
if (sleepFor > 0) {
139+
delay(sleepFor);
140+
}
141+
return true;
142+
}
143+
93144
} // namespace esp32cam

src/esp32cam/config.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,18 @@ class Config {
5252
friend class CameraClass;
5353
};
5454

55+
/** @brief Camera runtime settings. */
56+
struct Settings {
57+
/** @brief Picture resolution. */
58+
Resolution resolution;
59+
60+
/** @brief Horizontal flip. */
61+
bool hmirror = false;
62+
63+
/** @brief Vertical flip. */
64+
bool vflip = false;
65+
};
66+
5567
} // namespace esp32cam
5668

5769
#endif // ESP32CAM_CONFIG_HPP

0 commit comments

Comments
 (0)