Проект "умная Настольная Лампа"

MaxFos

✩✩✩✩✩✩✩
13 Янв 2022
22
1
Проект "умная Настольная Лампа"

Мозг всего проекта это esp 8266

(На данный момент ведется отладка на плате Wemos D1 mini)

Функционал: Не обычный настольный светильник с регулировкой теплого холодного свечения и поддержкой MQTT для интеграции в ха

Компонентны:
OLED LCD 0.96 дюйма синій I2C
Две сенсорные кнопки TP223
Датчик температуры и влажности BME280
Светодиод RGB LED WS2812B
Led матрица которая одновременно содержит светодиоды холодного и теплого свечения

Подключение:

Олед дисплей, так как он подключается по I2C шине, то его контакты припаиваю к пину на Wemos:
SCK на D1
SDA на D2
на ESP8266:
SCK на GPIO5
SDA на GPIO6

VDD на 5V
GND на общею землю

Датчик температуры и влажности BME280 тоже имеет шину I2C и к Wemos подключаем так же как и дисплей:
SCL на D1
SDA на D2
на ESP8266:
SCL на GPIO5
SDA на GPIO6

VDD на 5V
GND на общею землю

Кнопка TP223 первая. Подключается к пину на Wemos D7 на ESP8266 GPIO13
Кнопка TP223 вторая. Подключается к пину на Wemos D0 на ESP8266 GPIO16

Светодиод RGB LED WS2812B используется для индикации состояния лампы и подключен к пину Wemos D3 на ESP8266 GPIO0

Светодиоды подключены через мосфеты A09T, где теплый свет это Wemos D5 ESP8266 GPIO14, холодный свет это Wemos D6 ESP8266 GPIO12

А теперь по работе лампы:
Основная работа заключается в работе как светильник, при нажатии на первую кнопку Лампа Включается повторное нажатие, выключает светильник,
При удержании первой кнопки управление подсветкой так: удержание яркость вверх бросил регулировка остановилась. Повторное удержание меняет яркость в обратную сторону. Минимальная яркость при регулировании не должна быть меньше 1% то-есть светильник не отключается полностью
Кнопка номер два. то же самое что по яркости. но только температура свечения. от холодного до теплого.
Это самое важное регулировка яркости по удержанию первой кнопки температуры по второй.
Про сервера и MQTT:
Первое включение, лампа работает как лампа адресный светодиод "дишит Зелеными" раз в 5 секунд
при этом она создет точку доступа с именем STLamp_(ид чипа) и паролем 12345678
При подключении к точке доступа (автоматически) открывается веб интерфейс (можно по адресу 192.168.1.4 или 10.0.0.1)
На веб странице можно управлять лампой то есть яркость температура включить выключить и возможность подключения к роутеру.
После подключения к роутеру на веб странице появляется возможность настроить будильник, часы, MQTT. Включить выключить MqTT. Три страницы в веб морде Главная Будильник MQTT
Oled дисплей: По центру огромные часы(синхронизация по интернету) иконки вай-фай будильника и mqtt
Адресный светодиод
Зелёный(свечение) - все ок, мютт работает есть коннект к серверу(только при включённой лампы
Синий попытки подключения мютт
Красный Ошибка мютт
режим ап "дишит Зелеными" раз в 5 секунд

Несколько недель вожусь с кодом, ничего не выходит, то дисплей то кнопки, то ещё что-то
 

MaxFos

✩✩✩✩✩✩✩
13 Янв 2022
22
1
@SlavaZagaynov,
Хочется сделать чтобы совсем без кнопок, есть такая лампа
4798019512_w200_h200_akkumulyatornaya-svetodiodnaya-nastolnaya.jpg
В неё я умудрился засунуть одну кнопку и адресный , вроде работает, к ха по mqtt подключаюсь , запихнул в нее 2 аккумулятора , все работает, но только стандартный свет 😐😞
Я хочу добавить экран с часами ,термометр пускай в ха засылает данные о температуре и тому подобное. И лед панель которая имеет два ряда светодиодов и может ил включать по отдельности (холодный теплый)

По возможности на экран выводить информацию о яркости (во время изменения) температуры света, статус mqtt, заряда аккумулятора, и статус (питание от сети или аккумуляторов), а в режиме работы, часы)
 
