#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ======================================================
// PINS
// ======================================================
const byte PIN_IGNITION = 2;
const byte PIN_DAMPER = 3;
const byte PIN_STARTER = 4;
const byte PIN_GEN = 5;
const byte PIN_MAIN = 6;
const byte PIN_MODE = 7;
const byte PIN_SEASON = 8;
const byte PIN_MANUAL = 9;
const byte PIN_EMERGENCY = 10;
const byte PIN_BUZZER = 11;
const byte PIN_MAINS = 12;
// ======================================================
// TIME
// ======================================================
const unsigned long KD_TIME = 5000;
const unsigned long STARTER_TIME = 3000;
const unsigned long DAMPER_EXTRA_TIME = 2000;
const unsigned long WARMUP_TIME = 10000;
const unsigned long MAINS_RETURN_DELAY = 10000;
const unsigned long COOLDOWN_TIME = 20000;
const unsigned long MANUAL_CONFIRM_WINDOW = 3000;
const unsigned long BUTTON_DEBOUNCE = 50;
const unsigned int BEEP_FREQUENCY = 2000;
const unsigned long NORMAL_BEEP_TIME = 150;
const unsigned long ALARM_BEEP_TIME = 150;
const unsigned long ALARM_PAUSE = 150;
const unsigned long LCD_UPDATE_TIME = 250;
const byte ALARM_BEEP_COUNT = 5;
// ======================================================
// STATES
// ======================================================
enum State {
NORMAL,
CONTROL_DELAY,
STARTING,
WARMUP,
RUNNING,
RETURN_DELAY,
COOLDOWN,
EMERGENCY
};
State state = NORMAL;
unsigned long stateStartTime = 0;
unsigned long lcdUpdateTimer = 0;
// ======================================================
// NORMAL SCREEN TYPES
// ======================================================
enum NormalView {
NORMAL_MENU,
NORMAL_MANUAL_CONFIRM,
NORMAL_AUTO_LOCK
};
NormalView normalView = NORMAL_MENU;
// ======================================================
// FLAGS
// ======================================================
bool manualForcedRun = false;
bool autoRestartBlocked = false;
bool manualStopBlocked = false;
bool manualConfirmPending = false;
unsigned long manualConfirmTimer = 0;
// ======================================================
// D9 BUTTON
// ======================================================
bool manualButtonRaw = HIGH;
bool manualButtonStable = HIGH;
bool manualButtonHandled = false;
unsigned long manualButtonTimer = 0;
// ======================================================
// D10 BUTTON
// ======================================================
bool emergencyButtonRaw = HIGH;
bool emergencyButtonStable = HIGH;
bool emergencyButtonHandled = false;
unsigned long emergencyButtonTimer = 0;
// ======================================================
// BUZZER
// ======================================================
bool buzzerActive = false;
bool buzzerToneOn = false;
byte buzzerBeepCount = 0;
byte buzzerBeepIndex = 0;
unsigned long buzzerTimer = 0;
// ======================================================
// INPUT STATE MEMORY
// ======================================================
bool lastAutoMode = false;
bool lastWinterMode = false;
bool lastMainsPresent = false;
// ======================================================
// BASIC FUNCTIONS
// ======================================================
bool isAutoMode() {
return digitalRead(PIN_MODE) == LOW;
}
bool isWinter() {
return digitalRead(PIN_SEASON) == LOW;
}
bool mainsPresent() {
// D12:
// HIGH = main power present
// LOW = main power absent
return digitalRead(PIN_MAINS) == HIGH;
}
// ======================================================
// LCD HELPERS
// ======================================================
void lcdLine(byte row, const char* text) {
lcd.setCursor(0, row);
lcd.print(" ");
lcd.setCursor(0, row);
lcd.print(text);
}
unsigned long remainingSeconds(unsigned long total,
unsigned long elapsed) {
if (elapsed >= total)
return 0;
return (total - elapsed + 999) / 1000;
}
// ======================================================
// LCD SCREENS
// ======================================================
void showNormalMenu() {
char line1[17];
if (isAutoMode()) {
if (isWinter())
snprintf(line1, sizeof(line1), "AUTO WINTER");
else
snprintf(line1, sizeof(line1), "AUTO SUMMER");
} else {
if (isWinter())
snprintf(line1, sizeof(line1), "MANUAL WINTER");
else
snprintf(line1, sizeof(line1), "MANUAL SUMMER");
}
lcdLine(0, line1);
if (mainsPresent())
lcdLine(1, "MAINS: ON");
else
lcdLine(1, "MAINS: OFF");
}
void showManualConfirm() {
lcdLine(0, "MANUAL START?");
lcdLine(1, "PRESS D9 AGAIN");
}
void showAutoLock() {
lcdLine(0, "AUTO START LOCK");
lcdLine(1, "PRESS D9");
}
void showControlDelay() {
char line2[17];
unsigned long elapsed = millis() - stateStartTime;
unsigned long sec = remainingSeconds(KD_TIME, elapsed);
snprintf(line2, sizeof(line2), "START IN %lus", sec);
lcdLine(0, "MAINS LOST");
lcdLine(1, line2);
}
void showStarting() {
char line2[17];
unsigned long elapsed = millis() - stateStartTime;
unsigned long sec = remainingSeconds(STARTER_TIME, elapsed);
snprintf(line2, sizeof(line2), "STARTER %lus", sec);
lcdLine(0, "START GENERATOR");
lcdLine(1, line2);
}
void showWarmup() {
char line2[17];
unsigned long elapsed = millis() - stateStartTime;
unsigned long sec = remainingSeconds(WARMUP_TIME, elapsed);
snprintf(line2, sizeof(line2), "WARMUP %lus", sec);
lcdLine(0, "GEN WARMING");
lcdLine(1, line2);
}
void showRunning() {
lcdLine(0, "GENERATOR RUN");
lcdLine(1, "GEN CONT: ON");
}
void showReturnDelay() {
char line2[17];
unsigned long elapsed = millis() - stateStartTime;
unsigned long sec =
remainingSeconds(MAINS_RETURN_DELAY, elapsed);
snprintf(line2, sizeof(line2), "WAIT %lus", sec);
lcdLine(0, "MAINS RETURN");
lcdLine(1, line2);
}
void showCooldown() {
char line2[17];
unsigned long elapsed = millis() - stateStartTime;
unsigned long sec =
remainingSeconds(COOLDOWN_TIME, elapsed);
snprintf(line2, sizeof(line2), "NO LOAD %lus", sec);
lcdLine(0, "GEN COOLDOWN");
lcdLine(1, line2);
}
void showEmergency() {
lcdLine(0, "!! EMERGENCY !!");
lcdLine(1, "PRESS D10 RESET");
}
// ======================================================
// LCD UPDATE
// ======================================================
void updateLCD() {
switch (state) {
case NORMAL:
if (normalView == NORMAL_MANUAL_CONFIRM) {
showManualConfirm();
} else if (normalView == NORMAL_AUTO_LOCK) {
showAutoLock();
} else {
showNormalMenu();
}
break;
case CONTROL_DELAY:
showControlDelay();
break;
case STARTING:
showStarting();
break;
case WARMUP:
showWarmup();
break;
case RUNNING:
showRunning();
break;
case RETURN_DELAY:
showReturnDelay();
break;
case COOLDOWN:
showCooldown();
break;
case EMERGENCY:
showEmergency();
break;
}
}
// ======================================================
// STATE CHANGE
// ======================================================
void changeState(State newState) {
state = newState;
stateStartTime = millis();
if (state == NORMAL) {
normalView = NORMAL_MENU;
}
lcd.clear();
updateLCD();
lcdUpdateTimer = millis();
}
// ======================================================
// OUTPUTS
// ======================================================
void setIgnition(bool value) {
digitalWrite(PIN_IGNITION, value ? HIGH : LOW);
}
void setStarter(bool value) {
digitalWrite(PIN_STARTER, value ? HIGH : LOW);
}
// ------------------------------------------------------
// DAMPER
//
// HIGH = IMPULSE TO RELAY = CLOSED
// LOW = NO IMPULSE = OPEN
// ------------------------------------------------------
void setDamper(bool closed) {
digitalWrite(
PIN_DAMPER,
closed ? HIGH : LOW
);
}
// ------------------------------------------------------
// MAIN CONTACTOR
// ------------------------------------------------------
void setMainContactor(bool value) {
if (value) {
if (!mainsPresent()) {
digitalWrite(PIN_MAIN, LOW);
return;
}
digitalWrite(PIN_GEN, LOW);
digitalWrite(PIN_MAIN, HIGH);
} else {
digitalWrite(PIN_MAIN, LOW);
}
}
// ------------------------------------------------------
// GENERATOR CONTACTOR
// ------------------------------------------------------
void setGeneratorContactor(bool value) {
if (value) {
digitalWrite(PIN_MAIN, LOW);
digitalWrite(PIN_GEN, HIGH);
} else {
digitalWrite(PIN_GEN, LOW);
}
}
// ======================================================
// BUZZER
// ======================================================
void shortBeep() {
buzzerActive = true;
buzzerToneOn = true;
buzzerBeepCount = 1;
buzzerBeepIndex = 0;
buzzerTimer = millis();
tone(PIN_BUZZER, BEEP_FREQUENCY);
}
void alarmBeep() {
buzzerActive = true;
buzzerToneOn = true;
buzzerBeepCount = ALARM_BEEP_COUNT;
buzzerBeepIndex = 0;
buzzerTimer = millis();
tone(PIN_BUZZER, BEEP_FREQUENCY);
}
void updateBuzzer() {
if (!buzzerActive)
return;
unsigned long now = millis();
if (buzzerToneOn) {
unsigned long duration;
if (buzzerBeepCount == ALARM_BEEP_COUNT)
duration = ALARM_BEEP_TIME;
else
duration = NORMAL_BEEP_TIME;
if (now - buzzerTimer >= duration) {
noTone(PIN_BUZZER);
buzzerToneOn = false;
buzzerTimer = now;
}
} else {
if (now - buzzerTimer >= ALARM_PAUSE) {
buzzerBeepIndex++;
if (buzzerBeepIndex >= buzzerBeepCount) {
buzzerActive = false;
} else {
tone(PIN_BUZZER, BEEP_FREQUENCY);
buzzerToneOn = true;
buzzerTimer = now;
}
}
}
}
// ======================================================
// START GENERATOR
// ======================================================
void startGeneratorSequence() {
if (manualForcedRun) {
// Принудительный запуск:
// основной контактор выключаем
setMainContactor(false);
}
// Генераторный контактор на запуске выключен
setGeneratorContactor(false);
// Зажигание ON
setIgnition(true);
// Движок заслонки:
// HIGH = закрыть
setDamper(true);
// Стартер ON
setStarter(true);
shortBeep();
changeState(STARTING);
}
// ======================================================
// STOP GENERATOR
// ======================================================
void stopGenerator() {
setGeneratorContactor(false);
setStarter(false);
setIgnition(false);
// После остановки заслонка открыта
setDamper(false);
}
// ======================================================
// MANUAL STOP
// ======================================================
void stopGeneratorByButton() {
manualForcedRun = false;
manualConfirmPending = false;
stopGenerator();
manualStopBlocked = true;
if (mainsPresent())
setMainContactor(true);
else
setMainContactor(false);
changeState(NORMAL);
normalView = NORMAL_MENU;
updateLCD();
}
// ======================================================
// EMERGENCY
// ======================================================
void enterEmergency() {
manualForcedRun = false;
manualConfirmPending = false;
autoRestartBlocked = true;
manualStopBlocked = false;
stopGenerator();
if (mainsPresent())
setMainContactor(true);
else
setMainContactor(false);
alarmBeep();
changeState(EMERGENCY);
}
void resetEmergency() {
stopGenerator();
// Автоматический запуск всё ещё заблокирован
autoRestartBlocked = true;
manualStopBlocked = false;
if (mainsPresent())
setMainContactor(true);
else
setMainContactor(false);
state = NORMAL;
stateStartTime = millis();
normalView = NORMAL_AUTO_LOCK;
lcd.clear();
updateLCD();
}
// ======================================================
// D10 BUTTON
// ======================================================
void readEmergencyButton() {
bool raw = digitalRead(PIN_EMERGENCY);
if (raw != emergencyButtonRaw) {
emergencyButtonRaw = raw;
emergencyButtonTimer = millis();
}
if (millis() - emergencyButtonTimer >=
BUTTON_DEBOUNCE) {
if (emergencyButtonStable !=
emergencyButtonRaw) {
emergencyButtonStable =
emergencyButtonRaw;
if (emergencyButtonStable == LOW &&
!emergencyButtonHandled) {
emergencyButtonHandled = true;
if (state == EMERGENCY)
resetEmergency();
else
enterEmergency();
}
if (emergencyButtonStable == HIGH) {
emergencyButtonHandled = false;
}
}
}
}
// ======================================================
// D9 BUTTON
// ======================================================
void readManualButton() {
bool raw = digitalRead(PIN_MANUAL);
if (raw != manualButtonRaw) {
manualButtonRaw = raw;
manualButtonTimer = millis();
}
if (millis() - manualButtonTimer <
BUTTON_DEBOUNCE) {
return;
}
if (manualButtonStable == manualButtonRaw)
return;
manualButtonStable = manualButtonRaw;
if (manualButtonStable == HIGH) {
manualButtonHandled = false;
return;
}
if (manualButtonHandled)
return;
manualButtonHandled = true;
if (state == EMERGENCY)
return;
// ----------------------------------------------------
// GENERATOR IS RUNNING
// D9 = STOP
// ----------------------------------------------------
if (state == RUNNING ||
state == RETURN_DELAY ||
state == COOLDOWN) {
stopGeneratorByButton();
return;
}
// ----------------------------------------------------
// CLEAR AUTO START LOCK
// ----------------------------------------------------
if (autoRestartBlocked) {
autoRestartBlocked = false;
if (state == NORMAL &&
isAutoMode() &&
!mainsPresent()) {
normalView = NORMAL_MENU;
startGeneratorSequence();
} else {
normalView = NORMAL_MENU;
updateLCD();
}
return;
}
// ----------------------------------------------------
// CLEAR MANUAL STOP BLOCK
// ----------------------------------------------------
if (manualStopBlocked) {
manualStopBlocked = false;
if (state == NORMAL &&
isAutoMode() &&
!mainsPresent()) {
normalView = NORMAL_MENU;
startGeneratorSequence();
} else {
normalView = NORMAL_MENU;
updateLCD();
}
return;
}
// ----------------------------------------------------
// MANUAL START
// TWO PRESSES
// ----------------------------------------------------
if (!isAutoMode() &&
state == NORMAL) {
if (!manualConfirmPending) {
manualConfirmPending = true;
manualConfirmTimer = millis();
normalView = NORMAL_MANUAL_CONFIRM;
shortBeep();
updateLCD();
} else {
if (millis() - manualConfirmTimer <=
MANUAL_CONFIRM_WINDOW) {
manualConfirmPending = false;
// Принудительный запуск
// даже при наличии основной сети
manualForcedRun = true;
startGeneratorSequence();
} else {
manualConfirmPending = false;
normalView = NORMAL_MENU;
updateLCD();
}
}
}
}
// ======================================================
// INPUT CHANGES
// ======================================================
void monitorInputs() {
bool currentAuto = isAutoMode();
bool currentWinter = isWinter();
bool currentMains = mainsPresent();
// -----------------------------------------------
// MODE CHANGE
// -----------------------------------------------
if (currentAuto != lastAutoMode) {
lastAutoMode = currentAuto;
// В NORMAL убираем старое подтверждение
if (state == NORMAL) {
manualConfirmPending = false;
normalView = NORMAL_MENU;
updateLCD();
}
}
// -----------------------------------------------
// SEASON CHANGE
// -----------------------------------------------
if (currentWinter != lastWinterMode) {
lastWinterMode = currentWinter;
// D8 влияет только на отображение
if (state == NORMAL) {
updateLCD();
}
}
// -----------------------------------------------
// MAIN POWER CHANGE
// -----------------------------------------------
if (currentMains != lastMainsPresent) {
lastMainsPresent = currentMains;
if (state == NORMAL) {
normalView = NORMAL_MENU;
updateLCD();
}
}
}
// ======================================================
// SETUP
// ======================================================
void setup() {
pinMode(PIN_IGNITION, OUTPUT);
pinMode(PIN_DAMPER, OUTPUT);
pinMode(PIN_STARTER, OUTPUT);
pinMode(PIN_GEN, OUTPUT);
pinMode(PIN_MAIN, OUTPUT);
pinMode(PIN_MODE, INPUT_PULLUP);
pinMode(PIN_SEASON, INPUT_PULLUP);
pinMode(PIN_MANUAL, INPUT_PULLUP);
pinMode(PIN_EMERGENCY, INPUT_PULLUP);
pinMode(PIN_BUZZER, OUTPUT);
// D12:
// HIGH = main ON
// LOW = main OFF
pinMode(PIN_MAINS, INPUT_PULLUP);
// -----------------------------------------------
// INITIAL OUTPUTS
// -----------------------------------------------
digitalWrite(PIN_MAIN, LOW);
digitalWrite(PIN_GEN, LOW);
setIgnition(false);
setStarter(false);
// Заслонка открыта
setDamper(false);
// -----------------------------------------------
// LCD
// -----------------------------------------------
lcd.init();
lcd.backlight();
// -----------------------------------------------
// BUTTON INIT
// -----------------------------------------------
manualButtonRaw = digitalRead(PIN_MANUAL);
manualButtonStable = manualButtonRaw;
manualButtonHandled =
(manualButtonRaw == LOW);
emergencyButtonRaw =
digitalRead(PIN_EMERGENCY);
emergencyButtonStable =
emergencyButtonRaw;
emergencyButtonHandled =
(emergencyButtonRaw == LOW);
// -----------------------------------------------
// INPUT MEMORY
// -----------------------------------------------
lastAutoMode = isAutoMode();
lastWinterMode = isWinter();
lastMainsPresent = mainsPresent();
// -----------------------------------------------
// INITIAL STATE
// -----------------------------------------------
if (mainsPresent()) {
setMainContactor(true);
state = NORMAL;
stateStartTime = millis();
normalView = NORMAL_MENU;
lcd.clear();
updateLCD();
} else {
if (isAutoMode() &&
!autoRestartBlocked &&
!manualStopBlocked) {
state = CONTROL_DELAY;
stateStartTime = millis();
lcd.clear();
updateLCD();
} else {
state = NORMAL;
stateStartTime = millis();
normalView = NORMAL_MENU;
lcd.clear();
updateLCD();
}
}
lcdUpdateTimer = millis();
}
// ======================================================
// LOOP
// ======================================================
void loop() {
// -----------------------------------------------
// BUZZER
// -----------------------------------------------
updateBuzzer();
// -----------------------------------------------
// BUTTONS
// -----------------------------------------------
readEmergencyButton();
readManualButton();
// -----------------------------------------------
// INPUT MONITOR
// -----------------------------------------------
monitorInputs();
// -----------------------------------------------
// MANUAL CONFIRM TIMEOUT
// -----------------------------------------------
if (manualConfirmPending &&
millis() - manualConfirmTimer >
MANUAL_CONFIRM_WINDOW) {
manualConfirmPending = false;
if (state == NORMAL) {
normalView = NORMAL_MENU;
updateLCD();
}
}
// -----------------------------------------------
// LCD UPDATE
// -----------------------------------------------
if (millis() - lcdUpdateTimer >=
LCD_UPDATE_TIME) {
lcdUpdateTimer = millis();
updateLCD();
}
// =================================================
// STATE MACHINE
// =================================================
switch (state) {
// =================================================
// NORMAL
// =================================================
case NORMAL:
setGeneratorContactor(false);
setStarter(false);
setIgnition(false);
// Заслонка открыта
setDamper(false);
if (mainsPresent())
setMainContactor(true);
else
setMainContactor(false);
// AUTO + сеть пропала
if (isAutoMode() &&
!mainsPresent() &&
!autoRestartBlocked &&
!manualStopBlocked) {
changeState(CONTROL_DELAY);
}
break;
// =================================================
// CONTROL DELAY
// =================================================
case CONTROL_DELAY:
setMainContactor(false);
setGeneratorContactor(false);
setIgnition(false);
setStarter(false);
// До запуска заслонка открыта
setDamper(false);
// Сеть вернулась
if (mainsPresent()) {
setMainContactor(true);
changeState(NORMAL);
break;
}
// Автозапуск заблокирован
if (autoRestartBlocked ||
manualStopBlocked) {
changeState(NORMAL);
break;
}
// Перешли в MANUAL
if (!isAutoMode()) {
changeState(NORMAL);
break;
}
// KD закончилась
if (millis() - stateStartTime >=
KD_TIME) {
manualForcedRun = false;
startGeneratorSequence();
}
break;
// =================================================
// STARTING
// =================================================
case STARTING:
setMainContactor(false);
setGeneratorContactor(false);
setIgnition(true);
// ВО ВРЕМЯ СТАРТЕРА ЗАСЛОНКА ЗАКРЫТА
setDamper(true);
setStarter(true);
// AUTO + сеть вернулась
if (!manualForcedRun &&
mainsPresent()) {
stopGenerator();
setMainContactor(true);
changeState(NORMAL);
break;
}
// Стартер 3 секунды
if (millis() - stateStartTime >=
STARTER_TIME) {
setStarter(false);
changeState(WARMUP);
}
break;
// =================================================
// WARMUP
// =================================================
case WARMUP:
setMainContactor(false);
setGeneratorContactor(false);
setIgnition(true);
setStarter(false);
// После стартера ещё 2 секунды
// заслонка закрыта
if (millis() - stateStartTime <
DAMPER_EXTRA_TIME) {
setDamper(true);
} else {
// Затем открывается
setDamper(false);
}
// AUTO + сеть вернулась
if (!manualForcedRun &&
mainsPresent()) {
stopGenerator();
setMainContactor(true);
changeState(NORMAL);
break;
}
// Окончание прогрева
if (millis() - stateStartTime >=
WARMUP_TIME) {
if (manualForcedRun) {
setMainContactor(false);
setGeneratorContactor(true);
changeState(RUNNING);
} else if (!mainsPresent()) {
setGeneratorContactor(true);
changeState(RUNNING);
} else {
stopGenerator();
setMainContactor(true);
changeState(NORMAL);
}
}
break;
// =================================================
// RUNNING
// =================================================
case RUNNING:
setIgnition(true);
setStarter(false);
// При работе заслонка всегда открыта
setDamper(false);
if (manualForcedRun) {
setMainContactor(false);
setGeneratorContactor(true);
break;
}
setMainContactor(false);
setGeneratorContactor(true);
// Вернулась основная сеть
if (mainsPresent()) {
changeState(RETURN_DELAY);
}
break;
// =================================================
// RETURN DELAY
// =================================================
case RETURN_DELAY:
// 10 секунд генератор всё ещё питает нагрузку
setMainContactor(false);
setGeneratorContactor(true);
setIgnition(true);
setStarter(false);
// Заслонка открыта
setDamper(false);
// Сеть снова исчезла
if (!mainsPresent()) {
changeState(RUNNING);
break;
}
// 10 секунд прошло
if (millis() - stateStartTime >=
MAINS_RETURN_DELAY) {
// Сначала снимаем нагрузку
// с генератора
setGeneratorContactor(false);
// Потом включаем сеть
setMainContactor(true);
changeState(COOLDOWN);
}
break;
// =================================================
// COOLDOWN
// =================================================
case COOLDOWN:
setMainContactor(true);
setGeneratorContactor(false);
setIgnition(true);
setStarter(false);
setDamper(false);
// Сеть снова пропала
if (!mainsPresent()) {
setMainContactor(false);
setGeneratorContactor(true);
changeState(RUNNING);
break;
}
// 20 секунд охлаждения
if (millis() - stateStartTime >=
COOLDOWN_TIME) {
stopGenerator();
setMainContactor(true);
manualForcedRun = false;
changeState(NORMAL);
}
break;
// =================================================
// EMERGENCY
// =================================================
case EMERGENCY:
stopGenerator();
if (mainsPresent())
setMainContactor(true);
else
setMainContactor(false);
break;
}
}ALARM
START
режим работы
сезон работы
заслонка
стартер
зажигание
основной ввод
ввод генератор
Имитация
зажигание
заслонка
стартер
ввод генератор
основной ввод