|
@@ -44,6 +44,11 @@ void HTTPServer::setupWiFiAP(const char* ssid, const char* password,
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
void HTTPServer::setupRoutes()
|
|
void HTTPServer::setupRoutes()
|
|
|
{
|
|
{
|
|
|
|
|
+ // Root endpoint - serves home page
|
|
|
|
|
+ server->on("/", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
|
|
|
+ handleRoot(request);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// Focus endpoint
|
|
// Focus endpoint
|
|
|
server->on("/api/focus", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
server->on("/api/focus", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
|
handleFocus(request);
|
|
handleFocus(request);
|
|
@@ -65,11 +70,216 @@ void HTTPServer::setupRoutes()
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Health check endpoint
|
|
// Health check endpoint
|
|
|
- server->on("/get", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
|
|
|
|
|
+ server->on("/api/healthcheck", HTTP_GET, [this](AsyncWebServerRequest *request) {
|
|
|
handleHealthCheck(request);
|
|
handleHealthCheck(request);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/// <summary>
|
|
|
|
|
+/// Handles GET / request to serve the home page.
|
|
|
|
|
+/// Returns a simple HTML interface for controlling the camera remote.
|
|
|
|
|
+/// Provides buttons for focus, photo capture, reset, and multiple shot controls.
|
|
|
|
|
+/// </summary>
|
|
|
|
|
+void HTTPServer::handleRoot(AsyncWebServerRequest *request)
|
|
|
|
|
+{
|
|
|
|
|
+ const char* html = R"rawliteral(
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html>
|
|
|
|
|
+<head>
|
|
|
|
|
+ <title>Minolta Remote Control</title>
|
|
|
|
|
+ <style>
|
|
|
|
|
+ * { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
+ body {
|
|
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .container {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 15px;
|
|
|
|
|
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
|
|
|
+ padding: 30px;
|
|
|
|
|
+ max-width: 500px;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ h1 {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section {
|
|
|
|
|
+ margin-bottom: 25px;
|
|
|
|
|
+ padding-bottom: 25px;
|
|
|
|
|
+ border-bottom: 1px solid #eee;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section:last-child { border-bottom: none; }
|
|
|
|
|
+ .section h2 {
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+ button {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-focus {
|
|
|
|
|
+ background: #4CAF50;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-focus:hover { background: #45a049; }
|
|
|
|
|
+ .btn-photo {
|
|
|
|
|
+ background: #2196F3;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-photo:hover { background: #0b7dda; }
|
|
|
|
|
+ .btn-reset {
|
|
|
|
|
+ background: #f44336;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-reset:hover { background: #da190b; }
|
|
|
|
|
+ .input-group {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border: 1px solid #ddd;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-multiple {
|
|
|
|
|
+ background: #FF9800;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ }
|
|
|
|
|
+ .btn-multiple:hover { background: #e68900; }
|
|
|
|
|
+ .status {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ .status.success {
|
|
|
|
|
+ background: #d4edda;
|
|
|
|
|
+ color: #155724;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+ .status.error {
|
|
|
|
|
+ background: #f8d7da;
|
|
|
|
|
+ color: #721c24;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <h1>📷 Minolta Remote</h1>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Basic Controls</h2>
|
|
|
|
|
+ <button class="btn-focus" onclick="sendRequest('/api/focus')">🎯 Focus</button>
|
|
|
|
|
+ <button class="btn-photo" onclick="sendRequest('/api/takePhoto')">📸 Take Photo</button>
|
|
|
|
|
+ <button class="btn-reset" onclick="sendRequest('/api/reset')">🔄 Reset</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Photo Hold Duration</h2>
|
|
|
|
|
+ <input type="number" id="msec" min="100" max="5000" value="1000" placeholder="Duration (ms)">
|
|
|
|
|
+ <button class="btn-photo" onclick="takePhotoWithDelay()">📸 Take with Delay</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Multiple Shots</h2>
|
|
|
|
|
+ <div class="input-group">
|
|
|
|
|
+ <input type="number" id="count" min="1" max="100" value="3" placeholder="Count">
|
|
|
|
|
+ <input type="number" id="delay" min="100" max="5000" value="500" placeholder="Delay (ms)">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="input-group">
|
|
|
|
|
+ <input type="number" id="msecMulti" min="100" max="5000" value="1000" placeholder="Duration (ms)">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button class="btn-multiple" onclick="multipleShoot()">🎬 Multiple Shots</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div id="status" class="status"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ function sendRequest(endpoint) {
|
|
|
|
|
+ fetch(endpoint)
|
|
|
|
|
+ .then(response => {
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ showStatus('✓ Success: ' + endpoint, 'success');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showStatus('✗ Error: ' + response.statusText, 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(error => showStatus('✗ Connection Error', 'error'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function takePhotoWithDelay() {
|
|
|
|
|
+ const msec = document.getElementById('msec').value;
|
|
|
|
|
+ fetch('/api/takePhoto?msec=' + msec)
|
|
|
|
|
+ .then(response => {
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ showStatus('✓ Photo taken (' + msec + 'ms)', 'success');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showStatus('✗ Error taking photo', 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(error => showStatus('✗ Connection Error', 'error'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function multipleShoot() {
|
|
|
|
|
+ const count = document.getElementById('count').value;
|
|
|
|
|
+ const delay = document.getElementById('delay').value;
|
|
|
|
|
+ const msec = document.getElementById('msecMulti').value;
|
|
|
|
|
+
|
|
|
|
|
+ if (count < 1 || count > 100) {
|
|
|
|
|
+ showStatus('✗ Count must be between 1 and 100', 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fetch('/api/multiple?count=' + count + '&delay=' + delay + '&msec=' + msec)
|
|
|
|
|
+ .then(response => {
|
|
|
|
|
+ if (response.ok) {
|
|
|
|
|
+ showStatus('✓ ' + count + ' shots queued', 'success');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showStatus('✗ Error taking photos', 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(error => showStatus('✗ Connection Error', 'error'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function showStatus(message, type) {
|
|
|
|
|
+ const status = document.getElementById('status');
|
|
|
|
|
+ status.textContent = message;
|
|
|
|
|
+ status.className = 'status ' + type;
|
|
|
|
|
+
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ status.className = 'status';
|
|
|
|
|
+ }, 3000);
|
|
|
|
|
+ }
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|
|
|
|
|
+)rawliteral";
|
|
|
|
|
+ request->send(200, "text/html", html);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Handles GET /api/focus request to perform a half press (focus) operation.
|
|
/// Handles GET /api/focus request to perform a half press (focus) operation.
|
|
|
/// Logs the request, activates the camera focus, and responds with success.
|
|
/// Logs the request, activates the camera focus, and responds with success.
|
|
@@ -138,13 +348,13 @@ void HTTPServer::handleMultiple(AsyncWebServerRequest *request)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
-/// Handles GET /get request as a simple health check endpoint.
|
|
|
|
|
|
|
+/// Handles GET /api/healthcheck request as a simple health check endpoint.
|
|
|
/// Returns a plain text "Is working" response to verify server connectivity.
|
|
/// Returns a plain text "Is working" response to verify server connectivity.
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
void HTTPServer::handleHealthCheck(AsyncWebServerRequest *request)
|
|
void HTTPServer::handleHealthCheck(AsyncWebServerRequest *request)
|
|
|
{
|
|
{
|
|
|
- onRequestStart("get");
|
|
|
|
|
- onRequestEnd("get");
|
|
|
|
|
|
|
+ onRequestStart("api/healthcheck");
|
|
|
|
|
+ onRequestEnd("api/healthcheck");
|
|
|
request->send(200, "text/plain", "Is working");
|
|
request->send(200, "text/plain", "Is working");
|
|
|
}
|
|
}
|
|
|
|
|
|