Загрузка данных
/*
* Часы-лампа для ESP32
* Режимы: дыхание, ночник, дождь
* Фоновые временные метки каждые 15 минут и каждый час
* Управление кнопкой: короткое нажатие - смена режима,
* длинное (3 сек) - показать время бинарным кодом
*
* Бинарный код: 0 = длинная вспышка (500 мс), 1 = короткая (150 мс)
* Часы: 4 бита (1-12), минуты/15: 2 бита (0-3)
*/
#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 buttonPin = 0; // Кнопка (GND при нажатии, GPIO0)
// ===== ПАРАМЕТРЫ ШИМ =====
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; // длительность текущей вспышки
// ===== ПЕРЕМЕННЫЕ ДЛЯ КНОПКИ (антидребезг) =====
unsigned long lastButtonPress = 0;
bool buttonPressed = false;
unsigned long buttonPressStart = 0;
bool longPressTriggered = 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;
longPressTriggered = 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 handleButton() {
bool reading = digitalRead(buttonPin) == LOW;
unsigned long now = millis();
if (reading && !buttonPressed && (now - lastButtonPress > 200)) {
// Нажатие
buttonPressed = true;
buttonPressStart = now;
longPressTriggered = false;
}
else if (!reading && buttonPressed) {
// Отпускание
buttonPressed = false;
if (!longPressTriggered && (now - buttonPressStart < 3000)) {
// Короткое нажатие - смена режима
currentMode = (Mode)((currentMode + 1) % modeCount);
// Подтверждение: мигнуть номером режима (1,2,3 вспышки)
for (int i = 0; i <= (int)currentMode; i++) {
setBrightness(255);
delay(100);
setBrightness(0);
delay(150);
}
lastButtonPress = now;
}
longPressTriggered = false;
}
// Проверка длинного нажатия (3 секунды)
if (buttonPressed && !longPressTriggered && (now - buttonPressStart >= 3000)) {
longPressTriggered = true;
startShowTime();
lastButtonPress = now;
}
}
// ===== НАСТРОЙКА =====
void setup() {
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP);
setupLED();
setBrightness(0);
connectWiFi();
randomSeed(analogRead(0));
lastEffectUpdate = millis();
lastTimeCheck = millis();
lastDisplayedMinute = -1;
lastDisplayedHour = -1;
}
// ===== ГЛАВНЫЙ ЦИКЛ =====
void loop() {
handleButton();
// Проверка времени для фоновых меток (раз в секунду)
unsigned long now = millis();
if (now - lastTimeCheck >= 1000) {
lastTimeCheck = now;
checkAndStartTimeMark();
}
updateTimeMark(); // выполняем фоновую метку, если активна
updateShowTime(); // выполняем показ времени по кнопке
updateEffect(); // обновляем эффект текущего режима
}