Загрузка данных


/*
 * Часы-лампа для 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();     // обновляем эффект текущего режима
}