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


#include <Wire.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

// -------------------- ПИНЫ --------------------
#define BTN_UP   2
#define BTN_DOWN 3
#define LED_PIN  9

// -------------------- НАСТРОЙКИ КНОПОК --------------------
const unsigned long DEBOUNCE_MS      = 35;
const unsigned long LONG_PRESS_MS    = 2500;
const unsigned long MEDIUM_PRESS_MS  = 1000;
const unsigned long DOUBLE_GAP_MS    = 320;
const unsigned long DOUBLE_HOLD_MS   = 180;
const unsigned long STARTUP_TIMEOUT   = 5000;

// -------------------- ВРЕМЯ --------------------
byte h = 16, m = 11, s = 0;
unsigned long lastSecondTick = 0;

// -------------------- МЕТРОНОМ --------------------
int bpm = 100;
bool metroRunning = false;
unsigned long lastBeat = 0;
bool ledPulse = false;
unsigned long ledOffAt = 0;

// -------------------- СОСТОЯНИЯ --------------------
enum State {
  SLEEP,
  SHOW_TIME,
  TIME_PICK,
  TIME_EDIT,
  METRO_SHOW,
  METRO_EDIT
};

State state = SLEEP;
byte editMode = 0;   // 0 = часы, 1 = минуты
bool editBlink = true;
unsigned long lastBlink = 0;

// -------------------- КНОПКИ --------------------
struct Button {
  byte pin;
  bool raw;
  bool stable;
  bool lastStable;
  unsigned long lastChange;
  unsigned long pressTime;
  bool longFired;
  bool shortPending;
};

Button upBtn   = {BTN_UP, HIGH, HIGH, HIGH, 0, 0, false, false};
Button downBtn  = {BTN_DOWN, HIGH, HIGH, HIGH, 0, 0, false, false};

bool comboArmed = false;
unsigned long comboStart = 0;
bool comboDone = false;
unsigned long startupAt = 0;

bool isPressedRaw(byte pin) {
  return digitalRead(pin) == LOW;
}

void startSleep() {
  state = SLEEP;
  metroRunning = false;
  digitalWrite(LED_PIN, LOW);
}

void gotoShowTime() {
  state = SHOW_TIME;
  lastSecondTick = millis();
}

bool bothPressedNow() {
  return (upBtn.stable == LOW && downBtn.stable == LOW);
}

void setStable(Button &b) {
  bool r = digitalRead(b.pin);
  unsigned long now = millis();

  if (r != b.raw) {
    b.raw = r;
    b.lastChange = now;
  }

  if ((now - b.lastChange) >= DEBOUNCE_MS && b.stable != b.raw) {
    b.lastStable = b.stable;
    b.stable = b.raw;
  }
}

void handleShortPressUp();
void handleShortPressDown();
void handleMediumPressUp();
void handleMediumPressDown();
void handleLongPressUp();
void handleLongPressDown();
void handleComboPress();
void drawCenteredBoxText(int cx, int cy, const char *txt, int padX = 4, int padY = 4);
void drawArrowHints(int x, int y);
void updateClockTick();
void updateMetroTick();
void renderSleep();
void renderTime();
void renderTimePick();
void renderTimeEdit();
void renderMetroShow();
void renderMetroEdit();
void renderAll();

void setup() {
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  u8g2.begin();
  u8g2.setFontMode(1);
  u8g2.setFont(u8g2_font_profont22_tr);

  startupAt = millis();
  lastSecondTick = millis();
}

void loop() {
  unsigned long now = millis();

  setStable(upBtn);
  setStable(downBtn);

  if (state == SLEEP) {
    if (upBtn.stable == LOW || downBtn.stable == LOW) {
      gotoShowTime();
    }
  }

  bool comboNow = bothPressedNow();
  if (comboNow && !comboDone) {
    if (!comboArmed) {
      comboArmed = true;
      comboStart = now;
    } else if ((now - comboStart) >= DOUBLE_GAP_MS) {
      comboDone = true;
      handleComboPress();
    }
  }
  if (!comboNow) {
    comboArmed = false;
    comboDone = false;
  }

  if (upBtn.stable == LOW && !upBtn.longFired && (now - upBtn.lastChange) >= LONG_PRESS_MS) {
    upBtn.longFired = true;
    handleLongPressUp();
  }
  if (downBtn.stable == LOW && !downBtn.longFired && (now - downBtn.lastChange) >= LONG_PRESS_MS) {
    downBtn.longFired = true;
    handleLongPressDown();
  }

  if (upBtn.stable == HIGH && upBtn.lastStable == LOW) {
    unsigned long held = now - upBtn.pressTime;
    if (!upBtn.longFired && !comboDone) {
      if (held >= MEDIUM_PRESS_MS) handleMediumPressUp();
      else handleShortPressUp();
    }
    upBtn.longFired = false;
  }
  if (downBtn.stable == HIGH && downBtn.lastStable == LOW) {
    unsigned long held = now - downBtn.pressTime;
    if (!downBtn.longFired && !comboDone) {
      if (held >= MEDIUM_PRESS_MS) handleMediumPressDown();
      else handleShortPressDown();
    }
    downBtn.longFired = false;
  }

  if (upBtn.stable == LOW && upBtn.lastStable == HIGH) {
    upBtn.pressTime = now;
    upBtn.longFired = false;
  }
  if (downBtn.stable == LOW && downBtn.lastStable == HIGH) {
    downBtn.pressTime = now;
    downBtn.longFired = false;
  }

  if (state == SHOW_TIME) updateClockTick();
  if (state == METRO_SHOW && metroRunning) updateMetroTick();

  if (now - lastBlink >= 350) {
    lastBlink = now;
    editBlink = !editBlink;
  }

  renderAll();

  upBtn.lastStable = upBtn.stable;
  downBtn.lastStable = downBtn.stable;
}

