// --- Программа для Arduino: Живые глаза с режимом сна (Версия 6.0 - Сонное засыпание) ---
// --- 1. Анимация засыпания теперь многоступенчатая и более естественная.
// --- 2. Добавлена универсальная функция для анимации высоты глаз.
#include <GyverOLED.h>
GyverOLED<SSD1306_128x64, OLED_BUFFER> oled;
// Глобальные константы
const int eyeWidth = 40;
const int fullEyeHeight = 25;
const int eyeSpacing = 8;
const int EYE_SHIFT_AMOUNT = 5;
const int EYE_LOOK_PAUSE = 600;
const int BLINK_ANIM_DELAY = 0.5;
const int MOVE_ANIM_DELAY = 0.5;
const int BLINK_STEP = 5;
const long AWAKE_DURATION = 30000;
const long SLEEP_DURATION = 15000;
const int ZZZ_FRAME_DELAY = 200;
const int Z_BOX_X = 100, Z_BOX_Y = 0, Z_BOX_W = 27, Z_BOX_H = 20;
// Глобальные переменные
int leftEye_x0, leftEye_x1, rightEye_x0, rightEye_x1;
int eyeCenterY;
int currentEyeShift = 0;
unsigned long stateChangeTime = 0;
unsigned long lastActionTime = 0;
long nextActionDelay = 3000;
#define STATE_AWAKE 0
#define STATE_ASLEEP 1
int programState = STATE_AWAKE;
unsigned long lastZzzFrameTime = 0;
int zzzScale = 1;
void setup() {
oled.init();
int totalWidth = (eyeWidth * 2) + eyeSpacing;
int startX = (128 - totalWidth) / 2;
int startY = (64 - fullEyeHeight) / 2;
leftEye_x0 = startX;
leftEye_x1 = startX + eyeWidth;
rightEye_x0 = leftEye_x1 + eyeSpacing;
rightEye_x1 = rightEye_x0 + eyeWidth;
eyeCenterY = startY + (fullEyeHeight / 2);
delay(500);
animateOpening();
delay(300);
performLookAroundAnimation();
stateChangeTime = millis();
lastActionTime = millis();
}
// Базовые функции
void drawEyes(int currentHeight, int shift) {
if (currentHeight < 2) currentHeight = 2;
int y0 = eyeCenterY - (currentHeight / 2);
int y1 = eyeCenterY + (currentHeight / 2);
oled.roundRect(leftEye_x0 + shift, y0, leftEye_x1 + shift, y1, OLED_FILL);
oled.roundRect(rightEye_x0 + shift, y0, rightEye_x1 + shift, y1, OLED_FILL);
}
void animateEyeShift(int targetShift) {
int step = (targetShift > currentEyeShift) ? 1 : -1;
for (int shift = currentEyeShift; shift != targetShift; shift += step) {
oled.clear(); drawEyes(fullEyeHeight, shift); oled.update(); delay(MOVE_ANIM_DELAY);
}
currentEyeShift = targetShift;
oled.clear(); drawEyes(fullEyeHeight, currentEyeShift); oled.update();
}
// --- НОВАЯ универсальная функция для анимации высоты глаз ---
void animateEyeHeight(int startHeight, int endHeight) {
// Определяем направление анимации: закрытие (отрицательный шаг) или открытие (положительный)
int step = (startHeight > endHeight) ? -BLINK_STEP : BLINK_STEP;
// Цикл, который работает для обоих направлений
for (int h = startHeight; (step > 0) ? (h <= endHeight) : (h >= endHeight); h += step) {
oled.clear();
drawEyes(h, 0); // Анимация высоты происходит только в центральном положении
oled.update();
delay(BLINK_ANIM_DELAY);
}
// Убедимся, что в конце глаза имеют точную конечную высоту
oled.clear();
drawEyes(endHeight, 0);
oled.update();
}
// Старые функции теперь просто вызывают новую, универсальную
void animateClosing() {
animateEyeHeight(fullEyeHeight, 0);
}
void animateOpening() {
animateEyeHeight(0, fullEyeHeight);
}
void drawZgraphic(int x, int y, int width, int height) {
oled.line(x, y, x + width, y);
oled.line(x + width, y, x, y + height);
oled.line(x, y + height, x + width, y + height);
}
void drawSleepFrame(int scale) {
oled.clear();
int z_width = 3 * scale;
int z_height = 5 * scale;
int currentX = Z_BOX_X + (Z_BOX_W - z_width) / 2;
int currentY = Z_BOX_Y + (Z_BOX_H - z_height) / 2;
drawZgraphic(currentX, currentY, z_width, z_height);
drawEyes(0, 0);
oled.update();
}
// Функции высокого уровня
void performBlinkAnimation() {
animateClosing(); delay(25); animateOpening();
}
void performLookAroundAnimation() {
if (random(2) == 0) {
animateEyeShift(-EYE_SHIFT_AMOUNT); delay(EYE_LOOK_PAUSE);
animateEyeShift(EYE_SHIFT_AMOUNT); delay(EYE_LOOK_PAUSE);
} else {
animateEyeShift(EYE_SHIFT_AMOUNT); delay(EYE_LOOK_PAUSE);
animateEyeShift(-EYE_SHIFT_AMOUNT); delay(EYE_LOOK_PAUSE);
}
animateEyeShift(0);
}
// --- ОБНОВЛЕННАЯ, многоступенчатая функция засыпания ---
void performFallAsleepSequence() {
// Определяем пороговые высоты для "сонных" глаз
int sleepyHeight1 = fullEyeHeight * 0.7; // ~70% открыты
int sleepyHeight2 = fullEyeHeight * 0.4; // ~40% открыты
// Этап 1: Глаза немного прикрываются
animateEyeHeight(fullEyeHeight, sleepyHeight1);
delay(600); // Пауза, "борьба со сном"
// Этап 2: Глаза прикрываются еще сильнее
animateEyeHeight(sleepyHeight1, sleepyHeight2);
delay(800); // Более длинная пауза, почти заснул
// Этап 3: Глаза полностью закрываются
animateEyeHeight(sleepyHeight2, 0);
}
void performWakeUpSequence() {
animateOpening();
delay(300);
performLookAroundAnimation();
}
// Функция loop() (без изменений)
void loop() {
unsigned long currentTime = millis();
if (programState == STATE_AWAKE) {
if (currentTime - stateChangeTime >= AWAKE_DURATION) {
performFallAsleepSequence();
programState = STATE_ASLEEP;
stateChangeTime = currentTime;
lastZzzFrameTime = currentTime;
zzzScale = 1;
return;
}
if (currentTime - lastActionTime >= nextActionDelay) {
if (random(4) == 0) {
performLookAroundAnimation();
} else {
performBlinkAnimation();
}
lastActionTime = currentTime;
nextActionDelay = random(5000, 15000);
}
} else if (programState == STATE_ASLEEP) {
if (currentTime - stateChangeTime >= SLEEP_DURATION) {
performWakeUpSequence();
programState = STATE_AWAKE;
stateChangeTime = currentTime;
lastActionTime = currentTime;
return;
}
if (currentTime - lastZzzFrameTime >= ZZZ_FRAME_DELAY) {
int next_z_width = 3 * (zzzScale + 1);
int next_z_height = 5 * (zzzScale + 1);
drawSleepFrame(zzzScale);
if (next_z_width > Z_BOX_W || next_z_height > Z_BOX_H) {
zzzScale = 1;
} else {
zzzScale++;
}
lastZzzFrameTime = currentTime;
}
}
}