banddecoder_code
Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия | ||
| banddecoder_code [2026/01/16 20:37] – eu8t | banddecoder_code [2026/01/23 08:04] (текущий) – eu8t | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | IP 192.168.0.240 | + | IP 192.168.0.240 |
| - | v0.44 | + | v0.81 (примерно) |
| + | |||
| + | {{: | ||
| + | |||
| + | {{: | ||
| - | {{: | ||
| Строка 12: | Строка 15: | ||
| #include < | #include < | ||
| #include < | #include < | ||
| + | #include < | ||
| - | const float VERSION | + | // ===================================================== |
| - | LiquidCrystal_I2C lcd(0x27, 16, 2); | + | // === CONFIG: все константы устройства в одном месте === |
| + | // ===================================================== | ||
| - | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; | + | // --- EEPROM --- |
| - | IPAddress ip(192, 168, 0, 240); | + | const byte EEPROM_MAGIC |
| - | unsigned | + | const int EEPROM_ADDR_MAGIC = 0; |
| + | const int | ||
| - | EthernetUDP Udp; | + | // --- Версия прошивки --- |
| + | const float VERSION = 0.81; | ||
| + | // --- CAT / сеть --- | ||
| const unsigned long CAT_TIMEOUT = 7000; | const unsigned long CAT_TIMEOUT = 7000; | ||
| const unsigned long NET_TIMEOUT = 5000; | const unsigned long NET_TIMEOUT = 5000; | ||
| + | // --- Пины энкодера --- | ||
| const int ENC_A = 8; | const int ENC_A = 8; | ||
| const int ENC_B = A1; | const int ENC_B = A1; | ||
| const int ENC_BTN = 3; | const int ENC_BTN = 3; | ||
| + | // --- Пины реле --- | ||
| const int RELAYS[] = {4, 5, 6, 7}; | const int RELAYS[] = {4, 5, 6, 7}; | ||
| const int RELAY_COUNT = 4; | const int RELAY_COUNT = 4; | ||
| - | enum Mode { AUTO, MANUAL }; | + | // --- Кнопка --- |
| - | Mode mode = AUTO; | + | const unsigned long SHORT_PRESS = 100; |
| + | const unsigned long LONG_PRESS | ||
| + | const unsigned long HOLD_TIME | ||
| - | bool manualForced = false; | + | // --- Редактор IP --- |
| - | bool autoFallback | + | const unsigned long EDITOR_CLICK_GUARD |
| + | // --- Анимация CAT --- | ||
| + | const int CAT_ANIM_STEPS = 5; | ||
| + | |||
| + | |||
| + | // ===================================================== | ||
| + | // === ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ (состояние устройства) === | ||
| + | // ===================================================== | ||
| + | |||
| + | // --- IP редактор --- | ||
| + | bool ipEditMode = false; | ||
| + | byte ipEdit[4]; | ||
| + | int ipOctetIndex = 0; | ||
| + | |||
| + | // --- LCD --- | ||
| + | LiquidCrystal_I2C lcd(0x27, 16, 2); | ||
| + | |||
| + | // --- Ethernet --- | ||
| + | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; | ||
| + | IPAddress ip(192, 168, 0, 240); | ||
| + | unsigned int localPort = 12060; | ||
| + | IPAddress effectiveIP = ip; | ||
| + | EthernetUDP Udp; | ||
| + | |||
| + | // --- CAT состояние --- | ||
| unsigned long lastCatTime = 0; | unsigned long lastCatTime = 0; | ||
| bool catActive = false; | bool catActive = false; | ||
| - | |||
| unsigned long lastPacketTime = 0; | unsigned long lastPacketTime = 0; | ||
| - | |||
| long lastFreq = 0; | long lastFreq = 0; | ||
| - | int currentBand | + | bool freqReceived |
| - | int manualBandIndex | + | byte catAnimPos = 0; |
| + | int catPacketCounter | ||
| + | |||
| + | byte catDot[8] = { | ||
| + | 0b00000, | ||
| + | 0b00100, | ||
| + | 0b01110, | ||
| + | 0b01110, | ||
| + | 0b01110, | ||
| + | 0b00100, | ||
| + | 0b00000, | ||
| + | 0b00000 | ||
| + | }; | ||
| + | |||
| + | // --- Режимы --- | ||
| + | enum Mode { AUTO, MANUAL }; | ||
| + | Mode mode = AUTO; | ||
| + | bool manualForced = false; | ||
| + | // --- Диапазоны --- | ||
| struct Band { | struct Band { | ||
| const char* name; | const char* name; | ||
| Строка 64: | Строка 116: | ||
| const int BAND_COUNT = sizeof(bands) / sizeof(Band); | const int BAND_COUNT = sizeof(bands) / sizeof(Band); | ||
| + | |||
| + | // --- Текущее состояние диапазонов --- | ||
| + | int currentBand = -1; | ||
| + | int manualBandIndex = 0; | ||
| + | |||
| + | // --- Энкодер --- | ||
| + | int lastA = HIGH; | ||
| + | |||
| + | // --- Кнопка --- | ||
| + | bool buttonHeld = false; | ||
| + | bool pressed = false; | ||
| + | unsigned long pressStart = 0; | ||
| + | bool ignoreNextEditorClick = false; | ||
| + | |||
| + | // --- LCD обновление --- | ||
| + | Mode lastMode = AUTO; | ||
| + | int lastBandIndex = -1; | ||
| + | long lastDisplayedFreq = 0; | ||
| + | bool forceDisplayUpdate = false; | ||
| int detectBand(long freq) { | int detectBand(long freq) { | ||
| Строка 90: | Строка 161: | ||
| setRelaysByABCD(0); | setRelaysByABCD(0); | ||
| } | } | ||
| + | |||
| + | void saveIPToEEPROM(IPAddress ipToSave) { | ||
| + | EEPROM.update(EEPROM_ADDR_MAGIC, | ||
| + | for (int i = 0; i < 4; i++) | ||
| + | EEPROM.update(EEPROM_ADDR_IP + i, ipToSave[i]); | ||
| + | } | ||
| + | |||
| + | bool loadIPFromEEPROM(IPAddress &out) { | ||
| + | if (EEPROM.read(EEPROM_ADDR_MAGIC) != EEPROM_MAGIC) return false; | ||
| + | byte b[4]; | ||
| + | for (int i = 0; i < 4; i++) | ||
| + | b[i] = EEPROM.read(EEPROM_ADDR_IP + i); | ||
| + | out = IPAddress(b[0], | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | void enterIpEditMode() { | ||
| + | ipEditMode = true; | ||
| + | |||
| + | for (int i = 0; i < 4; i++) | ||
| + | ipEdit[i] = effectiveIP[i]; | ||
| + | |||
| + | ipOctetIndex = 0; | ||
| + | drawIpEditor(); | ||
| + | ignoreNextEditorClick = true; | ||
| + | } | ||
| + | |||
| + | void drawIpEditor() { | ||
| + | lcd.clear(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | |||
| + | lcd.setCursor(0, | ||
| + | |||
| + | for (int i = 0; i < 4; i++) { | ||
| + | |||
| + | // подчёркивание перед редактируемым октетом | ||
| + | if (i == ipOctetIndex) | ||
| + | lcd.print(" | ||
| + | else | ||
| + | lcd.print("" | ||
| + | |||
| + | // вывод октета с ведущими нулями | ||
| + | int val = ipEdit[i]; | ||
| + | if (val < 100) lcd.print(" | ||
| + | if (val < 10) lcd.print(" | ||
| + | lcd.print(val); | ||
| + | |||
| + | if (i < 3) lcd.print(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| void printFreqCompact(long f) { | void printFreqCompact(long f) { | ||
| Строка 107: | Строка 231: | ||
| } | } | ||
| - | Mode lastMode = AUTO; | + | |
| - | int lastBandIndex | + | void runRelayTest() { |
| - | long lastDisplayedFreq | + | lcd.clear(); |
| - | bool forceDisplayUpdate = false; | + | |
| + | lcd.print(" | ||
| + | |||
| + | for (int i = 0; i < BAND_COUNT; i++) { | ||
| + | |||
| + | // включаем реле | ||
| + | setRelaysByBand(i); | ||
| + | |||
| + | // выводим комбинацию ABCD | ||
| + | lcd.setCursor(0, | ||
| + | for (int b = 3; b >= 0; b--) | ||
| + | lcd.print((bands[i].abcdCode >> b) & 1); | ||
| + | |||
| + | delay(600); | ||
| + | } | ||
| + | |||
| + | disableAllRelays(); | ||
| + | delay(300); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| void updateLCDIfNeeded() { | void updateLCDIfNeeded() { | ||
| + | if (ipEditMode) return; | ||
| bool changed = false; | bool changed = false; | ||
| Строка 123: | Строка 269: | ||
| if (!changed) return; | if (!changed) return; | ||
| - | | + | |
| - | | + | |
| - | lcd.print(ip); | + | |
| lcd.setCursor(0, | lcd.setCursor(0, | ||
| lcd.print(" | lcd.print(" | ||
| Строка 138: | Строка 282: | ||
| lcd.print(" | lcd.print(" | ||
| if (catActive) printFreqCompact(lastFreq); | if (catActive) printFreqCompact(lastFreq); | ||
| - | else lcd.print(" | + | else lcd.print(" |
| lastBandIndex = currentBand; | lastBandIndex = currentBand; | ||
| Строка 154: | Строка 298: | ||
| lastMode = mode; | lastMode = mode; | ||
| } | } | ||
| + | |||
| + | void updateTopLine() { | ||
| + | if (ipEditMode) return; | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.setCursor(0, | ||
| + | |||
| + | // === AUTO режим === | ||
| + | if (mode == AUTO) { | ||
| + | lcd.print(" | ||
| + | |||
| + | if (catActive) { | ||
| + | // 5-позиционная анимация | ||
| + | for (int i = 0; i < 5; i++) { | ||
| + | if (i == catAnimPos) | ||
| + | lcd.write(byte(0)); | ||
| + | else | ||
| + | lcd.print(" | ||
| + | } | ||
| + | |||
| + | lcd.print(" | ||
| + | |||
| + | // Показ реле | ||
| + | if (currentBand >= 0) { | ||
| + | for (int i = 3; i >= 0; i--) | ||
| + | lcd.print((bands[currentBand].abcdCode >> i) & 1); | ||
| + | } else { | ||
| + | lcd.print(" | ||
| + | } | ||
| + | |||
| + | } else { | ||
| + | // CAT пропал | ||
| + | lcd.print(" | ||
| + | if (currentBand >= 0) { | ||
| + | for (int i = 3; i >= 0; i--) | ||
| + | lcd.print((bands[currentBand].abcdCode >> i) & 1); | ||
| + | } else { | ||
| + | lcd.print(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | return; | ||
| + | } | ||
| + | |||
| + | // === MANUAL режим === | ||
| + | lcd.print(" | ||
| + | if (catActive) lcd.print(" | ||
| + | else lcd.print(" | ||
| + | } | ||
| + | |||
| + | |||
| void splashScreen() { | void splashScreen() { | ||
| Строка 161: | Строка 356: | ||
| lcd.print(VERSION, | lcd.print(VERSION, | ||
| lcd.setCursor(0, | lcd.setCursor(0, | ||
| - | lcd.print(" | + | lcd.print(" |
| delay(2000); | delay(2000); | ||
| } | } | ||
| + | |||
| void readUDP() { | void readUDP() { | ||
| Строка 174: | Строка 370: | ||
| buf[len] = 0; | buf[len] = 0; | ||
| + | // Любой пакет = связь жива | ||
| lastPacketTime = millis(); | lastPacketTime = millis(); | ||
| + | lastCatTime = millis(); | ||
| + | catActive = true; | ||
| - | | + | |
| - | if (!f) return; | + | if (mode == MANUAL && !manualForced) { |
| + | mode = AUTO; | ||
| + | | ||
| + | | ||
| + | // Восстанавливаем реле по текущему диапазону | ||
| + | | ||
| + | setRelaysByBand(currentBand); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| - | | + | |
| - | | + | // Двигаем CAT-анимацию каждые 2 пакета |
| + | catPacketCounter++; | ||
| + | | ||
| + | catPacketCounter = 0; | ||
| + | catAnimPos | ||
| + | updateTopLine(); | ||
| + | } | ||
| - | int b = detectBand(lastFreq); | ||
| - | if (b >= 0) currentBand = b; | ||
| - | | + | |
| - | | + | char* f = strstr(buf, "< |
| + | | ||
| + | f += 6; | ||
| - | if (mode == MANUAL && autoFallback) { | + | lastFreq = atol(f) * 10; |
| - | mode = AUTO; | + | |
| - | | + | |
| - | manualForced = false; | + | |
| - | setRelaysByBand(currentBand); | + | |
| - | } | + | |
| - | if (mode == AUTO) | + | int b = detectBand(lastFreq); |
| - | | + | |
| + | currentBand = b; | ||
| + | // В AUTO обновляем реле | ||
| + | if (mode == AUTO && currentBand >= 0) | ||
| + | setRelaysByBand(currentBand); | ||
| + | } | ||
| + | |||
| + | // Если < | ||
| updateLCDIfNeeded(); | updateLCDIfNeeded(); | ||
| } | } | ||
| - | // | ||
| - | // === КНОПКА С АНТИДРЕБЕЗГОМ И ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ === | ||
| - | // | ||
| - | void handleButton() { | ||
| - | static bool pressed = false; | ||
| - | static unsigned long pressStart = 0; | ||
| - | static unsigned long lastCheck = 0; | ||
| - | const unsigned long SHORT_PRESS = 100; | ||
| - | const unsigned long LONG_PRESS | ||
| - | if (millis() - lastCheck < 30) return; | + | void handleButtonPress() { |
| - | lastCheck = millis(); | + | |
| - | + | ||
| - | bool btn = !digitalRead(ENC_BTN); | + | |
| - | + | ||
| - | if (btn && !pressed) { | + | |
| pressed = true; | pressed = true; | ||
| pressStart = millis(); | pressStart = millis(); | ||
| - | | + | buttonHeld = false; |
| + | } | ||
| - | if (!btn && pressed) { | + | void handleButtonHold(unsigned long held) { |
| - | | + | |
| - | pressed = false; | + | |
| + | // длинное → вход в редактор | ||
| + | if (!ipEditMode && held >= LONG_PRESS) { | ||
| + | enterIpEditMode(); | ||
| + | pressed = false; | ||
| + | buttonHeld = false; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // жест удержания | ||
| + | if (!ipEditMode && !buttonHeld && held >= HOLD_TIME) { | ||
| + | buttonHeld = true; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void handleIpEditorClick(unsigned long pressTime) { | ||
| + | static unsigned long lastEditorClick = 0; | ||
| + | |||
| + | // игнорируем первый клик после входа в редактор | ||
| + | if (ignoreNextEditorClick) { | ||
| + | ignoreNextEditorClick = false; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // длинное → выход без сохранения | ||
| if (pressTime >= LONG_PRESS) { | if (pressTime >= LONG_PRESS) { | ||
| - | lcd.clear(); | + | ipEditMode = false; |
| - | | + | |
| - | | + | |
| + | return; | ||
| + | } | ||
| - | for (int i = 0; i < BAND_COUNT; i++) { | + | // защита от двойного клика |
| - | setRelaysByBand(i); | + | if (millis() - lastEditorClick |
| - | delay(500); | + | |
| - | } | + | |
| - | disableAllRelays(); | + | // короткое → следующий октет |
| - | | + | if (pressTime >= 50) { |
| + | | ||
| - | lcd.clear(); | + | if (ipOctetIndex > 3) { |
| - | | + | |
| - | lcd.print(" | + | |
| - | lcd.setCursor(0,1); | + | |
| - | for (int i = 0; i < BAND_COUNT; i++) { | + | |
| - | for (int b = 3; b >= 0; b--) | + | |
| - | lcd.print((bands[i].abcdCode >> b) & 1); | + | |
| - | | + | |
| - | } | + | |
| - | delay(1500); | + | |
| - | // ВОССТАНАВЛИВАЕМ РЕЛЕ | + | lcd.clear(); |
| - | | + | |
| - | | + | |
| - | else | + | |
| - | | + | lcd.print(effectiveIP); |
| + | delay(1200); | ||
| - | | + | ipEditMode = false; |
| - | updateLCDIfNeeded(); | + | |
| - | return; | + | updateLCDIfNeeded(); |
| + | return; | ||
| + | } | ||
| + | |||
| + | drawIpEditor(); | ||
| } | } | ||
| + | } | ||
| - | if (pressTime >= SHORT_PRESS) { | + | void toggleAutoManual() { |
| - | if (mode == AUTO) { | + | |
| + | | ||
| mode = MANUAL; | mode = MANUAL; | ||
| manualForced = true; | manualForced = true; | ||
| - | | + | |
| - | setRelaysByBand(currentBand); | + | |
| - | } else { | + | manualBandIndex |
| + | setRelaysByBand(manualBandIndex); | ||
| + | } | ||
| + | | ||
| + | if (!catActive) { | ||
| + | lcd.clear(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | delay(1200); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | return; | ||
| + | } | ||
| mode = AUTO; | mode = AUTO; | ||
| manualForced = false; | manualForced = false; | ||
| - | | + | |
| - | setRelaysByBand(currentBand); | + | |
| - | } | + | |
| - | forceDisplayUpdate | + | if (currentBand >= 0) |
| - | | + | setRelaysByBand(currentBand); |
| + | | ||
| + | disableAllRelays(); | ||
| } | } | ||
| - | } | + | |
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| } | } | ||
| + | |||
| + | void handleButtonRelease(unsigned long pressTime) { | ||
| + | |||
| + | // редактор | ||
| + | if (ipEditMode) { | ||
| + | handleIpEditorClick(pressTime); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // длинное → вход в редактор | ||
| + | if (pressTime >= LONG_PRESS) { | ||
| + | enterIpEditMode(); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // короткое → AUTO/MANUAL | ||
| + | if (!buttonHeld && pressTime >= SHORT_PRESS) { | ||
| + | toggleAutoManual(); | ||
| + | } | ||
| + | |||
| + | buttonHeld = false; | ||
| + | } | ||
| + | |||
| + | |||
| + | // | ||
| + | // === КНОПКА С АНТИДРЕБЕЗГОМ И ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ === | ||
| + | // | ||
| + | void handleButton() { | ||
| + | static unsigned long lastCheck = 0; | ||
| + | |||
| + | if (millis() - lastCheck < 30) return; | ||
| + | lastCheck = millis(); | ||
| + | |||
| + | bool btn = !digitalRead(ENC_BTN); | ||
| + | |||
| + | // начало нажатия | ||
| + | if (btn && !pressed) { | ||
| + | handleButtonPress(); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // удержание | ||
| + | if (btn && pressed) { | ||
| + | unsigned long held = millis() - pressStart; | ||
| + | handleButtonHold(held); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // отпускание | ||
| + | if (!btn && pressed) { | ||
| + | pressed = false; | ||
| + | unsigned long pressTime = millis() - pressStart; | ||
| + | handleButtonRelease(pressTime); | ||
| + | return; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| // | // | ||
| Строка 284: | Строка 589: | ||
| // | // | ||
| void handleEncoder() { | void handleEncoder() { | ||
| - | | + | static |
| - | + | ||
| - | | + | |
| int A = digitalRead(ENC_A); | int A = digitalRead(ENC_A); | ||
| + | if (A == lastA) return; | ||
| + | |||
| + | // реагируем только на фронт A == LOW | ||
| if (A != lastA && A == LOW) { | if (A != lastA && A == LOW) { | ||
| + | |||
| bool clockwise = digitalRead(ENC_B); | bool clockwise = digitalRead(ENC_B); | ||
| - | | + | |
| - | manualBandIndex | + | |
| - | | + | |
| - | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | + | |
| - | | + | if (clockwise) { |
| - | forceDisplayUpdate = true; | + | runRelayTest(); |
| - | updateLCDIfNeeded(); | + | } else { |
| + | // резерв | ||
| + | } | ||
| + | |||
| + | buttonHeld = false; | ||
| + | lastA = A; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // === РЕЖИМ IP-РЕДАКТОРА === | ||
| + | if (ipEditMode) { | ||
| + | |||
| + | if (millis() - lastMove < 80) { | ||
| + | lastA = A; | ||
| + | return; | ||
| + | } | ||
| + | lastMove = millis(); | ||
| + | |||
| + | int val = ipEdit[ipOctetIndex]; | ||
| + | if (clockwise) val++; | ||
| + | else val--; | ||
| + | |||
| + | if (val < 0) val = 255; | ||
| + | if (val > 255) val = 0; | ||
| + | |||
| + | ipEdit[ipOctetIndex] = val; | ||
| + | drawIpEditor(); | ||
| + | |||
| + | lastA = A; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // === MANUAL режим === | ||
| + | if (mode == MANUAL) { | ||
| + | |||
| + | if (millis() - lastMove < 50) { | ||
| + | lastA = A; | ||
| + | return; | ||
| + | } | ||
| + | lastMove = millis(); | ||
| + | |||
| + | if (clockwise) | ||
| + | manualBandIndex = (manualBandIndex + 1) % BAND_COUNT; | ||
| + | else | ||
| + | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | ||
| + | |||
| + | | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| } | } | ||
| + | |||
| lastA = A; | lastA = A; | ||
| } | } | ||
| + | |||
| + | |||
| + | |||
| void checkCatTimeout() { | void checkCatTimeout() { | ||
| - | if (mode != AUTO) return; | ||
| + | // 1) Сбрасываем CAT независимо от режима | ||
| if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | ||
| - | | + | |
| - | mode = MANUAL; | + | |
| - | autoFallback = true; | + | |
| - | manualForced = false; | + | |
| - | manualBandIndex = (currentBand >= 0) ? currentBand : 0; | + | |
| - | disableAllRelays(); | + | // ОБНОВЛЯЕМ ПЕРВУЮ СТРОКУ |
| - | forceDisplayUpdate = true; | + | updateTopLine(); |
| - | updateLCDIfNeeded(); | + | } |
| + | |||
| + | // 2) Логика fallback только в AUTO | ||
| + | if (mode == AUTO && !catActive) { | ||
| + | mode = MANUAL; | ||
| + | manualForced = false; | ||
| + | |||
| + | if (currentBand >= 0) { | ||
| + | manualBandIndex = currentBand; | ||
| + | } | ||
| + | setRelaysByBand(manualBandIndex); | ||
| + | |||
| + | | ||
| + | updateLCDIfNeeded(); | ||
| } | } | ||
| } | } | ||
| - | void setup() { | + | |
| + | |||
| + | |||
| + | void setup() { | ||
| + | // | ||
| + | |||
| + | // --- Пины энкодера --- | ||
| pinMode(ENC_A, | pinMode(ENC_A, | ||
| pinMode(ENC_B, | pinMode(ENC_B, | ||
| pinMode(ENC_BTN, | pinMode(ENC_BTN, | ||
| + | // --- Пины реле --- | ||
| for (int i = 0; i < RELAY_COUNT; | for (int i = 0; i < RELAY_COUNT; | ||
| pinMode(RELAYS[i], | pinMode(RELAYS[i], | ||
| Строка 332: | Строка 706: | ||
| disableAllRelays(); | disableAllRelays(); | ||
| + | |||
| + | // --- LCD --- | ||
| lcd.begin(); | lcd.begin(); | ||
| lcd.backlight(); | lcd.backlight(); | ||
| + | |||
| + | // если есть сохранённый — подхватим | ||
| + | effectiveIP = ip; | ||
| + | loadIPFromEEPROM(effectiveIP); | ||
| + | |||
| + | // Регистрируем кастомный символ CAT‑точки | ||
| + | lcd.createChar(0, | ||
| + | |||
| + | // Splash screen | ||
| splashScreen(); | splashScreen(); | ||
| - | Ethernet.begin(mac, | + | |
| + | | ||
| Udp.begin(localPort); | Udp.begin(localPort); | ||
| + | // Первичное отображение IP (потом будет перерисовано updateTopLine) | ||
| lcd.clear(); | lcd.clear(); | ||
| lcd.setCursor(0, | lcd.setCursor(0, | ||
| lcd.print(" | lcd.print(" | ||
| - | lcd.print(ip); | + | lcd.print(effectiveIP); |
| lcd.setCursor(0, | lcd.setCursor(0, | ||
| lcd.print(" | lcd.print(" | ||
| + | // --- Начальные значения логики --- | ||
| currentBand = -1; | currentBand = -1; | ||
| catActive = false; | catActive = false; | ||
| + | manualBandIndex = 0; | ||
| + | lastFreq = 0; | ||
| lastPacketTime = millis(); | lastPacketTime = millis(); | ||
| + | |||
| disableAllRelays(); | disableAllRelays(); | ||
| - | | + | |
| + | // Обновляем дисплей по новой логике | ||
| + | | ||
| updateLCDIfNeeded(); | updateLCDIfNeeded(); | ||
| + | |||
| + | // Инициализация энкодера (важно!) | ||
| + | lastA = digitalRead(ENC_A); | ||
| } | } | ||
| - | void loop() { | + | |
| + | void loop() { | ||
| + | // === Всегда читаем UDP === | ||
| readUDP(); | readUDP(); | ||
| + | |||
| + | // === Обработка энкодера и кнопки === | ||
| handleEncoder(); | handleEncoder(); | ||
| handleButton(); | handleButton(); | ||
| + | |||
| + | // === Проверка таймаута CAT === | ||
| checkCatTimeout(); | checkCatTimeout(); | ||
| + | // === Периодическое обновление дисплея === | ||
| static unsigned long lastDisplayUpdate = 0; | static unsigned long lastDisplayUpdate = 0; | ||
| - | if (millis() - lastDisplayUpdate > 1000) { | + | if (!ipEditMode && |
| lastDisplayUpdate = millis(); | lastDisplayUpdate = millis(); | ||
| updateLCDIfNeeded(); | updateLCDIfNeeded(); | ||
| - | } | + | } |
| - | + | ||
| - | if (millis() - lastPacketTime > NET_TIMEOUT && mode == AUTO && !catActive) { | + | |
| - | static unsigned long lastStatusUpdate = 0; | + | |
| - | if (millis() - lastStatusUpdate > 1000) { | + | |
| - | lastStatusUpdate = millis(); | + | |
| - | lcd.setCursor(0, | + | |
| - | lcd.print(" | + | |
| - | lcd.print(ip); | + | |
| - | lcd.setCursor(0, | + | |
| - | lcd.print(" | + | |
| - | } | + | |
| - | } | + | |
| delay(10); | delay(10); | ||
| } | } | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| </ | </ | ||
banddecoder_code.1768585034.txt.gz · Последнее изменение: — eu8t
