#pragma once #include #include class CommonCLI; // Локальный мастер настройки репитера. Работает на любой плате, у которой есть // дисплей (DISPLAY_CLASS) и кнопка (PIN_USER_BTN); на остальных целях код // полностью исключается препроцессором. Все изменения применяются через тот же // CommonCLI, что и последовательный порт, поэтому проверка значений и запись в // флеш выполняются одним и тем же кодом. class UITask { public: enum Screen : uint8_t { SCREEN_STATUS = 0, SCREEN_MENU, SCREEN_VALUE, // числа и переключатели: SF, CR, TX, repeat, path hash, advert, flood SCREEN_DIGITS, // поразрядный ввод значения с точкой (полоса, ADC) SCREEN_FREQ, // частота: МГц шагом 1, кГц по разрядам SCREEN_BATTERY, // заряд аккумулятора: напряжение, процент, множитель ADC SCREEN_COORDS, SCREEN_COORDS_SAVE }; private: mesh::MainBoard* _board; DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; NodePrefs* _node_prefs; CommonCLI* _cli = nullptr; char _version_info[32]; unsigned long _powering_off_at = 0; unsigned long _started_at = 0; // состояние мастера настройки uint8_t _screen = SCREEN_STATUS; uint8_t _menu_index = 0; uint8_t _menu_top = 0; int _value = 0; uint8_t _coord_digit = 0; // 0..5 широта, 6..11 долгота bool _coord_editing = false; // true: цифра правится, false: применена bool _coord_applied[12]; char _coord_digits[12]; char _toast[64]; bool _popup_active = false; // всплывающее окно с ответом устройства открыто // радиопараметры: рабочая копия, пока мастер открыт bool _radio_loaded = false; bool _radio_dirty = false; // правили ли частоту/полосу/SF/CR в этой сессии double _radio_freq = 0; double _radio_bw = 0; int _radio_sf = 0; int _radio_cr = 0; // поразрядный редактор значения с десятичной точкой (полоса) uint8_t _digit_count = 0; uint8_t _digit_int = 0; // цифр до точки uint8_t _digit_index = 0; bool _digit_editing = false; bool _digit_applied[8]; char _digit_digits[8]; // редактор частоты: целые МГц меняются шагом 1, кГц — по разрядам int _freq_mhz = 0; char _freq_khz[3] = { '0', '0', '0' }; uint8_t _freq_index = 0; // 0 = МГц, 1..3 = разряды кГц bool _freq_editing = false; bool _freq_applied[4]; bool wizardActive() const { return _screen != SCREEN_STATUS; } void openMenu(); void openField(uint8_t index); void applyField(); void loadRadio(); void applyRadio(); void beginBwEdit(); void beginAdcEdit(); void beginFreqEdit(); void applyDigits(); double digitValue() const; double freqValue() const; int batteryPercent() const; void coordFromPrefs(); void coordSave(); double coordAxisValue(uint8_t axis) const; // axis 0 = lat, 1 = lon void showPopup(const char* msg); bool runCommand(const char* command); void handleButton(int ev); void renderCurrScreen(); void renderStatus(); void renderMenu(); void renderValue(); void renderDigits(); void renderFreq(); void renderBattery(); void renderCoords(); void renderCoordsSave(); void renderTitleBar(const char* title); void renderHint(const char* hint); void renderPopup(); int rowHeight() const; public: UITask(mesh::MainBoard& board, DisplayDriver& display) : _board(&board), _display(&display) { _next_read = _next_refresh = 0; } void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version, CommonCLI* cli = nullptr); void loop(); };