feat: separate radio settings in the on-device setup wizard
- радиопресет заменён на четыре отдельных пункта меню: Frequency, Bandwidth, CR, SF - частота: целые МГц шагом +-1, кГц правятся по разрядам (принцип как у координат) - полоса: поразрядный ввод значения с точкой (формат 062.50) - CR и SF: шаг +-1 с ограничением 5-8 и 5-12 - применение любого из четырёх пунктов отправляет set radio <частота>,<полоса>,<sf>,<cr> целиком, остальные значения сохраняются; внизу меню остаётся Save & exit - документация device-setup.md обновлена, README: раздел и журнал изменений - проверено сборкой: heltec_v4_repeater_mqtt, Meshadventurer_sx1262_repeater_mqtt, Generic_E22_sx1262_repeater_mqtt (без дисплея)
This commit is contained in:
@@ -304,9 +304,9 @@ get bridge.enabled
|
||||
|
||||
### Настройка с дисплея и кнопки
|
||||
|
||||
На платах с дисплеем и кнопкой мастер настройки доступен прямо на устройстве — без компьютера и телефона: двойное нажатие кнопки из экрана состояния открывает меню. Меняются координаты, радиопресет сети MeshCoreTel, мощность передатчика, пересылка пакетов, размер хэша пути, интервалы advert и flood advert, лимит flood. Значения применяются через тот же обработчик, что и последовательный CLI, поэтому проверка и сохранение одинаковы для обоих путей, а ответ устройства показывается на экране.
|
||||
На платах с дисплеем и кнопкой мастер настройки доступен прямо на устройстве — без компьютера и телефона: двойное нажатие кнопки из экрана состояния открывает меню. Настраиваются координаты, частота, полоса, CR и SF по отдельности, мощность передатчика, пересылка пакетов, размер хэша пути, интервалы advert и flood advert, лимит flood. Внизу меню — `Save & exit`. Значения применяются через тот же обработчик, что и последовательный CLI, поэтому проверка и сохранение одинаковы для обоих путей, а ответ устройства показывается на экране.
|
||||
|
||||
Координаты вводятся в формате `xx.xxxx yy.yyyy`: длинное нажатие входит в правку цифры, короткое — `+1`, двойное — `−1`, длинное — применить, короткое после применения — перейти к следующей цифре, в конце экран подтверждения.
|
||||
Координаты вводятся в формате `xx.xxxx yy.yyyy`, частота — как `868.731` (целые МГц шагом 1, кГц по разрядам), полоса — поразрядно: длинное нажатие входит в правку цифры или позиции, короткое — `+1`, двойное — `−1`, длинное — применить, короткое после применения — перейти к следующей цифре, в конце экран подтверждения.
|
||||
|
||||
Подробное описание кнопок, меню и ограничений — в [Настройке с дисплея и кнопки](./posadmesh-docs/device-setup.md).
|
||||
|
||||
@@ -375,7 +375,8 @@ MQTT-репитер использует:
|
||||
- добавлен мастер настройки репитера ([`examples/simple_repeater/UITask.*`](./examples/simple_repeater/UITask.cpp)): работает на любой цели, у которой есть дисплей и кнопка, и полностью исключается препроцессором там, где их нет;
|
||||
- вход в мастер — двойное нажатие кнопки из экрана состояния (на узле без имени мастер открывается сам после заставки); выход — пункт `Save & exit`;
|
||||
- ввод координат в формате `xx.xxxx yy.yyyy`: длинное нажатие — правка текущей цифры, короткое — `+1`, двойное — `−1`, длинное — применить, короткое после применения — следующая цифра; после всех 12 цифр — экран сохранения;
|
||||
- остальные пункты: радиопресет MeshCoreTel (868,731 МГц, 62,5 кГц, SF7, CR7), мощность TX, пересылка пакетов, размер хэша пути, интервалы advert и flood advert, лимит flood;
|
||||
- радиопараметры настраиваются по отдельности: `Frequency` (целые МГц шагом ±1, кГц по разрядам — как координаты), `Bandwidth` (поразрядно, формат `062.50`), `CR` и `SF` (шагом); любой из четырёх пунктов отправляет `set radio <частота>,<полоса>,<sf>,<cr>`, поэтому остальные значения сохраняются;
|
||||
- остальные пункты: мощность TX, пересылка пакетов, размер хэша пути, интервалы advert и flood advert, лимит flood; внизу меню — `Save & exit`;
|
||||
- значения применяются через тот же `CommonCLI`, что и последовательный порт, а ответ устройства (`OK` или текст ошибки) выводится на экран;
|
||||
- документация — [Настройка с дисплея и кнопки](./posadmesh-docs/device-setup.md);
|
||||
- сборка проверена на `heltec_v4_repeater_mqtt`, `Meshadventurer_sx1262_repeater_mqtt`, `Generic_E22_sx1262_repeater_mqtt` (без дисплея) и `RAK_3112_repeater_mqtt`.
|
||||
|
||||
@@ -16,11 +16,15 @@
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
#define POWEROFF_DELAY 3000
|
||||
#define TOAST_MILLIS 3000
|
||||
#define CHAR_W 6 // ширина символа при setTextSize(1)
|
||||
|
||||
// Пункты мастера. Подписи латиницей: шрифты драйверов не содержат кириллицы.
|
||||
enum {
|
||||
MENU_COORDS = 0,
|
||||
MENU_RADIO,
|
||||
MENU_FREQ,
|
||||
MENU_BW,
|
||||
MENU_CR,
|
||||
MENU_SF,
|
||||
MENU_TX,
|
||||
MENU_REPEAT,
|
||||
MENU_PATHHASH,
|
||||
@@ -33,7 +37,10 @@ enum {
|
||||
|
||||
static const char* const MENU_NAMES[MENU_COUNT] = {
|
||||
"Coordinates",
|
||||
"Radio preset",
|
||||
"Frequency",
|
||||
"Bandwidth",
|
||||
"CR",
|
||||
"SF",
|
||||
"TX power",
|
||||
"Repeat",
|
||||
"Path hash",
|
||||
@@ -43,9 +50,6 @@ static const char* const MENU_NAMES[MENU_COUNT] = {
|
||||
"Save & exit"
|
||||
};
|
||||
|
||||
// 0 = не менять; MeshCoreTel MOW: 868.731 МГц, 62.5 кГц, SF7, CR7
|
||||
static const char* const RADIO_PRESET_CMDS[] = { nullptr, "set radio 868.731,62.5,7,7" };
|
||||
static const char* const RADIO_PRESET_NAMES[] = { "keep current", "MeshCoreTel MOW" };
|
||||
static const char* const PATH_HASH_NAMES[] = { "0 bytes", "1 byte", "2 bytes" };
|
||||
|
||||
// позиции цифр в строке вида "xx.xxxx"
|
||||
@@ -143,90 +147,160 @@ void UITask::openMenu() {
|
||||
_auto_off = millis() + WIZARD_AUTO_OFF_MILLIS;
|
||||
}
|
||||
|
||||
// --- радиопараметры ---------------------------------------------------------
|
||||
|
||||
void UITask::loadRadio() {
|
||||
if (_radio_loaded || _node_prefs == nullptr) return;
|
||||
_radio_freq = _node_prefs->freq;
|
||||
_radio_bw = _node_prefs->bw;
|
||||
_radio_sf = _node_prefs->sf;
|
||||
_radio_cr = _node_prefs->cr;
|
||||
_radio_loaded = true;
|
||||
}
|
||||
|
||||
void UITask::applyRadio() {
|
||||
char cmd[96];
|
||||
snprintf(cmd, sizeof(cmd), "set radio %.3f,%.2f,%d,%d", _radio_freq, _radio_bw, _radio_sf, _radio_cr);
|
||||
runCommand(cmd);
|
||||
}
|
||||
|
||||
void UITask::beginFreqEdit() {
|
||||
loadRadio();
|
||||
for (int i = 0; i < 4; i++) _freq_applied[i] = false;
|
||||
_freq_index = 0;
|
||||
_freq_editing = false;
|
||||
|
||||
double v = _radio_freq < 0 ? -_radio_freq : _radio_freq;
|
||||
int mhz = (int)v;
|
||||
int khz = (int)((v - mhz) * 1000.0 + 0.5);
|
||||
if (khz > 999) { khz = 0; mhz++; }
|
||||
_freq_mhz = mhz;
|
||||
_freq_khz[0] = (char)('0' + (khz / 100) % 10);
|
||||
_freq_khz[1] = (char)('0' + (khz / 10) % 10);
|
||||
_freq_khz[2] = (char)('0' + khz % 10);
|
||||
_screen = SCREEN_FREQ;
|
||||
}
|
||||
|
||||
double UITask::freqValue() const {
|
||||
int khz = (_freq_khz[0] - '0') * 100 + (_freq_khz[1] - '0') * 10 + (_freq_khz[2] - '0');
|
||||
return _freq_mhz + khz / 1000.0;
|
||||
}
|
||||
|
||||
void UITask::beginBwEdit() {
|
||||
loadRadio();
|
||||
_digit_int = 3;
|
||||
_digit_count = 5; // xxx.xx, например 062.50
|
||||
for (int i = 0; i < _digit_count; i++) _digit_applied[i] = false;
|
||||
_digit_index = 0;
|
||||
_digit_editing = false;
|
||||
|
||||
double v = _radio_bw < 0 ? -_radio_bw : _radio_bw;
|
||||
int ip = (int)v;
|
||||
int fp = (int)((v - ip) * 100.0 + 0.5);
|
||||
if (fp > 99) { fp = 0; ip++; }
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof(buf), "%03d%02d", ip % 1000, fp);
|
||||
memcpy(_digit_digits, buf, _digit_count);
|
||||
_screen = SCREEN_DIGITS;
|
||||
}
|
||||
|
||||
double UITask::digitValue() const {
|
||||
int ip = 0, fp = 0;
|
||||
for (int i = 0; i < _digit_int; i++) ip = ip * 10 + (_digit_digits[i] - '0');
|
||||
for (int i = _digit_int; i < _digit_count; i++) fp = fp * 10 + (_digit_digits[i] - '0');
|
||||
double scale = 1;
|
||||
for (int i = _digit_int; i < _digit_count; i++) scale *= 10;
|
||||
return ip + fp / scale;
|
||||
}
|
||||
|
||||
// --- меню -------------------------------------------------------------------
|
||||
|
||||
void UITask::openField(uint8_t index) {
|
||||
_menu_index = index;
|
||||
_auto_off = millis() + WIZARD_AUTO_OFF_MILLIS;
|
||||
|
||||
switch (index) {
|
||||
case MENU_COORDS:
|
||||
coordFromPrefs();
|
||||
_screen = SCREEN_COORDS;
|
||||
return;
|
||||
case MENU_FREQ:
|
||||
beginFreqEdit();
|
||||
return;
|
||||
case MENU_BW:
|
||||
beginBwEdit();
|
||||
return;
|
||||
case MENU_CR:
|
||||
loadRadio();
|
||||
_value = _radio_cr;
|
||||
break;
|
||||
case MENU_RADIO:
|
||||
_value = 0; // keep current
|
||||
_screen = SCREEN_VALUE;
|
||||
case MENU_SF:
|
||||
loadRadio();
|
||||
_value = _radio_sf;
|
||||
break;
|
||||
case MENU_TX:
|
||||
_value = _node_prefs ? _node_prefs->tx_power_dbm : 0;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
case MENU_REPEAT:
|
||||
_value = (_node_prefs && _node_prefs->disable_fwd) ? 0 : 1;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
case MENU_PATHHASH:
|
||||
_value = _node_prefs ? _node_prefs->path_hash_mode : 0;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
case MENU_ADVERT:
|
||||
_value = _node_prefs ? _node_prefs->advert_interval * 2 : 0;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
case MENU_FLOODADV:
|
||||
_value = _node_prefs ? _node_prefs->flood_advert_interval : 0;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
case MENU_FLOODMAX:
|
||||
_value = _node_prefs ? _node_prefs->flood_max : 0;
|
||||
_screen = SCREEN_VALUE;
|
||||
break;
|
||||
default:
|
||||
_screen = SCREEN_STATUS;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
_auto_off = millis() + WIZARD_AUTO_OFF_MILLIS;
|
||||
_screen = SCREEN_VALUE;
|
||||
}
|
||||
|
||||
void UITask::applyField() {
|
||||
char cmd[96];
|
||||
bool ok = false;
|
||||
|
||||
switch (_menu_index) {
|
||||
case MENU_RADIO:
|
||||
if (_value <= 0 || RADIO_PRESET_CMDS[_value] == nullptr) {
|
||||
setToast("keep current");
|
||||
return;
|
||||
}
|
||||
ok = runCommand(RADIO_PRESET_CMDS[_value]);
|
||||
break;
|
||||
case MENU_CR:
|
||||
_radio_cr = _value;
|
||||
applyRadio();
|
||||
return;
|
||||
case MENU_SF:
|
||||
_radio_sf = _value;
|
||||
applyRadio();
|
||||
return;
|
||||
case MENU_TX:
|
||||
snprintf(cmd, sizeof(cmd), "set tx %d", _value);
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
case MENU_REPEAT:
|
||||
snprintf(cmd, sizeof(cmd), "set repeat %s", _value ? "on" : "off");
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
case MENU_PATHHASH:
|
||||
snprintf(cmd, sizeof(cmd), "set path.hash.mode %d", _value);
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
case MENU_ADVERT:
|
||||
snprintf(cmd, sizeof(cmd), "set advert.interval %d", _value);
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
case MENU_FLOODADV:
|
||||
snprintf(cmd, sizeof(cmd), "set flood.advert.interval %d", _value);
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
case MENU_FLOODMAX:
|
||||
snprintf(cmd, sizeof(cmd), "set flood.max %d", _value);
|
||||
ok = runCommand(cmd);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
(void)ok;
|
||||
runCommand(cmd);
|
||||
}
|
||||
|
||||
// --- координаты -------------------------------------------------------------
|
||||
|
||||
void UITask::coordFromPrefs() {
|
||||
for (int i = 0; i < 12; i++) _coord_applied[i] = false;
|
||||
_coord_digit = 0;
|
||||
@@ -274,6 +348,8 @@ void UITask::coordSave() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- кнопка -----------------------------------------------------------------
|
||||
|
||||
void UITask::handleButton(int ev) {
|
||||
switch (_screen) {
|
||||
case SCREEN_STATUS:
|
||||
@@ -295,6 +371,7 @@ void UITask::handleButton(int ev) {
|
||||
_menu_index = (uint8_t)((_menu_index + MENU_COUNT - 1) % MENU_COUNT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
if (_menu_index == MENU_EXIT) {
|
||||
applyRadio(); // на случай правок частоты/полосы/SF/CR
|
||||
_screen = SCREEN_STATUS;
|
||||
} else {
|
||||
openField(_menu_index);
|
||||
@@ -306,8 +383,11 @@ void UITask::handleButton(int ev) {
|
||||
if (ev == BUTTON_EVENT_CLICK || ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
int dir = (ev == BUTTON_EVENT_CLICK) ? 1 : -1;
|
||||
switch (_menu_index) {
|
||||
case MENU_RADIO:
|
||||
_value = (_value + 1) % 2;
|
||||
case MENU_CR:
|
||||
stepClamped(_value, dir, 5, 8, 1);
|
||||
break;
|
||||
case MENU_SF:
|
||||
stepClamped(_value, dir, 5, 12, 1);
|
||||
break;
|
||||
case MENU_REPEAT:
|
||||
_value = _value ? 0 : 1;
|
||||
@@ -336,6 +416,82 @@ void UITask::handleButton(int ev) {
|
||||
}
|
||||
break;
|
||||
|
||||
case SCREEN_DIGITS: { // полоса: поразрядно, как координаты
|
||||
if (_digit_editing) {
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
int d = _digit_digits[_digit_index] - '0';
|
||||
_digit_digits[_digit_index] = (char)('0' + (d + 1) % 10);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
int d = _digit_digits[_digit_index] - '0';
|
||||
_digit_digits[_digit_index] = (char)('0' + (d + 9) % 10);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
_digit_editing = false;
|
||||
_digit_applied[_digit_index] = true;
|
||||
}
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
_digit_editing = true;
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
_digit_index = (uint8_t)((_digit_index + _digit_count - 1) % _digit_count);
|
||||
} else if (ev == BUTTON_EVENT_CLICK) {
|
||||
bool all = true;
|
||||
for (int i = 0; i < _digit_count; i++) {
|
||||
if (!_digit_applied[i]) { all = false; break; }
|
||||
}
|
||||
if (_digit_index == (uint8_t)(_digit_count - 1) && all) {
|
||||
_radio_bw = digitValue();
|
||||
applyRadio();
|
||||
_screen = SCREEN_MENU;
|
||||
} else {
|
||||
_digit_index = (uint8_t)((_digit_index + 1) % _digit_count);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_FREQ: { // частота: МГц шагом 1, кГц по разрядам
|
||||
if (_freq_editing) {
|
||||
if (_freq_index == 0) {
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
if (_freq_mhz < 2500) _freq_mhz++;
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
if (_freq_mhz > 150) _freq_mhz--;
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
_freq_editing = false;
|
||||
_freq_applied[0] = true;
|
||||
}
|
||||
} else {
|
||||
uint8_t k = (uint8_t)(_freq_index - 1);
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
int d = _freq_khz[k] - '0';
|
||||
_freq_khz[k] = (char)('0' + (d + 1) % 10);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
int d = _freq_khz[k] - '0';
|
||||
_freq_khz[k] = (char)('0' + (d + 9) % 10);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
_freq_editing = false;
|
||||
_freq_applied[_freq_index] = true;
|
||||
}
|
||||
}
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
_freq_editing = true;
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
_freq_index = (uint8_t)((_freq_index + 3) % 4);
|
||||
} else if (ev == BUTTON_EVENT_CLICK) {
|
||||
bool all = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!_freq_applied[i]) { all = false; break; }
|
||||
}
|
||||
if (_freq_index == 3 && all) {
|
||||
_radio_freq = freqValue();
|
||||
applyRadio();
|
||||
_screen = SCREEN_MENU;
|
||||
} else {
|
||||
_freq_index = (uint8_t)((_freq_index + 1) % 4);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SCREEN_COORDS:
|
||||
if (_coord_editing) {
|
||||
// правка текущей цифры: короткое +1, двойное -1, длинное — применить
|
||||
@@ -412,6 +568,8 @@ void UITask::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- отрисовка --------------------------------------------------------------
|
||||
|
||||
void UITask::renderTitleBar(const char* title) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(0, 0, _display->width(), 11);
|
||||
@@ -478,8 +636,11 @@ void UITask::renderValue() {
|
||||
const char* title = MENU_NAMES[_menu_index];
|
||||
|
||||
switch (_menu_index) {
|
||||
case MENU_RADIO:
|
||||
snprintf(value, sizeof(value), "%s", RADIO_PRESET_NAMES[_value % 2]);
|
||||
case MENU_CR:
|
||||
snprintf(value, sizeof(value), "%d", _value);
|
||||
break;
|
||||
case MENU_SF:
|
||||
snprintf(value, sizeof(value), "%d", _value);
|
||||
break;
|
||||
case MENU_TX:
|
||||
snprintf(value, sizeof(value), "%d dBm", _value);
|
||||
@@ -514,6 +675,91 @@ void UITask::renderValue() {
|
||||
renderToast();
|
||||
}
|
||||
|
||||
// поразрядный ввод значения с точкой (полоса): "062.50"
|
||||
void UITask::renderDigits() {
|
||||
renderTitleBar("Bandwidth");
|
||||
_display->setTextSize(1);
|
||||
int y = 22;
|
||||
int cx = 2;
|
||||
for (int i = 0; i < _digit_count; i++) {
|
||||
if (i == _digit_int) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->setCursor(cx, y);
|
||||
_display->print(".");
|
||||
cx += CHAR_W;
|
||||
}
|
||||
char c[2] = { _digit_digits[i], 0 };
|
||||
if (i == _digit_index) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(cx, y - 1, CHAR_W, 9);
|
||||
_display->setColor(UIColor::window_bkg);
|
||||
} else {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
}
|
||||
_display->setCursor(cx, y);
|
||||
_display->print(c);
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
cx += CHAR_W;
|
||||
}
|
||||
_display->setCursor(2, 36);
|
||||
_display->print("MHz");
|
||||
renderHint(_digit_editing ? "clk+ dbl- hold=set" : "hold=edit clk=next");
|
||||
renderToast();
|
||||
}
|
||||
|
||||
// частота: целые МГц выделяются целиком (шаг 1), кГц — по разрядам
|
||||
void UITask::renderFreq() {
|
||||
renderTitleBar("Frequency");
|
||||
_display->setTextSize(1);
|
||||
|
||||
int y = 22;
|
||||
int cx = 2;
|
||||
char mhz[8];
|
||||
snprintf(mhz, sizeof(mhz), "%d", _freq_mhz);
|
||||
int mhzLen = (int)strlen(mhz);
|
||||
|
||||
if (_freq_index == 0) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(cx, y - 1, mhzLen * CHAR_W, 9);
|
||||
_display->setColor(UIColor::window_bkg);
|
||||
} else {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
}
|
||||
_display->setCursor(cx, y);
|
||||
_display->print(mhz);
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
cx += mhzLen * CHAR_W;
|
||||
|
||||
_display->setCursor(cx, y);
|
||||
_display->print(".");
|
||||
cx += CHAR_W;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char c[2] = { _freq_khz[i], 0 };
|
||||
if (_freq_index == (uint8_t)(i + 1)) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(cx, y - 1, CHAR_W, 9);
|
||||
_display->setColor(UIColor::window_bkg);
|
||||
} else {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
}
|
||||
_display->setCursor(cx, y);
|
||||
_display->print(c);
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
cx += CHAR_W;
|
||||
}
|
||||
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->setCursor(2, 36);
|
||||
_display->print("MHz");
|
||||
if (_freq_editing) {
|
||||
renderHint(_freq_index == 0 ? "clk +1MHz dbl -1" : "clk+ dbl- hold=set");
|
||||
} else {
|
||||
renderHint("hold=edit clk=next");
|
||||
}
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderCoords() {
|
||||
char text[10];
|
||||
renderTitleBar("Coordinates");
|
||||
@@ -528,12 +774,12 @@ void UITask::renderCoords() {
|
||||
for (int k = 0; k < 6; k++) {
|
||||
if (COORD_POS[k] == i) { digitIndex = k; break; }
|
||||
}
|
||||
int x = i * 6;
|
||||
int x = i * CHAR_W;
|
||||
bool active = (digitIndex >= 0) && ((axis * 6 + digitIndex) == _coord_digit);
|
||||
char c[2] = { text[i], 0 };
|
||||
if (active) {
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(x, y - 1, 6, 9);
|
||||
_display->fillRect(x, y - 1, CHAR_W, 9);
|
||||
_display->setColor(UIColor::window_bkg);
|
||||
_display->setCursor(x, y);
|
||||
_display->print(c);
|
||||
@@ -642,6 +888,8 @@ void UITask::renderCurrScreen() {
|
||||
switch (_screen) {
|
||||
case SCREEN_MENU: renderMenu(); break;
|
||||
case SCREEN_VALUE: renderValue(); break;
|
||||
case SCREEN_DIGITS: renderDigits(); break;
|
||||
case SCREEN_FREQ: renderFreq(); break;
|
||||
case SCREEN_COORDS: renderCoords(); break;
|
||||
case SCREEN_COORDS_SAVE: renderCoordsSave(); break;
|
||||
default: renderStatus(); break;
|
||||
|
||||
@@ -15,7 +15,9 @@ public:
|
||||
enum Screen : uint8_t {
|
||||
SCREEN_STATUS = 0,
|
||||
SCREEN_MENU,
|
||||
SCREEN_VALUE,
|
||||
SCREEN_VALUE, // числа и переключатели: SF, CR, TX, repeat, path hash, advert, flood
|
||||
SCREEN_DIGITS, // поразрядный ввод значения с точкой (полоса)
|
||||
SCREEN_FREQ, // частота: МГц шагом 1, кГц по разрядам
|
||||
SCREEN_COORDS,
|
||||
SCREEN_COORDS_SAVE
|
||||
};
|
||||
@@ -43,10 +45,38 @@ private:
|
||||
char _toast[40];
|
||||
unsigned long _toast_until = 0;
|
||||
|
||||
// радиопараметры: рабочая копия, пока мастер открыт
|
||||
bool _radio_loaded = false;
|
||||
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 beginFreqEdit();
|
||||
double digitValue() const;
|
||||
double freqValue() const;
|
||||
void coordFromPrefs();
|
||||
void coordSave();
|
||||
double coordAxisValue(uint8_t axis) const; // axis 0 = lat, 1 = lon
|
||||
@@ -59,6 +89,8 @@ private:
|
||||
void renderStatus();
|
||||
void renderMenu();
|
||||
void renderValue();
|
||||
void renderDigits();
|
||||
void renderFreq();
|
||||
void renderCoords();
|
||||
void renderCoordsSave();
|
||||
void renderTitleBar(const char* title);
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
| Пункт меню | Что делает | Команда CLI |
|
||||
| --- | --- | --- |
|
||||
| `Coordinates` | широта и долгота узла | `set lat`, `set lon` |
|
||||
| `Radio preset` | `keep current` или готовый пресет сети MeshCoreTel (868,731 МГц, 62,5 кГц, SF7, CR7) | `set radio` |
|
||||
| `Frequency` | частота: целые МГц шагом 1, кГц — по разрядам | `set radio` |
|
||||
| `Bandwidth` | полоса, поразрядно | `set radio` |
|
||||
| `CR` | coding rate, 5–8 | `set radio` |
|
||||
| `SF` | spreading factor, 5–12 | `set radio` |
|
||||
| `TX power` | мощность передатчика, 1–30 дБм | `set tx` |
|
||||
| `Repeat` | пересылка пакетов включена или выключена | `set repeat` |
|
||||
| `Path hash` | размер хэша пути: 0, 1 или 2 байта | `set path.hash.mode` |
|
||||
@@ -58,6 +61,21 @@
|
||||
|
||||
Ограничение: мастер вводит положительные координаты (формат без знака). Для южного полушария и западной долготы используйте CLI (`set lat -33.1234`) или веб-панель.
|
||||
|
||||
## Ввод частоты и полосы
|
||||
|
||||
Частота показывается как `868.731`. Курсор проходит четыре позиции: целые мегагерцы и три разряда килогерц.
|
||||
|
||||
- **целые МГц** (выделены целиком): короткое нажатие — `+1 МГц`, двойное — `−1 МГц`;
|
||||
- **разряды кГц** (по одному): короткое нажатие — `+1`, двойное — `−1`, принцип тот же, что у координат;
|
||||
- длинное нажатие — войти в правку позиции, а в режиме правки — применить её;
|
||||
- короткое нажатие после применения — перейти к следующей позиции; на последней позиции короткое нажатие применяет частоту и возвращает в меню.
|
||||
|
||||
Полоса вводится так же поразрядно, формат `062.50` — то есть 62,5 кГц.
|
||||
|
||||
`CR` и `SF` меняются шагом: короткое нажатие — `+1`, двойное — `−1`, длинное — применить.
|
||||
|
||||
Любой из четырёх пунктов отправляет команду целиком — `set radio <частота>,<полоса>,<sf>,<cr>`, поэтому остальные три параметра сохраняют свои значения. Устройство отвечает `OK - reboot to apply`: изменения радио вступают в силу после перезагрузки.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- На экранах без кириллицы подписи меню выводятся латиницей.
|
||||
|
||||
Reference in New Issue
Block a user