Re-implemented BT trainer setup

This commit is contained in:
Raphael Coeffic
2022-06-25 15:37:11 +02:00
committed by Raphael Coeffic
parent 7b053c1c05
commit e76c7f95be
6 changed files with 274 additions and 243 deletions
-9
View File
@@ -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
+4
View File
@@ -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)
-1
View File
@@ -131,7 +131,6 @@ ModuleWindow::ModuleWindow(Window* parent, uint8_t moduleIdx) :
{
setFlexLayout();
updateModule();
// updateSubType();
}
void ModuleWindow::updateModule()
@@ -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<uint8_t>(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);
}
}
@@ -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();
};
+83 -233
View File
@@ -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<uint8_t>(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<uint8_t>(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<uint8_t>(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<TrainerModuleData>
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<TrainerModuleData>(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;