banddecoder_code
Различия
Показаны различия между двумя версиями страницы.
| Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия | ||
| banddecoder_code [2026/01/13 12:53] – eu8t | banddecoder_code [2026/01/23 08:04] (текущий) – eu8t | ||
|---|---|---|---|
| Строка 1: | Строка 1: | ||
| - | IP 192.168.0.240 | + | IP 192.168.0.240 |
| + | |||
| + | v0.81 (примерно) | ||
| + | |||
| + | {{: | ||
| + | |||
| + | {{: | ||
| - | v0.44 | ||
| - | {{: | ||
| - | NEW CODE | ||
| < | < | ||
| #include < | #include < | ||
| Строка 12: | Строка 15: | ||
| #include < | #include < | ||
| #include < | #include < | ||
| + | #include < | ||
| - | /* ================= | + | // ===================================================== |
| - | const float VERSION | + | // === CONFIG: все константы устройства в одном |
| - | LiquidCrystal_I2C lcd(0x27, 16, 2); | + | // ===================================================== |
| - | 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; | ||
| - | const unsigned long CAT_TIMEOUT = 7000; // переключение в MANUAL | + | // --- CAT / сеть --- |
| - | const unsigned long NET_TIMEOUT = 5000; // только надпись WAIT CAT DATA | + | const unsigned long CAT_TIMEOUT = 7000; |
| + | const unsigned long NET_TIMEOUT = 5000; | ||
| - | const int ENC_A | + | // --- Пины энкодера --- |
| - | const int ENC_B | + | const int ENC_A |
| - | const int ENC_BTN = 2; | + | const int ENC_B |
| + | const int ENC_BTN = 3; | ||
| - | // Используем 4 реле | + | // --- Пины |
| - | const int RELAYS[] = {4, 5, 6, 7}; // Реле A,B,C,D | + | const int RELAYS[] = {4, 5, 6, 7}; |
| - | const int RELAY_COUNT = 4; // Теперь точно 4 реле | + | const int RELAY_COUNT = 4; |
| - | /* ================= СОСТОЯНИЯ ================= */ | + | // --- Кнопка --- |
| - | enum Mode { AUTO, MANUAL }; | + | const unsigned long SHORT_PRESS |
| - | Mode mode = AUTO; | + | 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; | ||
| long from; | long from; | ||
| long to; | long to; | ||
| - | byte abcdCode; | + | byte abcdCode; |
| }; | }; | ||
| - | // Коды ABCD согласно вашей схеме: | ||
| - | // 4 реле: A(бит0), B(бит1), C(бит2), D(бит3) | ||
| - | // 160M = 0001 (A=1, B=0, C=0, D=0) | ||
| - | // 80M = 0010 (A=0, B=1, C=0, D=0) | ||
| - | // 40M = 0011 (A=1, B=1, C=0, D=0) | ||
| - | // 20M = 0101 (A=1, B=0, C=1, D=0) | ||
| - | // 15M = 0111 (A=1, B=1, C=1, D=0) | ||
| - | // 10M = 1001 (A=1, B=0, C=0, D=1) | ||
| 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) { | ||
| for (int i = 0; i < BAND_COUNT; i++) { | for (int i = 0; i < BAND_COUNT; i++) { | ||
| Строка 86: | Строка 144: | ||
| } | } | ||
| - | // Устанавливаем реле согласно коду ABCD | ||
| void setRelaysByABCD(byte abcdCode) { | void setRelaysByABCD(byte abcdCode) { | ||
| - | Serial.print(" | ||
| - | for (int i = 3; i >= 0; i--) { | ||
| - | Serial.print((abcdCode >> i) & 1); | ||
| - | } | ||
| - | Serial.print(" | ||
| - | if (abcdCode < 0x10) Serial.print(" | ||
| - | Serial.print(abcdCode, | ||
| - | Serial.println(" | ||
| - | | ||
| for (int i = 0; i < RELAY_COUNT; | for (int i = 0; i < RELAY_COUNT; | ||
| - | // Проверяем соответствующий бит в коде ABCD | ||
| - | // i=0 -> бит0 (A), i=1 -> бит1 (B), i=2 -> бит2 (C), i=3 -> бит3 (D) | ||
| bool state = (abcdCode >> i) & 1; | bool state = (abcdCode >> i) & 1; | ||
| - | digitalWrite(RELAYS[i], | + | digitalWrite(RELAYS[i], |
| - | + | ||
| - | Serial.print(" | + | |
| - | Serial.print((char)(' | + | |
| - | Serial.print(" | + | |
| - | Serial.print(RELAYS[i]); | + | |
| - | Serial.print(" | + | |
| - | Serial.println(state ? "HIGH" : " | + | |
| } | } | ||
| - | Serial.println(); | ||
| } | } | ||
| - | // Установить реле для конкретного диапазона | ||
| void setRelaysByBand(int bandIndex) { | void setRelaysByBand(int bandIndex) { | ||
| - | if (bandIndex >= 0 && bandIndex < BAND_COUNT) | + | if (bandIndex >= 0 && bandIndex < BAND_COUNT) |
| - | Serial.print(" | + | |
| - | Serial.print(bands[bandIndex].name); | + | |
| - | Serial.print(" | + | |
| - | Serial.print(bandIndex); | + | |
| - | Serial.println(" | + | |
| setRelaysByABCD(bands[bandIndex].abcdCode); | setRelaysByABCD(bands[bandIndex].abcdCode); | ||
| - | | + | else |
| - | // Неизвестный диапазон - выключаем все реле | + | setRelaysByABCD(0); |
| - | Serial.println(" | + | |
| - | setRelaysByABCD(0b00000000); | + | |
| - | } | + | |
| } | } | ||
| - | // Выключить все реле | ||
| void disableAllRelays() { | 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) { | ||
| Строка 151: | Строка 231: | ||
| } | } | ||
| - | /* ================= ЭКРАН ================= */ | ||
| - | Mode lastMode = AUTO; | ||
| - | int lastBandIndex = -1; | ||
| - | long lastDisplayedFreq = 0; | ||
| - | bool forceDisplayUpdate = false; | ||
| - | void updateLCDIfNeeded() { | + | void runRelayTest() { |
| - | | + | |
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| - | | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | + | | |
| - | if (mode != AUTO && lastBandIndex != manualBandIndex) { | + | |
| - | | + | |
| - | | + | lcd.print((bands[i].abcdCode >> b) & 1); |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | if (mode == AUTO && lastBandIndex != currentBand) { | + | |
| - | | + | |
| - | Serial.print("Auto band changed to: "); | + | |
| - | Serial.println(currentBand); | + | |
| - | } | + | |
| - | + | ||
| - | if (mode == AUTO && lastDisplayedFreq != lastFreq) { | + | |
| - | changed = true; | + | |
| - | } | + | |
| - | if (forceDisplayUpdate) { | + | delay(600); |
| - | changed = true; | + | |
| - | forceDisplayUpdate = false; | + | |
| } | } | ||
| - | | + | |
| + | delay(300); | ||
| - | | + | |
| - | | + | |
| - | | + | } |
| + | 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.setCursor(0, | ||
| lcd.print(" | lcd.print(" | ||
| Строка 199: | Строка 277: | ||
| if (mode == AUTO) { | if (mode == AUTO) { | ||
| lcd.print(" | lcd.print(" | ||
| - | if (currentBand >= 0) { | + | if (currentBand >= 0) lcd.print(bands[currentBand].name); |
| - | | + | else lcd.print(" |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | | + | |
| lcd.print(" | lcd.print(" | ||
| - | if (catActive) | + | if (catActive) printFreqCompact(lastFreq); |
| - | | + | else lcd.print(" |
| - | | + | |
| - | | + | |
| - | } | + | |
| lastBandIndex = currentBand; | lastBandIndex = currentBand; | ||
| Строка 217: | Строка 289: | ||
| lcd.print(" | lcd.print(" | ||
| lcd.print(bands[manualBandIndex].name); | lcd.print(bands[manualBandIndex].name); | ||
| - | lcd.print(" | + | lcd.print(" |
| - | for (int i = 3; i >= 0; i--) { | + | for (int i = 3; i >= 0; i--) |
| lcd.print((bands[manualBandIndex].abcdCode >> i) & 1); | lcd.print((bands[manualBandIndex].abcdCode >> i) & 1); | ||
| - | } | + | |
| lastBandIndex = manualBandIndex; | lastBandIndex = manualBandIndex; | ||
| } | } | ||
| Строка 227: | Строка 299: | ||
| } | } | ||
| - | /* ================= | + | 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.print(VERSION, | lcd.print(VERSION, | ||
| lcd.setCursor(0, | lcd.setCursor(0, | ||
| lcd.print(" | lcd.print(" | ||
| - | delay(1000); | ||
| - | | ||
| - | lcd.clear(); | ||
| - | lcd.setCursor(0, | ||
| - | lcd.print(" | ||
| - | lcd.setCursor(0, | ||
| - | for (int i = 0; i < min(6, BAND_COUNT); | ||
| - | lcd.print((char)(' | ||
| - | } | ||
| delay(2000); | delay(2000); | ||
| } | } | ||
| - | /* ================= UDP ================= */ | + | |
| void readUDP() { | void readUDP() { | ||
| int packetSize = Udp.parsePacket(); | int packetSize = Udp.parsePacket(); | ||
| 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(); | lastPacketTime = millis(); | ||
| + | lastCatTime = millis(); | ||
| + | catActive = true; | ||
| - | | + | |
| - | if (!f) return; | + | if (mode == MANUAL && !manualForced) { |
| + | mode = AUTO; | ||
| + | | ||
| + | | ||
| + | // Восстанавливаем реле по текущему диапазону | ||
| + | | ||
| + | setRelaysByBand(currentBand); | ||
| + | |||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | } | ||
| - | f += 6; | ||
| - | lastFreq = atol(f) * 10; | ||
| | | ||
| - | | + | |
| - | | + | catPacketCounter++; |
| - | | + | if (catPacketCounter >= 2) { // ← каждые 2 пакета |
| + | catPacketCounter = 0; | ||
| + | | ||
| + | | ||
| + | } | ||
| - | int b = detectBand(lastFreq); | ||
| - | Serial.print(" | ||
| - | Serial.print(b); | ||
| - | Serial.println(" | ||
| - | | + | |
| + | char* f = strstr(buf, "< | ||
| + | if (f) { | ||
| + | f += 6; | ||
| - | catActive | + | lastFreq |
| - | | + | |
| - | if (mode == MANUAL && autoFallback) { | + | int b = detectBand(lastFreq); |
| - | Serial.println(" | + | |
| - | | + | |
| - | | + | |
| - | manualForced = false; | + | |
| - | setRelaysByBand(currentBand); | + | |
| - | } | + | |
| - | | + | // В AUTO обновляем реле |
| - | setRelaysByBand(currentBand); | + | |
| + | setRelaysByBand(currentBand); | ||
| } | } | ||
| + | // Если < | ||
| updateLCDIfNeeded(); | updateLCDIfNeeded(); | ||
| } | } | ||
| - | /* ================= КНОПКА ================= */ | ||
| - | void handleButton() { | ||
| - | static bool pressed = false; | ||
| - | static unsigned long lastPress = 0; | ||
| - | const unsigned long DEBOUNCE_DELAY = 50; | ||
| - | if (!digitalRead(ENC_BTN)) { | + | |
| - | | + | void handleButtonPress() { |
| - | | + | pressed = true; |
| - | | + | |
| - | + | | |
| - | Serial.println(" | + | |
| - | + | ||
| - | if (mode == AUTO) { | + | |
| - | mode = MANUAL; | + | |
| - | manualForced = true; | + | |
| - | autoFallback = false; | + | |
| - | Serial.println(" | + | |
| - | disableAllRelays(); | + | |
| - | } else { | + | |
| - | mode = AUTO; | + | |
| - | manualForced = false; | + | |
| - | autoFallback = false; | + | |
| - | Serial.println(" | + | |
| - | setRelaysByBand(currentBand); | + | |
| - | } | + | |
| - | forceDisplayUpdate = true; | + | |
| - | updateLCDIfNeeded(); | + | |
| - | | + | |
| - | } else { | + | |
| - | pressed | + | |
| - | } | + | |
| } | } | ||
| - | /* ================= ЭНКОДЕР ================= */ | + | void handleButtonHold(unsigned long held) { |
| - | void handleEncoder() { | + | |
| - | static int lastA = HIGH; | + | |
| - | static unsigned long lastTurn = 0; | + | |
| - | const unsigned long TURN_DEBOUNCE = 100; | + | |
| - | if (mode == AUTO) return; | + | |
| + | if (!ipEditMode && held >= LONG_PRESS) { | ||
| + | enterIpEditMode(); | ||
| + | pressed = false; | ||
| + | buttonHeld = false; | ||
| + | return; | ||
| + | } | ||
| - | int A = digitalRead(ENC_A); | + | // жест удержания |
| - | + | if (!ipEditMode | |
| - | | + | |
| - | lastTurn | + | |
| - | + | ||
| - | bool clockwise = digitalRead(ENC_B); | + | |
| - | + | ||
| - | if (clockwise) { | + | |
| - | | + | |
| - | Serial.print(" | + | |
| - | } else { | + | |
| - | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | + | |
| - | Serial.print(" | + | |
| } | } | ||
| - | Serial.print(manualBandIndex); | ||
| - | Serial.print(" | ||
| - | Serial.print(bands[manualBandIndex].name); | ||
| - | Serial.println(" | ||
| - | | ||
| - | // Устанавливаем реле согласно выбранному диапазону в ручном режиме | ||
| - | setRelaysByBand(manualBandIndex); | ||
| - | forceDisplayUpdate = true; | ||
| - | updateLCDIfNeeded(); | ||
| - | } | ||
| - | lastA = A; | ||
| } | } | ||
| - | /* ================= TIMEOUT ================= */ | + | void handleIpEditorClick(unsigned long pressTime) { |
| - | void checkCatTimeout() { | + | |
| - | if (mode != AUTO) return; | + | |
| - | if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | + | |
| - | catActive = false; | + | |
| - | mode = MANUAL; | + | ignoreNextEditorClick |
| - | autoFallback = true; | + | |
| - | manualForced = false; | + | } |
| - | manualBandIndex = (currentBand >= 0) ? currentBand : 0; | + | |
| - | + | ||
| - | Serial.println(" | + | |
| - | + | ||
| - | disableAllRelays(); | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | } | + | |
| - | // Тестовая функция | + | |
| - | void testAllBands() { | + | |
| - | | + | |
| - | for (int i = 0; i < BAND_COUNT; i++) { | + | |
| - | | + | |
| - | | + | |
| - | Serial.print(": | + | |
| - | for (int j = 3; j >= 0; j--) { | + | |
| - | Serial.print((bands[i].abcdCode >> j) & 1); | + | |
| } | } | ||
| - | Serial.println(); | ||
| - | | ||
| - | setRelaysByBand(i); | ||
| - | delay(1000); | ||
| - | } | ||
| - | | ||
| - | Serial.println(" | ||
| - | disableAllRelays(); | ||
| - | } | ||
| - | /* ================= SETUP ================= */ | + | |
| - | void setup() { | + | |
| - | Serial.begin(115200); | + | |
| - | | + | |
| - | Serial.print(" | + | |
| - | Serial.println(VERSION, 2); | + | |
| - | pinMode(ENC_A, | + | // короткое → следующий октет |
| - | | + | |
| - | | + | |
| - | // Инициализируем 4 реле | + | if (ipOctetIndex > 3) { |
| - | for (int i = 0; i < RELAY_COUNT; | + | |
| - | | + | |
| - | digitalWrite(RELAYS[i], LOW); // Гарантируем выключенное состояние | + | |
| - | } | + | |
| - | disableAllRelays(); // Выключаем все реле при старте | + | lcd.clear(); |
| + | lcd.setCursor(0, | ||
| + | lcd.print(" | ||
| + | lcd.setCursor(0, | ||
| + | lcd.print(effectiveIP); | ||
| + | delay(1200); | ||
| - | lcd.begin(); | + | ipEditMode = false; |
| - | | + | |
| - | | + | |
| + | return; | ||
| + | } | ||
| - | Ethernet.begin(mac, ip); | + | drawIpEditor(); |
| - | Serial.print(" | + | |
| - | Serial.println(Ethernet.localIP()); | + | |
| - | + | ||
| - | Udp.begin(localPort); | + | |
| - | Serial.print(" | + | |
| - | Serial.println(localPort); | + | |
| - | + | ||
| - | // Выводим таблицу кодов ABCD при старте | + | |
| - | Serial.println(" | + | |
| - | Serial.println(" | + | |
| - | Serial.println(" | + | |
| - | for (int i = 0; i < BAND_COUNT; i++) { | + | |
| - | Serial.print(bands[i].name); | + | |
| - | Serial.print(" | + | |
| - | Serial.print(bands[i].from); | + | |
| - | Serial.print(" | + | |
| - | Serial.print(bands[i].to); | + | |
| - | Serial.print(" | + | |
| - | + | ||
| - | // Показываем ABCD код | + | |
| - | Serial.print(" | + | |
| - | for (int j = 3; j >= 0; j--) { | + | |
| - | Serial.print((bands[i].abcdCode >> j) & 1); | + | |
| } | } | ||
| - | | ||
| - | Serial.print(" | ||
| - | for (int j = 3; j >= 0; j--) { | ||
| - | Serial.print((bands[i].abcdCode >> j) & 1); | ||
| - | } | ||
| - | Serial.print(" | ||
| - | | ||
| - | Serial.print(" | ||
| - | if (bands[i].abcdCode < 0x10) Serial.print(" | ||
| - | Serial.println(bands[i].abcdCode, | ||
| - | } | ||
| - | | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | for (int i = 0; i < RELAY_COUNT; | ||
| - | Serial.print(" | ||
| - | Serial.print((char)(' | ||
| - | Serial.print(": | ||
| - | Serial.print(RELAYS[i]); | ||
| - | Serial.print(" | ||
| - | Serial.print(i); | ||
| - | Serial.print(" | ||
| - | Serial.print(" | ||
| - | Serial.print(i); | ||
| - | Serial.println(" | ||
| - | } | ||
| - | |||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.print(CAT_TIMEOUT); | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.print(NET_TIMEOUT); | ||
| - | Serial.println(" | ||
| - | |||
| - | // Запускаем тест всех диапазонов | ||
| - | Serial.println(" | ||
| - | testAllBands(); | ||
| - | |||
| - | lcd.clear(); | ||
| - | lcd.setCursor(0, | ||
| - | lcd.print(" | ||
| - | lcd.print(ip); | ||
| - | lcd.setCursor(0, | ||
| - | lcd.print(" | ||
| - | |||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| - | Serial.println(" | ||
| } | } | ||
| - | /* ================= LOOP ================= */ | + | void toggleAutoManual() { |
| - | void loop() { | + | |
| - | readUDP(); | + | |
| - | handleEncoder(); | + | |
| - | handleButton(); | + | |
| - | checkCatTimeout(); | + | |
| - | // Периодическое обновление дисплея | + | |
| - | static unsigned long lastDisplayUpdate = 0; | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | // Отображение статуса сети | + | manualBandIndex |
| - | if (millis() - lastPacketTime > NET_TIMEOUT && mode == AUTO && !catActive) { | + | |
| - | static unsigned long lastStatusUpdate = 0; | + | |
| - | if (millis() - lastStatusUpdate | + | |
| - | lastStatusUpdate | + | |
| - | lcd.setCursor(0,0); | + | |
| - | lcd.print(" | + | |
| - | lcd.print(ip); | + | |
| - | lcd.setCursor(0,1); | + | |
| - | | + | |
| } | } | ||
| - | } | + | else { |
| - | + | if (!catActive) { | |
| - | // Небольшая задержка для стабильности | + | |
| - | delay(10); | + | |
| - | } | + | |
| - | </ | + | |
| + | lcd.print(" | ||
| + | delay(1200); | ||
| + | forceDisplayUpdate = true; | ||
| + | updateLCDIfNeeded(); | ||
| + | return; | ||
| + | } | ||
| + | mode = AUTO; | ||
| + | manualForced = false; | ||
| + | | ||
| + | if (currentBand >= 0) | ||
| + | setRelaysByBand(currentBand); | ||
| + | else | ||
| + | disableAllRelays(); | ||
| + | } | ||
| - | OLD CODE | + | forceDisplayUpdate = true; |
| - | < | + | |
| - | #include < | + | } |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | /* ================= НАСТРОЙКИ ================= */ | + | void handleButtonRelease(unsigned long pressTime) { |
| - | const float VERSION = 0.44; | + | // редактор |
| - | LiquidCrystal_I2C lcd(0x27, 16, 2); | + | |
| + | handleIpEditorClick(pressTime); | ||
| + | return; | ||
| + | } | ||
| - | byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; | + | // длинное → вход в редактор |
| - | IPAddress ip(192, 168, 0, 240); | + | if (pressTime >= LONG_PRESS) |
| - | unsigned int localPort = 12060; | + | |
| + | | ||
| + | } | ||
| - | EthernetUDP Udp; | + | // короткое → AUTO/ |
| + | if (!buttonHeld && pressTime >= SHORT_PRESS) { | ||
| + | toggleAutoManual(); | ||
| + | } | ||
| - | const unsigned long CAT_TIMEOUT | + | buttonHeld |
| - | const unsigned long NET_TIMEOUT = 5000; // только надпись WAIT CAT DATA | + | } |
| - | const int ENC_A = 10; | ||
| - | const int ENC_B = 3; | ||
| - | const int ENC_BTN = 2; | ||
| - | const int RELAYS[] = {4, 5, 6, 7, 8, 9}; | + | // |
| - | const int RELAY_COUNT | + | // === КНОПКА С АНТИДРЕБЕЗГОМ И ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ === |
| + | // | ||
| + | void handleButton() { | ||
| + | static unsigned long lastCheck = 0; | ||
| - | /* ================= СОСТОЯНИЯ ================= */ | + | if (millis() - lastCheck < 30) return; |
| + | lastCheck | ||
| - | enum Mode { AUTO, MANUAL }; | + | bool btn = !digitalRead(ENC_BTN); |
| - | Mode mode = AUTO; | + | |
| - | bool manualForced = false; | + | // начало нажатия |
| - | bool autoFallback = false; | + | if (btn && !pressed) { |
| + | handleButtonPress(); | ||
| + | | ||
| + | } | ||
| - | unsigned long lastCatTime | + | // удержание |
| - | bool catActive = false; | + | if (btn && pressed) { |
| + | | ||
| + | | ||
| + | return; | ||
| + | } | ||
| - | unsigned long lastPacketTime | + | // отпускание |
| + | if (!btn && pressed) { | ||
| + | pressed = false; | ||
| + | | ||
| + | handleButtonRelease(pressTime); | ||
| + | return; | ||
| + | } | ||
| + | } | ||
| - | long lastFreq = 0; | ||
| - | int currentBand = -1; | ||
| - | int manualBandIndex = 0; | ||
| - | /* ================= ДИАПАЗОНЫ ================= */ | ||
| - | struct Band { | + | // |
| - | const char* name; | + | // === ЭНКОДЕР С ЗАЩИТОЙ ОТ ЛОЖНЫХ СРАБАТЫВАНИЙ === |
| - | long from; | + | // |
| - | long to; | + | void handleEncoder() { |
| - | }; | + | |
| - | Band bands[] | + | int A = digitalRead(ENC_A); |
| - | | + | |
| - | {" | + | |
| - | {" | + | |
| - | {" | + | |
| - | {" | + | |
| - | {" | + | |
| - | }; | + | |
| - | const int BAND_COUNT | + | // реагируем только на фронт A == LOW |
| + | if (A != lastA && A == LOW) { | ||
| - | /* ================= СЛУЖЕБНЫЕ ================= */ | + | bool clockwise |
| - | int detectBand(long freq) { | + | // === УДЕРЖАНИЕ КНОПКИ |
| - | for (int i = 0; i < BAND_COUNT; i++) { | + | if (buttonHeld |
| - | if (freq >= bands[i].from | + | |
| - | return i; | + | |
| - | } | + | |
| - | return -1; | + | |
| - | } | + | |
| - | void setRelays(int bandIndex) { | + | if (clockwise) { |
| - | | + | |
| - | | + | } else { |
| + | // резерв | ||
| + | } | ||
| - | if (bandIndex < 0) | + | buttonHeld = false; |
| - | for (int i = 0; i < RELAY_COUNT; | + | |
| - | | + | |
| - | } | + | } |
| - | void printFreqCompact(long f) { | + | |
| - | int MHz = f / 1000000; | + | |
| - | int kHz = (f / 1000) % 1000; | + | |
| - | int hundred | + | |
| - | int tens = (f / 10) % 10; | + | |
| - | lcd.print(MHz); | + | |
| - | lcd.print(" | + | lastA = A; |
| - | | + | |
| - | if (kHz < 10) | + | } |
| - | | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | /* ================= ЭКРАН ================= */ | + | int val = ipEdit[ipOctetIndex]; |
| + | if (clockwise) val++; | ||
| + | else val--; | ||
| - | Mode lastMode | + | if (val < 0) val = 255; |
| - | int lastBandIndex = -1; | + | if (val > 255) val = 0; |
| - | long lastDisplayedFreq | + | |
| - | void updateLCDIfNeeded() { | + | ipEdit[ipOctetIndex] = val; |
| - | bool changed = false; | + | drawIpEditor(); |
| - | if (lastMode != mode) changed = true; | + | lastA = A; |
| - | if (mode != AUTO && lastBandIndex != manualBandIndex) changed = true; | + | |
| - | if (mode == AUTO && lastBandIndex != currentBand) changed = true; | + | } |
| - | if (mode == AUTO && lastDisplayedFreq != lastFreq) changed = true; | + | |
| - | | + | // === MANUAL режим === |
| + | | ||
| - | lcd.setCursor(0,0); | + | if (millis() - lastMove < 50) { |
| - | | + | lastA = A; |
| - | | + | |
| + | } | ||
| + | lastMove = millis(); | ||
| - | lcd.setCursor(0,1); | + | if (clockwise) |
| - | | + | |
| - | | + | |
| + | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | ||
| - | if (mode == AUTO) { | + | setRelaysByBand(manualBandIndex); |
| - | lcd.print(" | + | |
| - | if (currentBand >= 0) lcd.print(bands[currentBand].name); | + | |
| - | else lcd.print(" | + | } |
| - | | + | |
| - | if (catActive) printFreqCompact(lastFreq); | + | |
| - | else lcd.print(" | + | |
| - | + | ||
| - | lastBandIndex = currentBand; | + | |
| - | lastDisplayedFreq = lastFreq; | + | |
| - | | + | |
| - | lcd.print(" | + | |
| - | lcd.print(bands[manualBandIndex].name); | + | |
| - | lastBandIndex = manualBandIndex; | + | |
| } | } | ||
| - | | + | |
| } | } | ||
| - | /* ================= СТАРТОВЫЙ ЭКРАН ================= */ | ||
| - | void splashScreen() { | ||
| - | lcd.clear(); | ||
| - | lcd.setCursor(0, | ||
| - | lcd.print(" | ||
| - | lcd.print(VERSION, | ||
| - | lcd.setCursor(0, | ||
| - | lcd.print(" | ||
| - | delay(2000); | ||
| - | } | ||
| - | /* ================= UDP ================= */ | ||
| - | void readUDP() { | ||
| - | int packetSize = Udp.parsePacket(); | ||
| - | if (!packetSize) return; | ||
| - | char buf[400]; | + | void checkCatTimeout() { |
| - | int len = Udp.read(buf, sizeof(buf) - 1); | + | |
| - | if (len <= 0) return; | + | |
| - | buf[len] = 0; | + | |
| - | | + | |
| + | if (catActive && | ||
| + | catActive = false; | ||
| - | char* f = strstr(buf, "< | + | // ОБНОВЛЯЕМ ПЕРВУЮ СТРОКУ |
| - | if (!f) return; | + | |
| - | + | ||
| - | f += 6; | + | |
| - | lastFreq = atol(f) * 10; | + | |
| - | + | ||
| - | int b = detectBand(lastFreq); | + | |
| - | if (b >= 0) currentBand = b; | + | |
| - | + | ||
| - | catActive = true; | + | |
| - | lastCatTime = millis(); | + | |
| - | + | ||
| - | if (mode == MANUAL && autoFallback) { | + | |
| - | mode = AUTO; | + | |
| - | autoFallback = false; | + | |
| - | manualForced = false; | + | |
| - | | + | |
| } | } | ||
| - | if (mode == AUTO) setRelays(currentBand); | + | |
| + | | ||
| + | mode = MANUAL; | ||
| + | manualForced = false; | ||
| - | updateLCDIfNeeded(); | + | if (currentBand >= 0) { |
| - | } | + | manualBandIndex = currentBand; |
| + | } | ||
| + | setRelaysByBand(manualBandIndex); | ||
| - | /* ================= КНОПКА ================= */ | + | forceDisplayUpdate |
| - | + | ||
| - | void handleButton() { | + | |
| - | static bool pressed = false; | + | |
| - | + | ||
| - | if (!digitalRead(ENC_BTN)) { | + | |
| - | pressed | + | |
| - | } else { | + | |
| - | if (pressed) { | + | |
| - | if (mode == AUTO) { | + | |
| - | mode = MANUAL; | + | |
| - | manualForced = true; | + | |
| - | autoFallback = false; | + | |
| - | setRelays(-1); | + | |
| - | } else { | + | |
| - | mode = AUTO; | + | |
| - | manualForced = false; | + | |
| - | autoFallback = false; | + | |
| - | setRelays(currentBand); | + | |
| - | } | + | |
| updateLCDIfNeeded(); | updateLCDIfNeeded(); | ||
| - | } | ||
| - | pressed = false; | ||
| } | } | ||
| } | } | ||
| - | /* ================= ЭНКОДЕР ================= */ | ||
| - | void handleEncoder() { | ||
| - | static int lastA = HIGH; | ||
| - | int A = digitalRead(ENC_A); | ||
| - | |||
| - | if (A != lastA && A == LOW && mode != AUTO) { | ||
| - | if (digitalRead(ENC_B)) | ||
| - | manualBandIndex = (manualBandIndex + 1) % BAND_COUNT; | ||
| - | else | ||
| - | manualBandIndex = (manualBandIndex - 1 + BAND_COUNT) % BAND_COUNT; | ||
| - | |||
| - | updateLCDIfNeeded(); | ||
| - | } | ||
| - | lastA = A; | ||
| - | } | ||
| - | |||
| - | /* ================= TIMEOUT ================= */ | ||
| - | |||
| - | void checkCatTimeout() { | ||
| - | if (mode != AUTO) return; | ||
| - | |||
| - | if (catActive && millis() - lastCatTime > CAT_TIMEOUT) { | ||
| - | catActive = false; | ||
| - | mode = MANUAL; | ||
| - | autoFallback = true; | ||
| - | manualForced = false; | ||
| - | manualBandIndex = (currentBand >= 0) ? currentBand : 0; | ||
| - | setRelays(-1); | ||
| - | updateLCDIfNeeded(); | ||
| - | } | ||
| - | } | ||
| - | /* ================= SETUP ================= */ | ||
| - | void setup() { | + | void setup() { |
| - | Serial.begin(115200); | + | |
| - | | + | |
| + | // --- Пины энкодера --- | ||
| 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.begin(); | + | |
| + | | ||
| lcd.backlight(); | lcd.backlight(); | ||
| - | splashScreen(); | ||
| - | | + | |
| - | | + | |
| - | | + | |
| + | // Регистрируем кастомный символ CAT‑точки | ||
| + | lcd.createChar(0, | ||
| + | |||
| + | // Splash screen | ||
| + | splashScreen(); | ||
| + | |||
| + | // --- Ethernet --- | ||
| + | Ethernet.begin(mac, | ||
| Udp.begin(localPort); | Udp.begin(localPort); | ||
| - | Serial.println(" | ||
| + | // Первичное отображение 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; | ||
| + | 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(); | ||
| - | checkCatTimeout(); | ||
| - | // только надпись при NET_TIMEOUT | + | // === Проверка |
| - | if (millis() - lastPacketTime | + | checkCatTimeout(); |
| - | | + | |
| - | | + | // === Периодическое обновление |
| - | | + | static unsigned long lastDisplayUpdate = 0; |
| - | | + | if (!ipEditMode && |
| - | lcd.print(" | + | |
| - | } | + | |
| + | | ||
| + | delay(10); | ||
| } | } | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| </ | </ | ||
banddecoder_code.1768297988.txt.gz · Последнее изменение: — eu8t
