Загрузка данных
/*
* Часы-лампа для ESP32 с ЁМКОСТНОЙ КНОПКОЙ (сенсорной)
* Режимы: дыхание, ночник, дождь
* Фоновые временные метки каждые 15 минут и каждый час
* Управление: короткое касание - смена режима,
* длинное касание (3 сек) - показать время бинарным кодом
*
* Бинарный код: 0 = длинная вспышка (500 мс), 1 = короткая (150 мс)
* Часы: 4 бита (1-12), минуты/15: 2 бита (0-3)
*
* Сенсорный пин: GPIO4 (просто провод - касаться пальцем)
* Лампа: GPIO16
*/
#include <WiFi.h>
#include <time.h>
// ===== НАСТРОЙКИ Wi-Fi =====
const char* ssid = "ваш_SSID"; // Имя вашей Wi-Fi сети
const char* password = "ваш_ПАРОЛЬ"; // Пароль от Wi-Fi
// ===== НАСТРОЙКИ ВРЕМЕНИ =====
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 3 * 3600; // Москва UTC+3, для другого часового пояса измените
const int daylightOffset_sec = 0;
// ===== ПИНЫ =====
const int ledPin = 16; // ШИМ на лампу (GPIO16)
const int touchPin = T4; // Ёмкостная кнопка: GPIO4 (TOUCH0) - просто провод
// ===== ПАРАМЕТРЫ ШИМ =====
const int pwmChannel = 0;
const int pwmFreq = 5000;
const int pwmResolution = 8; // 0-255
// ===== РЕЖИМЫ =====
enum Mode { MODE_BREATH, MODE_NIGHT, MODE_RAIN };
Mode currentMode = MODE_BREATH;
const int modeCount = 3;
// ===== ПЕРЕМЕННЫЕ ДЛЯ ЭФФЕКТОВ (неблокирующие) =====
unsigned long lastEffectUpdate = 0;
float breathPhase = 0.0; // фаза дыхания (0..2PI)
unsigned long lastRainUpdate = 0;
int rainState = 0; // 0 - выкл, 1 - вкл
// ===== ПЕРЕМЕННЫЕ ДЛЯ ФОНОВЫХ МЕТОК =====
unsigned long lastTimeCheck = 0;
int lastDisplayedMinute = -1;
int lastDisplayedHour = -1;
// ===== ПЕРЕМЕННЫЕ ДЛЯ ОТОБРАЖЕНИЯ ВРЕМЕНИ ПО КНОПКЕ (бинарный код) =====
bool showingTime = false;
int showTimeStep = 0; // 0=часы, 1=пауза, 2=минуты
int showTimeBitIndex = 0; // какой бит показываем (0-3 для часов, 0-1 для минут)
int hoursToShow = 0; // часы для показа (1-12)
int quartersToShow = 0; // четверти (0-3)
unsigned long showTimeLastChange = 0;
bool showTimeLedOn = false;
int showTimeDuration = 0; // длительность текущей вспышки
// ===== ПЕРЕМЕННЫЕ ДЛЯ ЁМКОСТНОЙ КНОПКИ (сенсор) =====
int touchThreshold = 35; // порог срабатывания (подберите под себя)
bool lastTouchState = false;
unsigned long lastTouchTime = 0;
bool isPressing = false;
unsigned long pressStartTime = 0;
bool longTouchTriggered = false;
// ===== ПЕРЕМЕННЫЕ ДЛЯ ФОНОВЫХ МЕТОК =====
bool timeMarkActive = false;
int timeMarkType = 0; // 1 = четверть (3 вспышки), 2 = час (пауза)
int timeMarkStep = 0;
unsigned long timeMarkLastChange = 0;
int savedBrightness = 0; // для восстановления после метки
// ===== НАСТРОЙКА ШИМ =====
void setupLED() {
ledcSetup(pwmChannel, pwmFreq, pwmResolution);
ledcAttachPin(ledPin, pwmChannel);
}
// Установка яркости (0-255)
void setBrightness(int brightness) {
ledcWrite(pwmChannel, brightness);
}
// ===== ПОДКЛЮЧЕНИЕ Wi-Fi И СИНХРОНИЗАЦИЯ ВРЕМЕНИ =====
void connectWiFi() {
Serial.print("Подключение к Wi-Fi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi подключён");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
Serial.print("Синхронизация времени");
struct tm timeinfo;
while (!getLocalTime(&timeinfo)) {
delay(500);
Serial.print(".");
}
Serial.println("\nВремя синхронизировано");
}
// ===== ПОЛУЧЕНИЕ ТЕКУЩЕГО ВРЕМЕНИ =====
bool getCurrentTime(int &hour, int &minute) {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) return false;
hour = timeinfo.tm_hour;
minute = timeinfo.tm_min;
// Приводим часы к 12-часовому формату
if (hour > 12) hour -= 12;
if (hour == 0) hour = 12;
return true;
}
// ===== ОТОБРАЖЕНИЕ ВРЕМЕНИ БИНАРНЫМ КОДОМ =====
// Длинная вспышка = 0, короткая = 1
// Часы: 4 бита (1..12), минуты/15: 2 бита (0..3)
void startShowTime() {
int hour, minute;
if (!getCurrentTime(hour, minute)) return;
hoursToShow = hour;
quartersToShow = minute / 15; // 0,1,2,3
showingTime = true;
showTimeStep = 0; // 0 = часы, 1 = пауза, 2 = минуты
showTimeBitIndex = 0;
showTimeLastChange = 0;
showTimeLedOn = false;
longTouchTriggered = false;
setBrightness(0);
}
void updateShowTime() {
if (!showingTime) return;
unsigned long now = millis();
if (showTimeStep == 0) { // === Часы (4 бита) ===
if (!showTimeLedOn && showTimeBitIndex < 4) {
// Начать новый бит
int bit = (hoursToShow >> (3 - showTimeBitIndex)) & 1;
int duration = (bit == 0) ? 500 : 150; // 0=длинная 500мс, 1=короткая 150мс
setBrightness(255);
showTimeLedOn = true;
showTimeLastChange = now;
showTimeDuration = duration;
}
else if (showTimeLedOn) {
if (now - showTimeLastChange >= showTimeDuration) {
setBrightness(0);
showTimeLedOn = false;
showTimeLastChange = now;
showTimeBitIndex++;
}
}
else if (!showTimeLedOn && showTimeBitIndex < 4) {
// Пауза между битами (250 мс) - просто ждём
if (now - showTimeLastChange >= 250) {
// пауза закончилась, следующий бит начнётся в начале условия
}
}
if (showTimeBitIndex >= 4 && !showTimeLedOn) {
// Часы кончились → пауза 1 секунда перед минутами
showTimeStep = 1;
showTimeBitIndex = 0;
showTimeLastChange = now;
}
}
else if (showTimeStep == 1) { // === Пауза между часами и минутами ===
if (now - showTimeLastChange >= 1000) {
showTimeStep = 2;
showTimeBitIndex = 0;
showTimeLastChange = now;
}
}
else if (showTimeStep == 2) { // === Минуты/15 (2 бита) ===
if (!showTimeLedOn && showTimeBitIndex < 2) {
int bit = (quartersToShow >> (1 - showTimeBitIndex)) & 1;
int duration = (bit == 0) ? 500 : 150;
setBrightness(255);
showTimeLedOn = true;
showTimeLastChange = now;
showTimeDuration = duration;
}
else if (showTimeLedOn) {
if (now - showTimeLastChange >= showTimeDuration) {
setBrightness(0);
showTimeLedOn = false;
showTimeLastChange = now;
showTimeBitIndex++;
}
}
else if (!showTimeLedOn && showTimeBitIndex < 2) {
// Пауза между битами
if (now - showTimeLastChange >= 250) {
// ждём
}
}
if (showTimeBitIndex >= 2 && !showTimeLedOn) {
// Готово
showingTime = false;
setBrightness(0);
}
}
}
// ===== ФОНОВЫЕ ВРЕМЕННЫЕ МЕТКИ =====
void checkAndStartTimeMark() {
if (timeMarkActive) return; // уже выполняется метка
if (showingTime) return; // не мешать показу времени по кнопке
int hour, minute;
if (!getCurrentTime(hour, minute)) return;
// Проверка часа (минуты = 0)
if (minute == 0 && lastDisplayedHour != hour) {
lastDisplayedHour = hour;
timeMarkActive = true;
timeMarkType = 2; // часовая метка: пауза 500 мс
timeMarkStep = 0;
timeMarkLastChange = millis();
savedBrightness = ledcRead(pwmChannel);
setBrightness(0);
return;
}
// Проверка четверти (15,30,45)
if ((minute == 15 || minute == 30 || minute == 45) && lastDisplayedMinute != minute) {
lastDisplayedMinute = minute;
timeMarkActive = true;
timeMarkType = 1; // четверть: 3 вспышки по 50 мс
timeMarkStep = 0;
timeMarkLastChange = millis();
savedBrightness = ledcRead(pwmChannel);
setBrightness(0);
return;
}
}
void updateTimeMark() {
if (!timeMarkActive) return;
unsigned long now = millis();
if (timeMarkType == 2) { // часовая метка: просто пауза 500 мс
if (now - timeMarkLastChange >= 500) {
timeMarkActive = false;
// Яркость восстановится при следующем обновлении эффекта
}
}
else if (timeMarkType == 1) { // четверть: 3 вспышки по 50 мс
const int flashDuration = 50;
const int pauseDuration = 50;
if (timeMarkStep < 6) { // 3 вспышки = 6 шагов (вкл/выкл)
if (timeMarkStep % 2 == 0) { // вкл
if (now - timeMarkLastChange >= (timeMarkStep == 0 ? 0 : pauseDuration)) {
setBrightness(255);
timeMarkLastChange = now;
timeMarkStep++;
}
} else { // выкл
if (now - timeMarkLastChange >= flashDuration) {
setBrightness(0);
timeMarkLastChange = now;
timeMarkStep++;
}
}
} else {
timeMarkActive = false;
}
}
}
// ===== ЭФФЕКТЫ РЕЖИМОВ =====
void updateEffect() {
if (timeMarkActive) return; // метка имеет приоритет
if (showingTime) return; // показ времени по кнопке тоже приоритетнее
unsigned long now = millis();
switch (currentMode) {
case MODE_BREATH: {
// Плавное дыхание, период 8 секунд = 8000 мс
if (now - lastEffectUpdate >= 20) { // обновление каждые 20 мс
lastEffectUpdate = now;
breathPhase += 0.0157; // 2*PI / (8000/20)
if (breathPhase >= 2 * PI) breathPhase -= 2 * PI;
int brightness = (int)(127.5 + 127.5 * sin(breathPhase));
setBrightness(brightness);
}
break;
}
case MODE_NIGHT:
setBrightness(76); // 30% от 255
break;
case MODE_RAIN: {
// Имитация дождя: случайные короткие вспышки
if (now - lastRainUpdate >= 50) {
lastRainUpdate = now;
if (rainState == 0) {
// вероятность вспышки ~10%
if (random(100) < 10) {
rainState = 1;
setBrightness(random(100, 200));
lastRainUpdate = now;
} else {
setBrightness(51); // 20% яркости фон
}
} else {
rainState = 0;
setBrightness(51);
}
}
break;
}
}
}
// ===== ОБРАБОТКА ЁМКОСТНОЙ КНОПКИ (СЕНСОР) =====
void handleTouch() {
int touchValue = touchRead(touchPin); // значение ~50-70 (не тронут) ~10-20 (тронут)
bool touched = (touchValue < touchThreshold);
unsigned long now = millis();
// Антидребезг
if (touched != lastTouchState) {
lastTouchTime = now;
lastTouchState = touched;
return;
}
if (now - lastTouchTime < 50) return; // слишком быстро
if (touched && !isPressing) {
// Касание началось
isPressing = true;
pressStartTime = now;
longTouchTriggered = false;
// Отладка в Serial Monitor
Serial.print("Касание началось, значение: ");
Serial.println(touchValue);
}
else if (!touched && isPressing) {
// Отпускание
isPressing = false;
if (!longTouchTriggered) {
// Короткое касание (менее 3 секунд)
unsigned long pressDuration = now - pressStartTime;
if (pressDuration < 3000) {
// Смена режима
currentMode = (Mode)((currentMode + 1) % modeCount);
// Отладка
Serial.print("Смена режима на: ");
Serial.println(currentMode);
// Подтверждение: мигнуть номером режима (1,2,3 вспышки)
for (int i = 0; i <= (int)currentMode; i++) {
setBrightness(255);
delay(100);
setBrightness(0);
delay(150);
}
}
}
longTouchTriggered = false;
}
// Проверка длинного касания (3 секунды)
if (isPressing && !longTouchTriggered && (now - pressStartTime >= 3000)) {
longTouchTriggered = true;
Serial.println("Длинное касание - показываем время");
startShowTime(); // показать время бинарным кодом
}
}
// ===== НАСТРОЙКА =====
void setup() {
Serial.begin(115200);
// Настройка сенсорного пина (опционально)
// touchAttachInterrupt(touchPin, NULL, touchThreshold);
setupLED();
setBrightness(0);
connectWiFi();
randomSeed(analogRead(0));
lastEffectUpdate = millis();
lastTimeCheck = millis();
lastDisplayedMinute = -1;
lastDisplayedHour = -1;
// Калибровка сенсора (вывод значений в Serial)
Serial.println("Калибровка сенсора...");
for (int i = 0; i < 10; i++) {
Serial.print("Значение сенсора: ");
Serial.println(touchRead(touchPin));
delay(500);
}
Serial.println("Готово. Прикасайтесь к проводу на GPIO4.");
}
// ===== ГЛАВНЫЙ ЦИКЛ =====
void loop() {
handleTouch();
// Проверка времени для фоновых меток (раз в секунду)
unsigned long now = millis();
if (now - lastTimeCheck >= 1000) {
lastTimeCheck = now;
checkAndStartTimeMark();
}
updateTimeMark(); // выполняем фоновую метку, если активна
updateShowTime(); // выполняем показ времени по кнопке
updateEffect(); // обновляем эффект текущего режима
}