void handleShortPressUp() {
  if (state == TIME_PICK) {
    editMode = 0;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 1) % 24;
    else m = (m + 1) % 60;
  } else if (state == METRO_SHOW || state == METRO_EDIT) {
    if (state == METRO_EDIT) bpm = min(220, bpm + 1);
  } else if (state == SHOW_TIME) {
    gotoShowTime();
  }
}

void handleShortPressDown() {
  if (state == SLEEP) {
    gotoShowTime();
  } else if (state == TIME_PICK) {
    editMode = 1;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 23) % 24;
    else m = (m + 59) % 60;
  } else if (state == METRO_EDIT) {
    bpm = max(40, bpm - 1);
  } else if (state == METRO_SHOW && !metroRunning) {
    metroRunning = true;
    lastBeat = millis();
  }
}

void handleMediumPressUp() {
  if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 10) % 24;
    else m = (m + 10) % 60;
  } else if (state == METRO_EDIT) {
    bpm = min(220, bpm + 10);
  }
}

void handleMediumPressDown() {
  if (state == TIME_EDIT) {
    if (editMode == 0) h = (h + 14) % 24;
    else m = (m + 50) % 60;
  } else if (state == METRO_EDIT) {
    bpm = max(40, bpm - 10);
  }
}

void handleLongPressUp() {
  if (state == SHOW_TIME) {
    state = TIME_PICK;
    editMode = 0;
  } else if (state == METRO_SHOW || state == METRO_EDIT) {
    state = SHOW_TIME;
    metroRunning = false;
  }
}

void handleLongPressDown() {
  if (state == SHOW_TIME) {
    state = METRO_SHOW;
    metroRunning = false;
  } else if (state == METRO_SHOW || state == METRO_EDIT) {
    state = SHOW_TIME;
    metroRunning = false;
  }
}

void handleComboPress() {
  if (state == TIME_PICK) {
    state = TIME_EDIT;
  } else if (state == TIME_EDIT) {
    if (editMode == 0) {
      editMode = 1;
      state = TIME_PICK;
    } else {
      state = SHOW_TIME;
    }
  } else if (state == METRO_SHOW) {
    state = METRO_EDIT;
  } else if (state == METRO_EDIT) {
    state = METRO_SHOW;
  }
}

void updateClockTick() {
  unsigned long now = millis();
  while (now - lastSecondTick >= 1000) {
    lastSecondTick += 1000;
    s++;
    if (s >= 60) {
      s = 0;
      m++;
      if (m >= 60) {
        m = 0;
        h = (h + 1) % 24;
      }
    }
  }
}

void updateMetroTick() {
  unsigned long now = millis();
  unsigned long interval = 60000UL / (unsigned long)bpm;

  if (ledPulse && now >= ledOffAt) {
    digitalWrite(LED_PIN, LOW);
    ledPulse = false;
  }

  if (now - lastBeat >= interval) {
    lastBeat += interval;
    digitalWrite(LED_PIN, HIGH);
    ledPulse = true;
    ledOffAt = now + 35;
  }
}

void drawCenteredBoxText(int cx, int cy, const char *txt, int padX, int padY) {
  int w = u8g2.getStrWidth(txt);
  int hgt = u8g2.getAscent() - u8g2.getDescent();
  int x = cx - w / 2 - padX;
  int y = cy - hgt / 2 - padY;
  u8g2.drawBox(x, y, w + padX * 2, hgt + padY * 2);
  u8g2.setDrawColor(0);
  u8g2.drawStr(cx - w / 2, cy + (u8g2.getAscent() / 2) - 1, txt);
  u8g2.setDrawColor(1);
}

