#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <WebServer.h>
#define RED_PIN 16
#define GREEN_PIN 17
#define BLUE_PIN 18
#define LDR_PIN 35
// === Підключення Wi-Fi ===
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// === Дані для API ===
String GEMINI_API_KEY = "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";
String GEMINI_URL = "https://openrouter.ai/api/v1/chat/completions";
String GEMINI_MODEL = "google/gemini-2.5-flash-lite-preview-09-2025";
// === Ініціалізація вебсервера ===
WebServer server(80);
bool aiMode = false; // false — ручний режим
int manualR = 255, manualG = 200, manualB = 180;
unsigned long lastLuxUpdate = 0;
int autoLux = 0;
// === Допоміжні функції ===
void setColor(int r, int g, int b) {
analogWrite(RED_PIN, 255 - r);
analogWrite(GREEN_PIN, 255 - g);
analogWrite(BLUE_PIN, 255 - b);
}
int readLux() {
int sensorValue = analogRead(LDR_PIN);
return map(sensorValue, 0, 4095, 0, 1000);
}
// === Виклик моделі Gemini ===
String queryGemini(String userPrompt) {
WiFiClientSecure client;
client.setInsecure(); // щоб уникнути SSL-помилок
HTTPClient http;
if (!http.begin(client, GEMINI_URL)) {
Serial.println("Помилка ініціалізації HTTP");
return "Error";
}
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + GEMINI_API_KEY);
http.addHeader("HTTP-Referer", "https://wokwi.com");
http.addHeader("X-Title", "ESP32 Smart Nightlight");
DynamicJsonDocument doc(2048);
doc["model"] = GEMINI_MODEL;
JsonArray msgs = doc.createNestedArray("messages");
JsonObject msg = msgs.createNestedObject();
msg["role"] = "user";
msg["content"] = "Suggest RGB color in format 255,200,100 for this: " + userPrompt;
String body;
serializeJson(doc, body);
int code = http.POST(body);
if (code <= 0) {
Serial.printf("HTTP error: %d\n", code);
http.end();
return "Error";
}
String response = http.getString();
http.end();
DynamicJsonDocument res(4096);
deserializeJson(res, response);
String content = res["choices"][0]["message"]["content"].as<String>();
Serial.println("Відповідь AI: " + content);
return content;
}
// === HTML сторінка ===
String webPage() {
String modeText = aiMode ? "Автоматичний (AI)" : "Ручний";
String lux = String(readLux());
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 Ambient Light</title>
<style>
body {
background: linear-gradient(145deg, #101820, #18252f);
color: #f3f3f3;
font-family: 'Segoe UI', sans-serif;
text-align: center;
padding: 40px;
transition: background 1s ease;
}
.box {
background: rgba(25,25,25,0.85);
border-radius: 20px;
padding: 25px;
display: inline-block;
box-shadow: 0 0 25px rgba(0,150,255,0.15);
width: 360px;
}
input[type=range], input[type=text] {
width: 85%;
margin: 8px 0;
}
button {
background: #2196F3;
border: none;
color: white;
padding: 10px 20px;
border-radius: 10px;
cursor: pointer;
transition: 0.3s;
}
button:hover {
background: #0b7dda;
transform: scale(1.07);
}
h1 { color: #64b5f6; }
hr { border: 0; border-top: 1px solid #333; margin: 15px 0; }
</style>
</head>
<body>
<div class='box'>
<h1>ESP32 Ambient Light</h1>
<p>Режим: <b id='mode'>)rawliteral" + modeText + R"rawliteral(</b></p>
<p>Освітленість: <span id='lux'>)rawliteral" + lux + R"rawliteral(</span></p>
<button onclick="toggleMode()">Змінити режим</button>
<hr>
<div id='manual' style='display:)rawliteral" + (aiMode ? "none" : "block") + R"rawliteral(;'>
<p><b>Ручне керування RGB</b></p>
<p>R: <input type='range' min='0' max='255' id='r'></p>
<p>G: <input type='range' min='0' max='255' id='g'></p>
<p>B: <input type='range' min='0' max='255' id='b'></p>
<button onclick='sendColor()'>Встановити колір</button>
</div>
</div>
<script>
async function sendColor(){
let r=document.getElementById('r').value;
let g=document.getElementById('g').value;
let b=document.getElementById('b').value;
await fetch(`/set?r=${r}&g=${g}&b=${b}`);
}
async function toggleMode(){
await fetch('/toggle');
location.reload();
}
async function updateLux(){
let res = await fetch('/lux');
let t = await res.text();
document.getElementById('lux').innerText=t;
let bg = `rgb(${t/5}, ${t/3}, ${t/2})`;
document.body.style.background = `radial-gradient(circle, ${bg}, #111)`;
}
setInterval(updateLux, 3000);
</script>
</body>
</html>
)rawliteral";
return html;
}
// === Обробники запитів ===
void handleRoot() { server.send(200, "text/html", webPage()); }
void handleSetColor() {
manualR = server.arg("r").toInt();
manualG = server.arg("g").toInt();
manualB = server.arg("b").toInt();
setColor(manualR, manualG, manualB);
server.send(200, "text/plain", "Колір встановлено вручну");
}
void handleToggleMode() {
aiMode = !aiMode;
server.send(200, "text/plain", aiMode ? "AI-режим активовано" : "Ручний режим активовано");
}
void handleAskAI() {
String text = server.arg("text");
String response = queryGemini(text);
server.send(200, "text/plain", response);
}
void handleLux() {
autoLux = readLux();
server.send(200, "text/plain", String(autoLux));
}
// === Основна логіка програми ===
void setup() {
Serial.begin(115200);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
pinMode(LDR_PIN, INPUT);
WiFi.begin(ssid, password);
Serial.print("Підключення до Wi-Fi");
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println("\nWi-Fi підключено! IP: " + WiFi.localIP().toString());
server.on("/", handleRoot);
server.on("/set", handleSetColor);
server.on("/toggle", handleToggleMode);
server.on("/ask", handleAskAI);
server.on("/lux", handleLux);
server.begin();
setColor(manualR, manualG, manualB);
}
void loop() {
server.handleClient();
if (aiMode && millis() - lastLuxUpdate > 2000) {
lastLuxUpdate = millis();
int lux = readLux();
float norm = lux / 1000.0;
int r = (int)(255 * (1.0 - norm));
int g = (int)(180 * (1.0 - norm * 0.8));
int b = (int)(100 + 100 * norm);
setColor(r, g, b);
}
}