banddecoder_code
Различия
Показаны различия между двумя версиями страницы.
| Следующая версия | Предыдущая версия | ||
| banddecoder_code [2026/01/10 16:27] – создано eu8t | banddecoder_code [2026/01/23 08:04] (текущий) – eu8t | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | CODE | + | IP 192.168.0.240 (можно сменить через настройки) |
| + | v0.81 (примерно) | ||
| + | {{: | ||
| + | |||
| + | {{: | ||
| + | |||
| + | |||
| + | |||
| + | < | ||
| #include < | #include < | ||
| #include < | #include < | ||
| Строка 7: | Строка 15: | ||
| #include < | #include < | ||
| #include < | #include < | ||
| + | #include < | ||
| - | /* ================= | + | // ===================================================== |
| + | // === CONFIG: все константы устройства в одном месте === | ||
| + | // ===================================================== | ||
| - | LiquidCrystal_I2C lcd(0x27, 16, 2); | + | // --- EEPROM --- |
| + | const byte EEPROM_MAGIC | ||
| + | const int EEPROM_ADDR_MAGIC = 0; | ||
| + | const int EEPROM_ADDR_IP | ||
| - | // Ethernet | + | // --- Версия прошивки --- |
| - | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; | + | const float VERSION |
| - | IPAddress ip(192, 168, 0, 240); // IP декодера | + | |
| - | unsigned int localPort | + | |
| - | // CAT timeout | + | // --- CAT / сеть --- |
| const unsigned long CAT_TIMEOUT = 7000; | const unsigned long CAT_TIMEOUT = 7000; | ||
| + | const unsigned long NET_TIMEOUT = 5000; | ||
| - | // Энкодер | + | // --- Пины энкодера --- |
| - | const int ENC_A | + | const int ENC_A |
| - | const int ENC_B | + | const int ENC_B |
| - | const int ENC_BTN = 2; | + | const int ENC_BTN = 3; |
| - | // Реле | + | // --- Пины реле |
| - | const int RELAYS[] = {4, 5, 6, 7, 8, 9}; | + | const int RELAYS[] = {4, 5, 6, 7}; |
| - | const int RELAY_COUNT = sizeof(RELAYS) / sizeof(int); | + | const int RELAY_COUNT = 4; |
| - | /* ================= СОСТОЯНИЯ ================= */ | + | // --- Кнопка --- |
| + | const unsigned long SHORT_PRESS | ||
| + | const unsigned long LONG_PRESS | ||
| + | const unsigned long HOLD_TIME | ||
| - | enum Mode { AUTO, MANUAL }; | + | // --- Редактор IP --- |
| - | Mode mode = AUTO; | + | const unsigned long EDITOR_CLICK_GUARD |
| - | // Причины MANUAL | + | // --- Анимация CAT --- |
| - | bool manualForced | + | const int CAT_ANIM_STEPS |
| - | bool autoFallback = false; | + | |
| + | |||
| + | // ===================================================== | ||
| + | // === ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ (состояние устройства) === | ||
| + | // ===================================================== | ||
| + | |||
| + | // --- 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; | EthernetUDP Udp; | ||
| + | // --- CAT состояние --- | ||
| unsigned long lastCatTime = 0; | unsigned long lastCatTime = 0; | ||
| bool catActive = false; | bool catActive = false; | ||
| + | unsigned long lastPacketTime = 0; | ||
| long lastFreq = 0; | long lastFreq = 0; | ||
| - | int currentBand | + | bool freqReceived = false; |
| - | int manualBandIndex | + | byte catAnimPos |
| + | int catPacketCounter | ||
| - | /* ================= ДИАПАЗОНЫ ================= */ | + | byte catDot[8] = { |
| + | 0b00000, | ||
| + | 0b00100, | ||
| + | 0b01110, | ||
| + | 0b01110, | ||
| + | 0b01110, | ||
| + | 0b00100, | ||
| + | 0b00000, | ||
| + | 0b00000 | ||
| + | }; | ||
| + | |||
| + | // --- Режимы --- | ||
| + | enum Mode { AUTO, MANUAL }; | ||
| + | Mode mode = AUTO; | ||
| + | bool manualForced | ||
| + | // --- Диапазоны --- | ||
| struct Band { | struct Band { | ||
| const char* name; | const char* name; | ||
| long from; | long from; | ||
| long to; | long to; | ||
| + | byte abcdCode; | ||
| }; | }; | ||
| Band bands[] = { | Band bands[] = { | ||
| - | {" | + | {" |
| - | {" | + | {" |
| - | {" | + | {" |
| - | {" | + | {" |
| - | {" | + | {" |
| - | {" | + | {" |
| }; | }; | ||
| const int BAND_COUNT = sizeof(bands) / sizeof(Band); | const int BAND_COUNT = sizeof(bands) / sizeof(Band); | ||
| - | /* ================= СЛУЖЕБНЫЕ ================= */ | + | // --- Текущее состояние диапазонов --- |
| + | int currentBand | ||
| + | int manualBandIndex | ||
| + | |||
| + | // --- Энкодер --- | ||
| + | int lastA = HIGH; | ||
| + | |||
| + | // --- Кнопка --- | ||
| + | bool buttonHeld | ||
| + | bool pressed | ||
| + | unsigned long pressStart | ||
| + | bool ignoreNextEditorClick | ||
| + | |||
| + | // --- LCD обновление --- | ||
| + | Mode lastMode | ||
| + | int lastBandIndex | ||
| + | long lastDisplayedFreq | ||
| + | bool forceDisplayUpdate | ||
| int detectBand(long freq) { | int detectBand(long freq) { | ||
| Строка 76: | Строка 144: | ||
| } | } | ||
| - | void setRelays(int bandIndex) { | + | void setRelaysByABCD(byte abcdCode) { |
| - | for (int i = 0; i < RELAY_COUNT; | + | for (int i = 0; i < RELAY_COUNT; |
| - | digitalWrite(RELAYS[i], | + | bool state = (abcdCode >> i) & 1; |
| + | digitalWrite(RELAYS[i], | ||
| + | } | ||
| + | } | ||
| - | if (bandIndex | + | void setRelaysByBand(int bandIndex) |
| - | | + | |
| - | | + | |
| + | else | ||
| + | setRelaysByABCD(0); | ||
| } | } | ||
| - | // Формат: 7.165.7 | + | void disableAllRelays() { |
| + | 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) { | ||
| int MHz = f / 1000000; | int MHz = f / 1000000; | ||
| int kHz = (f / 1000) % 1000; | int kHz = (f / 1000) % 1000; | ||
| int hundred = (f / 100) % 10; | int hundred = (f / 100) % 10; | ||
| + | int tens = (f / 10) % 10; | ||
| lcd.print(MHz); | lcd.print(MHz); | ||
| Строка 98: | Строка 228: | ||
| lcd.print(" | lcd.print(" | ||
| lcd.print(hundred); | lcd.print(hundred); | ||
| + | lcd.print(tens); | ||
| } | } | ||
| - | /* ================= ЭКРАН ================= */ | ||
| - | void drawScreen() { | + | void runRelayTest() { |
| lcd.clear(); | lcd.clear(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| - | lcd.setCursor(0, | + | |
| - | lcd.print(" | + | |
| - | lcd.print(ip); | + | // включаем реле |
| + | setRelaysByBand(i); | ||
| + | |||
| + | // выводим комбинацию ABCD | ||
| + | | ||
| + | for (int b = 3; b >= 0; b--) | ||
| + | lcd.print((bands[i].abcdCode >> b) & 1); | ||
| + | |||
| + | delay(600); | ||
| + | } | ||
| + | |||
| + | disableAllRelays(); | ||
| + | delay(300); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| + | |||
| + | void updateLCDIfNeeded() { | ||
| + | if (ipEditMode) return; | ||
| + | bool changed = false; | ||
| + | |||
| + | if (lastMode != mode) changed = true; | ||
| + | if (mode != AUTO && lastBandIndex != manualBandIndex) changed = true; | ||
| + | if (mode == AUTO && lastBandIndex != currentBand) changed = true; | ||
| + | if (mode == AUTO && lastDisplayedFreq != lastFreq) changed = true; | ||
| + | if (forceDisplayUpdate) { changed = true; forceDisplayUpdate = false; } | ||
| + | |||
| + | if (!changed) return; | ||
| + | |||
| + | updateTopLine(); | ||
| + | |||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.setCursor(0,1); | ||
| - | lcd.setCursor(0, | ||
| if (mode == AUTO) { | if (mode == AUTO) { | ||
| - | lcd.print(" | + | lcd.print(" |
| if (currentBand >= 0) lcd.print(bands[currentBand].name); | if (currentBand >= 0) lcd.print(bands[currentBand].name); | ||
| else lcd.print(" | else lcd.print(" | ||
| + | |||
| lcd.print(" | lcd.print(" | ||
| if (catActive) printFreqCompact(lastFreq); | if (catActive) printFreqCompact(lastFreq); | ||
| - | else lcd.print(" | + | else lcd.print(" |
| + | |||
| + | lastBandIndex = currentBand; | ||
| + | lastDisplayedFreq = lastFreq; | ||
| } else { | } else { | ||
| - | lcd.print(" | + | lcd.print(" |
| lcd.print(bands[manualBandIndex].name); | lcd.print(bands[manualBandIndex].name); | ||
| + | lcd.print(" | ||
| + | for (int i = 3; i >= 0; i--) | ||
| + | lcd.print((bands[manualBandIndex].abcdCode >> i) & 1); | ||
| + | |||
| + | lastBandIndex = manualBandIndex; | ||
| } | } | ||
| + | |||
| + | 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() { | ||
| lcd.clear(); | lcd.clear(); | ||
| - | lcd.setCursor(0, | + | lcd.setCursor(0, |
| - | lcd.print(" | + | lcd.print(" |
| - | lcd.setCursor(0, | + | lcd.print(VERSION, |
| - | lcd.print(" | + | lcd.setCursor(0, |
| + | lcd.print(" | ||
| delay(2000); | delay(2000); | ||
| } | } | ||
| - | /* ================= UDP ================= */ | ||
| void readUDP() { | void readUDP() { | ||
| Строка 140: | Строка 365: | ||
| if (!packetSize) return; | if (!packetSize) return; | ||
| - | char buf[400]; | + | char buf[200]; |
| int len = Udp.read(buf, | int len = Udp.read(buf, | ||
| if (len <= 0) return; | if (len <= 0) return; | ||
| buf[len] = 0; | buf[len] = 0; | ||
| - | | + | |
| - | | + | lastPacketTime = millis(); |
| + | | ||
| + | catActive = true; | ||
| - | | + | |
| - | | + | if (mode == MANUAL && !manualForced) { |
| + | mode = AUTO; | ||
| + | | ||
| + | | ||
| + | // Восстанавливаем реле по текущему диапазону | ||
| + | if (currentBand >= 0) | ||
| + | setRelaysByBand(currentBand); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| - | | + | |
| - | if (b >= 0) currentBand | + | // Двигаем CAT-анимацию каждые 2 пакета |
| + | catPacketCounter++; | ||
| + | if (catPacketCounter | ||
| + | catPacketCounter | ||
| + | catAnimPos = (catAnimPos + 1) % 5; | ||
| + | updateTopLine(); | ||
| + | } | ||
| - | catActive = true; | ||
| - | lastCatTime = millis(); | ||
| - | // 🔁 возврат из auto-fallback | + | // Ищем < |
| - | if (mode == MANUAL && autoFallback) { | + | char* f = strstr(buf, "< |
| - | | + | if (f) { |
| - | | + | f += 6; |
| - | | + | |
| + | lastFreq | ||
| + | | ||
| + | |||
| + | | ||
| + | | ||
| + | currentBand = b; | ||
| + | |||
| + | // В AUTO обновляем реле | ||
| + | if (mode == AUTO && currentBand >= 0) | ||
| + | setRelaysByBand(currentBand); | ||
| } | } | ||
| - | | + | |
| - | | + | |
| } | } | ||
| - | /* ================= КНОПКА ================= */ | ||
| - | void handleButton() { | ||
| - | static unsigned long pressTime = 0; | ||
| - | static bool pressed = false; | ||
| - | if (!digitalRead(ENC_BTN)) { | + | void handleButtonPress() { |
| - | if (!pressed) { | + | pressed = true; |
| - | pressed = true; | + | pressStart = millis(); |
| - | | + | buttonHeld = false; |
| + | } | ||
| + | |||
| + | void handleButtonHold(unsigned long held) { | ||
| + | |||
| + | // длинное → вход в редактор | ||
| + | if (!ipEditMode && held >= LONG_PRESS) { | ||
| + | | ||
| + | | ||
| + | | ||
| + | return; | ||
| } | } | ||
| - | } else { | + | |
| - | if (pressed | + | // жест удержания |
| - | if (mode == AUTO) { | + | if (!ipEditMode |
| + | buttonHeld = true; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void handleIpEditorClick(unsigned long pressTime) { | ||
| + | static unsigned long lastEditorClick = 0; | ||
| + | |||
| + | // игнорируем первый клик после входа в редактор | ||
| + | if (ignoreNextEditorClick) { | ||
| + | ignoreNextEditorClick = false; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // длинное → выход без сохранения | ||
| + | if (pressTime >= LONG_PRESS) { | ||
| + | ipEditMode = false; | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | // защита от двойного клика | ||
| + | if (millis() - lastEditorClick < 200) return; | ||
| + | lastEditorClick = millis(); | ||
| + | |||
| + | // короткое → следующий октет | ||
| + | if (pressTime >= 50) { | ||
| + | | ||
| + | |||
| + | if (ipOctetIndex > 3) { | ||
| + | effectiveIP = IPAddress(ipEdit[0], | ||
| + | saveIPToEEPROM(effectiveIP); | ||
| + | |||
| + | lcd.clear(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(effectiveIP); | ||
| + | delay(1200); | ||
| + | |||
| + | ipEditMode = false; | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | drawIpEditor(); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | void toggleAutoManual() { | ||
| + | |||
| + | | ||
| mode = MANUAL; | mode = MANUAL; | ||
| manualForced = true; | manualForced = true; | ||
| - | | + | |
| - | | + | |
| - | } else { | + | 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); |
| + | else | ||
| + | | ||
| } | } | ||
| - | pressed | + | |
| - | } | + | forceDisplayUpdate |
| + | | ||
| } | } | ||
| - | /* ================= ЭНКОДЕР ================= */ | + | 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; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | |||
| + | // | ||
| + | // === ЭНКОДЕР С ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ === | ||
| + | // | ||
| void handleEncoder() { | void handleEncoder() { | ||
| - | static | + | static |
| int A = digitalRead(ENC_A); | int A = digitalRead(ENC_A); | ||
| + | if (A == lastA) return; | ||
| - | if (A != lastA && A == LOW && mode == MANUAL) { | + | |
| - | if (digitalRead(ENC_B)) | + | |
| - | manualBandIndex = (manualBandIndex + 1) % BAND_COUNT; | + | |
| - | else | + | |
| - | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | + | |
| - | | + | |
| + | |||
| + | // === УДЕРЖАНИЕ КНОПКИ + ПОВОРОТ === | ||
| + | if (buttonHeld && !ipEditMode) { | ||
| + | |||
| + | if (clockwise) { | ||
| + | runRelayTest(); | ||
| + | } 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; | ||
| + | |||
| + | setRelaysByBand(manualBandIndex); | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| } | } | ||
| + | |||
| lastA = A; | lastA = A; | ||
| } | } | ||
| - | /* ================= TIMEOUT ================= */ | + | |
| + | |||
| void checkCatTimeout() { | void checkCatTimeout() { | ||
| - | if (mode != AUTO) return; | ||
| + | // 1) Сбрасываем CAT независимо от режима | ||
| if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | ||
| - | | + | |
| - | mode = MANUAL; | + | |
| - | | + | // ОБНОВЛЯЕМ ПЕРВУЮ СТРОКУ |
| - | manualForced = false; | + | updateTopLine(); |
| - | | + | } |
| - | | + | |
| - | | + | // 2) Логика fallback только в AUTO |
| + | if (mode == AUTO && !catActive) { | ||
| + | | ||
| + | manualForced = false; | ||
| + | |||
| + | if (currentBand >= 0) { | ||
| + | manualBandIndex = currentBand; | ||
| + | } | ||
| + | setRelaysByBand(manualBandIndex); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| } | } | ||
| } | } | ||
| - | /* ================= SETUP ================= */ | ||
| - | 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; | + | |
| + | | ||
| pinMode(RELAYS[i], | pinMode(RELAYS[i], | ||
| + | digitalWrite(RELAYS[i], | ||
| + | } | ||
| - | | + | |
| + | // --- 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); | ||
| - | | + | |
| + | lcd.clear(); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.print(effectiveIP); | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | |||
| + | // --- Начальные значения логики --- | ||
| + | currentBand = -1; | ||
| + | catActive = false; | ||
| + | manualBandIndex = 0; | ||
| + | lastFreq = 0; | ||
| + | lastPacketTime = millis(); | ||
| + | |||
| + | disableAllRelays(); | ||
| + | |||
| + | // Обновляем дисплей по новой логике | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | |||
| + | // Инициализация энкодера (важно!) | ||
| + | lastA = digitalRead(ENC_A); | ||
| } | } | ||
| - | /* ================= LOOP ================= */ | ||
| - | void loop() { | + | void loop() { |
| + | // === Всегда читаем UDP === | ||
| readUDP(); | readUDP(); | ||
| + | |||
| + | // === Обработка энкодера и кнопки === | ||
| handleEncoder(); | handleEncoder(); | ||
| handleButton(); | handleButton(); | ||
| + | |||
| + | // === Проверка таймаута CAT === | ||
| checkCatTimeout(); | checkCatTimeout(); | ||
| + | |||
| + | // === Периодическое обновление дисплея === | ||
| + | static unsigned long lastDisplayUpdate = 0; | ||
| + | if (!ipEditMode && millis() - lastDisplayUpdate > 1000) { | ||
| + | lastDisplayUpdate = millis(); | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| + | delay(10); | ||
| } | } | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | </ | ||
banddecoder_code.1768051628.txt.gz · Последнее изменение: — eu8t