void drawArrowHints(int x, int y) {
  u8g2.setFont(u8g2_font_5x8_tr);
  u8g2.drawStr(x, y, "^");
  u8g2.drawStr(x, y + 12, "v");
}

void renderSleep() {
  u8g2.clearBuffer();
  u8g2.sendBuffer();
  digitalWrite(LED_PIN, LOW);
}

void renderTime() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_profont22_tr);

  char buf[6];
  sprintf(buf, "%02d:%02d", h, m);
  u8g2.drawStr(26, 34, buf);

  u8g2.setFont(u8g2_font_6x10_tr);
  char buf2[16];
  sprintf(buf2, "%02d:%02d:%02d", h, m, s);
  u8g2.drawStr(34, 60, buf2);

  u8g2.sendBuffer();
}

void renderTimePick() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(4, 10, "TIME SET");

  u8g2.setFont(u8g2_font_profont22_tr);

  char hh[3], mm[3];
  sprintf(hh, "%02d", h);
  sprintf(mm, "%02d", m);

  if (editMode == 0) {
    drawCenteredBoxText(38, 36, hh);
    u8g2.setFont(u8g2_font_5x8_tr);
    drawArrowHints(88, 26);
    u8g2.drawStr(76, 46, "MIN");
  } else {
    u8g2.drawStr(30, 42, hh);
    u8g2.drawStr(58, 42, ":");
    drawCenteredBoxText(90, 36, mm);
    u8g2.setFont(u8g2_font_5x8_tr);
    drawArrowHints(18, 26);
    u8g2.drawStr(8, 46, "HOUR");
  }

  u8g2.setFont(u8g2_font_5x8_tr);
  u8g2.drawStr(8, 62, "UP/DOWN select  BOTH confirm");
  u8g2.sendBuffer();
}

void renderTimeEdit() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(4, 10, "ADJUST");

  u8g2.setFont(u8g2_font_profont22_tr);

  char hh[3], mm[3];
  sprintf(hh, "%02d", h);
  sprintf(mm, "%02d", m);

  if (editMode == 0) {
    drawCenteredBoxText(38, 36, hh);
    u8g2.setFont(u8g2_font_5x8_tr);
    drawArrowHints(88, 26);
    u8g2.drawStr(76, 46, "MIN");
  } else {
    u8g2.drawStr(30, 42, hh);
    u8g2.drawStr(58, 42, ":");
    drawCenteredBoxText(90, 36, mm);
    u8g2.setFont(u8g2_font_5x8_tr);
    drawArrowHints(18, 26);
    u8g2.drawStr(8, 46, "HOUR");
  }

  u8g2.setFont(u8g2_font_5x8_tr);
  u8g2.drawStr(8, 62, "UP +1 / MED +10   BOTH next/exit");
  u8g2.sendBuffer();
}

void renderMetroShow() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(4, 10, "METRONOME");

  u8g2.setFont(u8g2_font_profont22_tr);
  char buf[12];
  sprintf(buf, "%d BPM", bpm);
  if (state == METRO_SHOW && !metroRunning) {
    u8g2.drawStr(34, 36, buf);
    u8g2.setFont(u8g2_font_5x8_tr);
    u8g2.drawStr(18, 58, "DOWN starts   UP exits");
  } else {
    u8g2.drawStr(34, 36, buf);
    u8g2.setFont(u8g2_font_5x8_tr);
    u8g2.drawStr(36, 58, "RUNNING");
  }

  u8g2.sendBuffer();
}

void renderMetroEdit() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr);
  u8g2.drawStr(4, 10, "BPM SET");

  u8g2.setFont(u8g2_font_profont22_tr);
  char buf[8];
  sprintf(buf, "%d", bpm);

  int w = u8g2.getStrWidth(buf);
  int x = (128 - w) / 2;
  int y = 36;

  int boxX = x - 6;
  int boxY = y - 22;
  int boxW = w + 12;
  int boxH = 28;

  u8g2.drawBox(boxX, boxY, boxW, boxH);
  u8g2.setDrawColor(0);
  u8g2.drawStr(x, y, buf);
  u8g2.setDrawColor(1);

  u8g2.setFont(u8g2_font_5x8_tr);
  drawArrowHints(18, 24);
  drawArrowHints(108, 24);
  u8g2.drawStr(22, 58, "UP +1  MED +10  BOTH confirm");
  u8g2.sendBuffer();
}

void renderAll() {
  switch (state) {
    case SLEEP:      renderSleep(); break;
    case SHOW_TIME:  renderTime(); break;
    case TIME_PICK:  renderTimePick(); break;
    case TIME_EDIT:  renderTimeEdit(); break;
    case METRO_SHOW: renderMetroShow(); break;
    case METRO_EDIT: renderMetroEdit(); break;
  }
}