Изменено:

MaxFos

✩✩✩✩✩✩✩
13 Янв 2022
22
1

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <PubSubClient.h>
#include <EEPROM.h>
#include <FastLED.h>
#include <GyverButton.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ArduinoJson.h>
// Конфигурация пинов
const int LAMP_PIN = D6;
const int TOUCH_PIN = D7;
const int BTN_PIN = D8;
const int LED_PIN = D3;
const int LED_PIN_H = D5;
// Параметры Wi-Fi (для режима AP)
const char* ap_ssid = "Smart_Lamp";
const char* ap_password = "*********";
// Глобальные переменные и объекты
GButton touch(TOUCH_PIN);
GButton btn(BTN_PIN);
WiFiClient espClient;
PubSubClient client(espClient);
ESP8266WebServer server(80);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 3 * 3600);
#define NUM_LEDS 1
CRGB leds[NUM_LEDS];
// EEPROM
struct Config {
char wifiSsid[32];
char wifiPass[64];
char mqttServer[64];
char mqttUser[32];
char mqttPass[32];
char mqttTopic[64];
char mqttStateTopic[64];
int savedBrightness;
bool alarmEnabled;
int alarmHour;
int alarmMinute;
int alarmDay;
bool configSaved;
};
Config config;
// Переменные для управления
int lampBrightness = 255;
bool lampState = false;
bool ap_mode_active = false;
unsigned long lastAlarmCheck = 0;
bool dimmingUp = true;
unsigned long lastDimmingChange = 0;
const int dimmingInterval = 10;
const int brightnessStep = 10;
// Управление лампой
void setLampState(bool state) {
lampState = state;
analogWrite(LAMP_PIN, state ? map(lampBrightness, 0, 255, 0, 1023) : 0);
EEPROM.put(0, config);
EEPROM.commit();
}
void setLampBrightness(int brightness) {
lampBrightness = constrain(brightness, 10, 255);
if (lampState) {
analogWrite(LAMP_PIN, map(lampBrightness, 0, 255, 0, 1023));
}
config.savedBrightness = lampBrightness;
EEPROM.put(0, config);
EEPROM.commit();
}
// MQTT-функции
void publishState() {
if (client.connected()) {
StaticJsonDocument<200> doc;
doc["state"] = lampState ? "ON" : "OFF";
doc["brightness"] = lampBrightness;
String output;
serializeJson(doc, output);
client.publish(config.mqttStateTopic, output.c_str());
}
}
void callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
if (String(topic) == String(config.mqttTopic)) {
StaticJsonDocument<200> doc;
deserializeJson(doc, message);
if (doc.containsKey("state")) {
String state = doc["state"];
setLampState(state == "ON");
}
if (doc.containsKey("brightness")) {
setLampBrightness(doc["brightness"].as<int>());
}
}
}
void reconnectMQTT() {
if (strlen(config.mqttServer) > 0) {
while (!client.connected()) {
if (client.connect("ms_lamp", config.mqttUser, config.mqttPass)) {
client.subscribe(config.mqttTopic);
publishState();
} else {
delay(5000);
}
}
}
}
// Загрузка/сохранение настроек
void loadConfig() { EEPROM.get(0, config); }
void saveConfig() { EEPROM.put(0, config); EEPROM.commit(); }
// Режимы работы
void startAPMode() {
Serial.println("Starting AP Mode");
WiFi.mode(WIFI_AP);
WiFi.softAP(ap_ssid, ap_password);

// Сканируем сети и формируем HTML для выпадающего списка
int n = WiFi.scanNetworks();
String options = "";
for (int i = 0; i < n; ++i) {
options += "<option value='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + " (" + WiFi.RSSI(i) + " dBm)</option>";
}

server.on("/", [options]() {
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Настройка Wi-Fi</title><style>body{background-color:#333;color:#fff;font-family:sans-serif;text-align:center;}.container{max-width:400px;margin:50px auto;padding:20px;background-color:#444;border-radius:10px;}input,button,select{width:100%;box-sizing:border-box;margin-bottom:10px;padding:8px;border-radius:5px;border:none;}button{cursor:pointer;background-color:#007bff;color:#fff;}.note{font-size:12px;color:#aaa;}</style></head><body><div class='container'><h1>Настройка Wi-Fi</h1><form action='/save' method='post'>WiFi SSID: <select name='ssid'>" + options + "</select><br>WiFi Password: <input type='password' name='pass'><br><p class='note'>Чтобы очистить пароль, оставьте поле пустым.</p><button type='submit'>Сохранить и перезагрузить</button></form></div></body></html>";
server.send(200, "text/html", html);
});
server.on("/save", []() {
strncpy(config.wifiSsid, server.arg("ssid").c_str(), sizeof(config.wifiSsid));
strncpy(config.wifiPass, server.arg("pass").c_str(), sizeof(config.wifiPass));
config.configSaved = true;
EEPROM.put(0, config);
EEPROM.commit();
server.send(200, "text/plain", "Настройки сохранены. Перезагрузка...");
delay(1000);
ESP.restart();
});
server.onNotFound([]() {
server.sendHeader("Location", "http://" + WiFi.softAPIP().toString() + "/");
server.send(302, "text/plain", "");
});
server.begin();
ap_mode_active = true;
}
void startNormalMode() {
Serial.println("Starting Normal Mode");
WiFi.mode(WIFI_STA);
WiFi.hostname("ms_lamp");
WiFi.begin(config.wifiSsid, config.wifiPass);

unsigned long connectionTimeout = millis();
while (WiFi.status() != WL_CONNECTED && millis() - connectionTimeout < 20000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nConnection failed. Starting AP Mode.");
startAPMode();
} else {
Serial.println("\nWiFi connected.");
Serial.println(WiFi.localIP());
timeClient.begin();

server.on("/", []() {
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>Настройки лампы и умного дома</title><style>body{background-color:#333;color:#fff;font-family:sans-serif;text-align:center;}.container{max-width:400px;margin:50px auto;padding:20px;background-color:#444;border-radius:10px;}input,button{width:100%;box-sizing:border-box;margin-bottom:10px;padding:8px;border-radius:5px;border:none;}button{cursor:pointer;background-color:#007bff;color:#fff;}.note{font-size:12px;color:#aaa;}</style></head><body><div class='container'><h1>Настройки умного дома</h1><form action='/save' method='post'><h2>Настройки MQTT</h2>MQTT Server: <input type='text' name='mqttServer' value='" + String(config.mqttServer) + "'><br>MQTT User: <input type='text' name='mqttUser' value='" + String(config.mqttUser) + "'><br>MQTT Password: <input type='password' name='mqttPass' value='" + String(config.mqttPass) + "'><br>MQTT Topic: <input type='text' name='mqttTopic' value='" + String(config.mqttTopic) + "'><br>MQTT State Topic: <input type='text' name='mqttStateTopic' value='" + String(config.mqttStateTopic) + "'><hr><h2>Настройки будильника</h2><label for='alarmEnabled'>Включить будильник: </label><input type='checkbox' name='alarmEnabled' " + (config.alarmEnabled ? "checked" : "") + "><br>Время (чч:мм): <input type='number' name='alarmHour' min='0' max='23' value='" + String(config.alarmHour) + "'>:<input type='number' name='alarmMinute' min='0' max='59' value='" + String(config.alarmMinute) + "'><br>День недели (0-6): <input type='number' name='alarmDay' min='0' max='6' value='" + String(config.alarmDay) + "'><p class='note'>0 = воскресенье, 1 = понедельник и т.д.</p><button type='submit'>Сохранить</button></form></div></body></html>";
server.send(200, "text/html", html);
});
server.on("/save", []() {
strncpy(config.mqttServer, server.arg("mqttServer").c_str(), sizeof(config.mqttServer));
strncpy(config.mqttUser, server.arg("mqttUser").c_str(), sizeof(config.mqttUser));
strncpy(config.mqttPass, server.arg("mqttPass").c_str(), sizeof(config.mqttPass));
strncpy(config.mqttTopic, server.arg("mqttTopic").c_str(), sizeof(config.mqttTopic));
strncpy(config.mqttStateTopic, server.arg("mqttStateTopic").c_str(), sizeof(config.mqttStateTopic));
config.alarmEnabled = server.hasArg("alarmEnabled");
config.alarmHour = server.arg("alarmHour").toInt();
config.alarmMinute = server.arg("alarmMinute").toInt();
config.alarmDay = server.arg("alarmDay").toInt();
EEPROM.put(0, config);
EEPROM.commit();
server.send(200, "text/plain", "Настройки сохранены. Перезагрузка...");
delay(1000);
ESP.restart();
});
server.on("/toggle", []() {
setLampState(!lampState);
publishState();
server.send(200, "text/plain", "OK");
});
server.on("/setLampBrightness", []() {
setLampBrightness(server.arg("value").toInt());
publishState();
server.send(200, "text/plain", "OK");
});
server.on("/status", []() {
StaticJsonDocument<200> doc;
doc["state"] = lampState ? "ON" : "OFF";
doc["brightness"] = lampBrightness;
String output;
serializeJson(doc, output);
server.send(200, "application/json", output);
});
server.begin();
ap_mode_active = false;
}
}
void checkAlarm() {
timeClient.update();
int currentHour = timeClient.getHours();
int currentMinute = timeClient.getMinutes();
int currentDay = timeClient.getDay();
if (config.alarmEnabled && currentDay == config.alarmDay && currentHour == config.alarmHour && currentMinute == config.alarmMinute) {
if (!lampState) {
setLampState(true);
publishState();
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LAMP_PIN, OUTPUT);
analogWriteRange(1023);
analogWrite(LAMP_PIN, 0);
EEPROM.begin(512);
loadConfig();
FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.clear();
FastLED.show();

touch.setDebounce(50);
touch.setClickTimeout(600);
touch.setTimeout(300);
touch.setStepTimeout(10);
touch.setType(LOW_PULL);
touch.setDirection(NORM_OPEN);
btn.setDebounce(50);
btn.setClickTimeout(600);
btn.setTimeout(300);
WiFi.hostname("ms_lamp");
if (config.configSaved) {
startNormalMode();
lampBrightness = config.savedBrightness;
} else {
startAPMode();
}
}
void loop() {
touch.tick();
btn.tick();
if (touch.isClick()) {
if (lampState) {
setLampState(false);
} else {
setLampState(true);
if (lampBrightness >= 255) {
dimmingUp = false;
} else {
dimmingUp = true;
}
}
publishState();
}
if (touch.isHold()) {
if (millis() - lastDimmingChange > dimmingInterval) {
if (dimmingUp) {
setLampBrightness(lampBrightness + brightnessStep);
} else {
setLampBrightness(lampBrightness - brightnessStep);
}
lastDimmingChange = millis();
}
}
if (touch.isTriple()) {
setLampState(true);
setLampBrightness(255);
publishState();
}
if (btn.isDouble()) {
if (!ap_mode_active) {
startAPMode();
}
}

if (btn.isHold()) {
Serial.println("Hold detected on physical button. Clearing EEPROM and restarting...");
memset(&config, 0, sizeof(config));
EEPROM.put(0, config);
EEPROM.commit();
delay(1000);
ESP.restart();
}
if (ap_mode_active) {
static uint8_t hue = 0;
leds[0] = CHSV(hue++, 255, 255);
FastLED.show();
delay(20);
server.handleClient();
} else {
if (WiFi.status() == WL_CONNECTED) {
if (!client.connected() && strlen(config.mqttServer) > 0) {
reconnectMQTT();
}
client.loop();
server.handleClient();
if (millis() - lastAlarmCheck > 60000) {
checkAlarm();
lastAlarmCheck = millis();
}
}
}
}


Получилось как-то так, но там одна кнопка и один тип светодиодов
 
Изменено: