diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h index 5b790c17a..0fa9d41ee 100644 --- a/radio/src/datastructs_private.h +++ b/radio/src/datastructs_private.h @@ -700,15 +700,6 @@ PACK(struct ModelData { FUNCTION_SWITCHS_FIELDS - bool isTrainerTraineeEnable() const - { -#if defined(PCBNV14) - return trainerData.mode >= TRAINER_MODE_MASTER_TRAINER_JACK; -#else - return true; -#endif - } - uint8_t getThrottleStickTrimSource() const { // The order here is TERA, so that 0 (default) means Throttle diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index 4ac29d8c9..dac0830b8 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -156,6 +156,10 @@ if(GHOST) add_gui_src(radio_ghost_module_config.cpp) endif() +if(BLUETOOTH) + add_gui_src(trainer_bluetooth.cpp) +endif() + set(SRC ${SRC} storage/modelslist.cpp) option(WINDOWS_INSPECT_BORDERS "Draw bounding boxes around windows" OFF) diff --git a/radio/src/gui/colorlcd/module_setup.cpp b/radio/src/gui/colorlcd/module_setup.cpp index 031912938..27fc0f94d 100644 --- a/radio/src/gui/colorlcd/module_setup.cpp +++ b/radio/src/gui/colorlcd/module_setup.cpp @@ -131,7 +131,6 @@ ModuleWindow::ModuleWindow(Window* parent, uint8_t moduleIdx) : { setFlexLayout(); updateModule(); - // updateSubType(); } void ModuleWindow::updateModule() diff --git a/radio/src/gui/colorlcd/trainer_bluetooth.cpp b/radio/src/gui/colorlcd/trainer_bluetooth.cpp new file mode 100644 index 000000000..d3ea146c5 --- /dev/null +++ b/radio/src/gui/colorlcd/trainer_bluetooth.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "trainer_bluetooth.h" +#include "opentx.h" +#include "bluetooth.h" + +#define SET_DIRTY() storageDirty(EE_MODEL) + +static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), + LV_GRID_TEMPLATE_LAST}; +static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + +class BTDiscoverMenu : public Menu +{ + uint8_t devCount = 0; + + void checkEvents() override; + void selectAddr(const char* addr); + +public: + BTDiscoverMenu(); +}; + +BTDiscoverMenu::BTDiscoverMenu() : + Menu(Layer::back()) +{ + setTitle(STR_BT_SELECT_DEVICE); + // TODO: set minimum height + + reusableBuffer.moduleSetup.bt.devicesCount = 0; + bluetooth.state = BLUETOOTH_STATE_DISCOVER_REQUESTED; +} + +void BTDiscoverMenu::checkEvents() +{ + if (bluetooth.state == BLUETOOTH_STATE_DISCOVER_START || + bluetooth.state == BLUETOOTH_STATE_DISCOVER_END) { + int cnt = min(reusableBuffer.moduleSetup.bt.devicesCount, + MAX_BLUETOOTH_DISTANT_ADDR); + + if (devCount < cnt) { + for (int i = 0; i < cnt - devCount; i++) { + int index = devCount + i; + const char* item = reusableBuffer.moduleSetup.bt.devices[index]; + addLine(item, [=]() { selectAddr(item); }); + } + devCount = cnt; + } + } +} + +void BTDiscoverMenu::selectAddr(const char* addr) +{ + strncpy(bluetooth.distantAddr, addr, LEN_BLUETOOTH_ADDR); + bluetooth.state = BLUETOOTH_STATE_BIND_REQUESTED; + SET_DIRTY(); +} + +BluetoothTrainerWindow::BluetoothTrainerWindow(Window* parent) : + FormGroup(parent, rect_t{}) +{ + setFlexLayout(); + FlexGridLayout grid(col_dsc, row_dsc, 2); + + auto line = newLine(&grid); + state = new StaticText(line, rect_t{}, "", 0, COLOR_THEME_PRIMARY1); + r_addr = new StaticText(line, rect_t{}, "", 0, COLOR_THEME_PRIMARY1); + + btn_line = newLine(&grid); + grid.nextCell(); + + btn = new TextButton(btn_line, rect_t{}, ""); + btn->setWidth(LV_DPI_DEF / 2); + btn->padAll(lv_dpx(4)); +} + +void BluetoothTrainerWindow::setMaster(bool master) +{ + if (master) { + is_master = true; + lv_obj_clear_flag(btn_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } else { + is_master = false; + lv_obj_add_flag(btn_line->getLvObj(), LV_OBJ_FLAG_HIDDEN); + } +} + +static const char _empty_addr[] = "---"; + +void BluetoothTrainerWindow::refresh() +{ + if (is_master) { + if (bluetooth.distantAddr[0]) { + r_addr->setText(bluetooth.distantAddr); + btn->setText(STR_CLEAR); + btn->setPressHandler([]() { + bluetooth.state = BLUETOOTH_STATE_CLEAR_REQUESTED; + memclear(bluetooth.distantAddr, sizeof(bluetooth.distantAddr)); + return 0; + }); + } else if (bluetooth.state < BLUETOOTH_STATE_IDLE) { + r_addr->setText(_empty_addr); + btn->setText(STR_BLUETOOTH_INIT); + btn->setPressHandler([]() { + bluetooth.state = BLUETOOTH_STATE_OFF; + return 0; + }); + } else { + r_addr->setText(_empty_addr); + btn->setText(STR_DISCOVER); + btn->setPressHandler([=]() { + new BTDiscoverMenu(); + return 0; + }); + } + } + + if (bluetooth.state == BLUETOOTH_STATE_CONNECTED) { + state->setText(STR_CONNECTED); + if (!is_master) r_addr->setText(bluetooth.distantAddr); + } else { + state->setText(STR_NOT_CONNECTED); + if (!is_master) r_addr->setText(_empty_addr); + } +} diff --git a/radio/src/gui/colorlcd/trainer_bluetooth.h b/radio/src/gui/colorlcd/trainer_bluetooth.h new file mode 100644 index 000000000..9aa5d785e --- /dev/null +++ b/radio/src/gui/colorlcd/trainer_bluetooth.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include "form.h" +#include "static.h" +#include "button.h" + +class BluetoothTrainerWindow : public FormGroup +{ + bool is_master = true; + StaticText* state; + StaticText* r_addr; + + Window* btn_line; + TextButton* btn; + +public: + BluetoothTrainerWindow(Window* parent); + + void setMaster(bool master); + void refresh(); +}; diff --git a/radio/src/gui/colorlcd/trainer_setup.cpp b/radio/src/gui/colorlcd/trainer_setup.cpp index 4b91defb4..8dfa2810c 100644 --- a/radio/src/gui/colorlcd/trainer_setup.cpp +++ b/radio/src/gui/colorlcd/trainer_setup.cpp @@ -29,289 +29,139 @@ #include "static.h" #include "button.h" #include "numberedit.h" +#include "channel_range.h" +#include "ppm_settings.h" #if defined(BLUETOOTH) -#include "bluetooth.h" +#include "trainer_bluetooth.h" #endif +static const lv_coord_t col_dsc[] = {LV_GRID_FR(1), LV_GRID_FR(2), + LV_GRID_TEMPLATE_LAST}; +static const lv_coord_t row_dsc[] = {LV_GRID_CONTENT, + LV_GRID_TEMPLATE_LAST}; + class TrainerModuleWindow : public FormGroup { public: - TrainerModuleWindow(FormWindow *parent, const rect_t &rect); + TrainerModuleWindow(FormGroup* parent); void checkEvents() override; void update(); protected: - NumberEdit *channelStart = nullptr; - NumberEdit *channelEnd = nullptr; + ChannelRange* chRange = nullptr; #if defined(BLUETOOTH) - StaticText *btChannelEnd = nullptr; - StaticText *btDistAddress = nullptr; - TextButton *btMasterButton = nullptr; - Menu *btPopUpMenu = nullptr; - bool btCanceled = false; + // StaticText *btChannelEnd = nullptr; + // StaticText *btDistAddress = nullptr; + // TextButton *btMasterButton = nullptr; + // Menu *btPopUpMenu = nullptr; + // bool btCanceled = false; private: - bool popupopen = false; - int devicecount = 0; - uint8_t lastbluetoothstate = BLUETOOTH_STATE_OFF; + // bool popupopen = false; + // int devicecount = 0; + // uint8_t lastbluetoothstate = BLUETOOTH_STATE_OFF; - void btDiscoverMenuItemChosen(); - void btDiscoverMenuAddItem(const char *itm); + // void btDiscoverMenuItemChosen(); + // void btDiscoverMenuAddItem(const char *itm); #endif }; -TrainerModuleWindow::TrainerModuleWindow(FormWindow *parent, - const rect_t &rect) : - FormGroup(parent, rect, FORWARD_SCROLL | NO_FOCUS | FORM_FORWARD_FOCUS) +TrainerModuleWindow::TrainerModuleWindow(FormGroup* parent) : + FormGroup(parent, rect_t{}) { + setFlexLayout(); update(); } void TrainerModuleWindow::checkEvents() { -#if defined(BLUETOOTH) - if (popupopen) { - if (bluetooth.state == BLUETOOTH_STATE_DISCOVER_START || - bluetooth.state == BLUETOOTH_STATE_DISCOVER_END) { - int cnt = min(reusableBuffer.moduleSetup.bt.devicesCount, - MAX_BLUETOOTH_DISTANT_ADDR); - if (devicecount < cnt) { - for (int i = 0; i < cnt - devicecount; i++) { - int index = devicecount + i; - btDiscoverMenuAddItem(reusableBuffer.moduleSetup.bt.devices[index]); - } - devicecount = cnt; - } - } - } - if (bluetooth.state != lastbluetoothstate) { - // TODO: - // if (!popupopen && !trChoiceOpen) update(); - lastbluetoothstate = bluetooth.state; - } -#endif +// #if defined(BLUETOOTH) +// if (popupopen) { +// if (bluetooth.state == BLUETOOTH_STATE_DISCOVER_START || +// bluetooth.state == BLUETOOTH_STATE_DISCOVER_END) { +// int cnt = min(reusableBuffer.moduleSetup.bt.devicesCount, +// MAX_BLUETOOTH_DISTANT_ADDR); +// if (devicecount < cnt) { +// for (int i = 0; i < cnt - devicecount; i++) { +// int index = devicecount + i; +// btDiscoverMenuAddItem(reusableBuffer.moduleSetup.bt.devices[index]); +// } +// devicecount = cnt; +// } +// } +// } +// if (bluetooth.state != lastbluetoothstate) { +// // TODO: +// // if (!popupopen && !trChoiceOpen) update(); +// lastbluetoothstate = bluetooth.state; +// } +// #endif FormGroup::checkEvents(); } void TrainerModuleWindow::update() { - FormGridLayout grid; + FlexGridLayout grid(col_dsc, row_dsc, 2); clear(); - if (g_model.isTrainerTraineeEnable()) { -#if defined(BLUETOOTH) - if (g_model.trainerData.mode == TRAINER_MODE_MASTER_BLUETOOTH) { - - btDistAddress = new StaticText(this, grid.getFieldSlot(true), "---", 0, - COLOR_THEME_PRIMARY1); - if (bluetooth.state == BLUETOOTH_STATE_CONNECTED) - new StaticText(this, grid.getLabelSlot(true), STR_CONNECTED, 0, - COLOR_THEME_PRIMARY1); - else - new StaticText(this, grid.getLabelSlot(true), STR_NOT_CONNECTED, 0, - COLOR_THEME_PRIMARY1); - - grid.nextLine(); - btMasterButton = - new TextButton(this, grid.getFieldSlot(), "", [=]() -> uint8_t { - if (bluetooth.distantAddr[0]) { - bluetooth.state = BLUETOOTH_STATE_CLEAR_REQUESTED; - memclear(bluetooth.distantAddr, sizeof(bluetooth.distantAddr)); - } else if (bluetooth.state < BLUETOOTH_STATE_IDLE) { - bluetooth.state = BLUETOOTH_STATE_OFF; - } else { - reusableBuffer.moduleSetup.bt.devicesCount = 0; - devicecount = 0; - bluetooth.state = BLUETOOTH_STATE_DISCOVER_REQUESTED; - // TODO: - // btPopUpMenu = new Menu(parent); - // btPopUpMenu->setTitle(STR_BT_SELECT_DEVICE); - // popupopen = true; - // btPopUpMenu->setCloseHandler([=]() { popupopen = false; }); - } - return 0; - }); - - if (bluetooth.distantAddr[0]) { - btDistAddress->setText(bluetooth.distantAddr); - btMasterButton->setText(STR_CLEAR); - } else if (bluetooth.state < BLUETOOTH_STATE_IDLE) { - btMasterButton->setText(STR_BLUETOOTH_INIT); - } else { - btMasterButton->setText(STR_DISCOVER); - } - - grid.nextLine(); - - } else if (g_model.trainerData.mode == TRAINER_MODE_SLAVE_BLUETOOTH) { - if (bluetooth.state == BLUETOOTH_STATE_CONNECTED) { - new StaticText(this, grid.getLabelSlot(true), STR_CONNECTED, 0, - COLOR_THEME_PRIMARY1); - new StaticText(this, grid.getFieldSlot(), bluetooth.distantAddr, 0, - COLOR_THEME_PRIMARY1); - } else - new StaticText(this, grid.getLabelSlot(true), STR_NOT_CONNECTED, 0, - COLOR_THEME_PRIMARY1); - - grid.nextLine(); - - new StaticText(this, grid.getLabelSlot(true), STR_CHANNELRANGE, 0, - COLOR_THEME_PRIMARY1); - channelStart = - new NumberEdit(this, grid.getFieldSlot(2, 0), 1, - MAX_OUTPUT_CHANNELS - BLUETOOTH_TRAINER_CHANNELS + 1, - GET_DEFAULT(1 + g_model.trainerData.channelsStart)); - char chend[6]; - snprintf(chend, sizeof(chend), "%s%d", STR_CH, - g_model.trainerData.channelsStart + BLUETOOTH_TRAINER_CHANNELS); - - btChannelEnd = new StaticText(this, grid.getFieldSlot(2, 1), chend, 0, - COLOR_THEME_PRIMARY1); - channelStart->setPrefix(STR_CH); - channelStart->setSetValueHandler([=](int32_t newValue) { - g_model.trainerData.channelsStart = newValue - 1; - g_model.trainerData.channelsCount = - BLUETOOTH_TRAINER_CHANNELS - DEF_TRAINER_CHANNELS; - char chend[6]; - snprintf( - chend, sizeof(chend), "%s%d", STR_CH, - g_model.trainerData.channelsStart + BLUETOOTH_TRAINER_CHANNELS); - SET_DIRTY(); - btChannelEnd->setText(chend); - }); - - grid.nextLine(); - } else -#endif - if (g_model.trainerData.mode == TRAINER_MODE_SLAVE) { - // TODO: use generic ChannelRange -#define PPMCH_START_MIN 1 -#define PPMCH_START_MAX \ - (MAX_OUTPUT_CHANNELS - g_model.trainerData.channelsCount + 1 - \ - DEF_TRAINER_CHANNELS) -#define PPMCH_END_MIN (g_model.trainerData.channelsStart + MIN_TRAINER_CHANNELS) -#define PPMCH_END_MAX \ - min(g_model.trainerData.channelsStart + MAX_TRAINER_CHANNELS, \ - MAX_OUTPUT_CHANNELS) - - new StaticText(this, grid.getLabelSlot(true), STR_CHANNELRANGE, 0, - COLOR_THEME_PRIMARY1); - channelStart = new NumberEdit( - this, grid.getFieldSlot(2, 0), PPMCH_START_MIN, PPMCH_START_MAX, - GET_DEFAULT(1 + g_model.trainerData.channelsStart)); - channelEnd = new NumberEdit( - this, grid.getFieldSlot(2, 1), PPMCH_END_MIN, PPMCH_END_MAX, - GET_DEFAULT((g_model.trainerData.channelsStart + - DEF_TRAINER_CHANNELS + - g_model.trainerData.channelsCount))); - channelStart->setPrefix(STR_CH); - channelEnd->setPrefix(STR_CH); - channelStart->setSetValueHandler([=](int32_t newValue) { - g_model.trainerData.channelsStart = newValue - 1; - SET_DIRTY(); - channelEnd->setMin(PPMCH_END_MIN); - channelEnd->setMax(PPMCH_END_MAX); - channelEnd->invalidate(); - }); - channelEnd->setSetValueHandler([=](int32_t newValue) { - g_model.trainerData.channelsCount = - newValue - g_model.trainerData.channelsStart - DEF_TRAINER_CHANNELS; - SET_DIRTY(); - channelStart->setMax(PPMCH_START_MAX); - }); - - grid.nextLine(); - - // PPM frame - new StaticText(this, grid.getLabelSlot(true), STR_PPMFRAME, 0, - COLOR_THEME_PRIMARY1); - - // PPM frame length - // TODO: use PpmFrameSettings - auto edit = new NumberEdit( - this, grid.getFieldSlot(3, 0), 125, 35 * 5 + 225, - GET_DEFAULT(g_model.trainerData.frameLength * 5 + 225), - SET_VALUE(g_model.trainerData.frameLength, (newValue - 225) / 5), 0, - PREC1); - edit->setStep(5); - edit->setSuffix(STR_MS); - - // PPM frame delay - edit = new NumberEdit( - this, grid.getFieldSlot(3, 1), 100, 800, - GET_DEFAULT(g_model.trainerData.delay * 50 + 300), - SET_VALUE(g_model.trainerData.delay, (newValue - 300) / 50)); - edit->setStep(50); - edit->setSuffix(STR_US); - - // PPM Polarity - new Choice(this, grid.getFieldSlot(3, 2), STR_PPM_POL, 0, 1, - GET_SET_DEFAULT(g_model.trainerData.pulsePol)); - grid.nextLine(); - } - } -#if defined(HARDWARE_TOUCH) - new StaticText(this, grid.getLabelSlot(true)); -#endif - auto par = getParent(); - par->moveWindowsTop(top() + 1, adjustHeight()); - // par->adjustInnerHeight(); -} + auto td = &g_model.trainerData; + if (td->mode == TRAINER_MODE_OFF) return; #if defined(BLUETOOTH) -void TrainerModuleWindow::btDiscoverMenuItemChosen() -{ - if (bluetooth.state == BLUETOOTH_STATE_DISCOVER_SENT || - bluetooth.state == BLUETOOTH_STATE_DISCOVER_END) { - int index = btPopUpMenu->selection(); - if (index >= 0 && index < reusableBuffer.moduleSetup.bt.devicesCount) { - strncpy(bluetooth.distantAddr, - reusableBuffer.moduleSetup.bt.devices[index], LEN_BLUETOOTH_ADDR); - bluetooth.state = BLUETOOTH_STATE_BIND_REQUESTED; - SET_DIRTY(); - } + if (td->mode == TRAINER_MODE_MASTER_BLUETOOTH || + td->mode == TRAINER_MODE_SLAVE_BLUETOOTH) { + + auto bt = new BluetoothTrainerWindow(this); + if (td->mode == TRAINER_MODE_SLAVE_BLUETOOTH) + bt->setMaster(false); + + bt->refresh(); + // TODO: slave: channel range + } +#endif + + if (td->mode == TRAINER_MODE_SLAVE) { + + // Channel range + auto line = newLine(&grid); + new StaticText(line, rect_t{}, STR_CHANNELRANGE, 0, COLOR_THEME_PRIMARY1); + chRange = new TrainerChannelRange(line); + + // PPM frame + line = newLine(&grid); + new StaticText(line, rect_t{}, STR_PPMFRAME, 0, COLOR_THEME_PRIMARY1); + new PpmFrameSettings(line, grid, td); } } -void TrainerModuleWindow::btDiscoverMenuAddItem(const char *itm) -{ - if (btPopUpMenu != nullptr && !btPopUpMenu->deleted()) - btPopUpMenu->addLine( - itm, std::bind(&TrainerModuleWindow::btDiscoverMenuItemChosen, this)); -} -#endif - TrainerPage::TrainerPage() : Page(ICON_MODEL_SETUP) { - new StaticText(&header, - {PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT, - PAGE_LINE_HEIGHT}, - STR_TRAINER, 0, COLOR_THEME_PRIMARY2); + header.setTitle(STR_TRAINER); - FormGridLayout grid; + auto form = new FormGroup(&body, rect_t{}); + form->setFlexLayout(); + form->padAll(lv_dpx(8)); - new StaticText(&body, grid.getLabelSlot(true), STR_MODE, 0, - COLOR_THEME_PRIMARY1); + FlexGridLayout grid(col_dsc, row_dsc, 2); - auto trainerChoice = new Choice( - &body, grid.getFieldSlot(), STR_VTRAINERMODES, 0, TRAINER_MODE_MAX(), - GET_DEFAULT(g_model.trainerData.mode)); + auto line = form->newLine(&grid); + new StaticText(line, rect_t{}, STR_MODE, 0, COLOR_THEME_PRIMARY1); + auto trainerChoice = + new Choice(line, rect_t{}, STR_VTRAINERMODES, 0, TRAINER_MODE_MAX(), + GET_SET_DEFAULT(g_model.trainerData.mode)); trainerChoice->setAvailableHandler(isTrainerModeAvailable); - grid.nextLine(); - coord_t y = grid.getWindowHeight(); - coord_t h = body.height() - y; - coord_t w = body.width(); - - auto trainerModule = new TrainerModuleWindow(&body, {0, y, w, h}); + auto trainerModule = new TrainerModuleWindow(form); TrainerModuleData* tr = &g_model.trainerData; trainerChoice->setSetValueHandler([=](int32_t newValue) { + + //TODO: move the BT stuff somewhere else? #if defined(BLUETOOTH) memclear(bluetooth.distantAddr, sizeof(bluetooth.distantAddr)); bluetooth.state = BLUETOOTH_STATE_OFF;