Ghost menu basic handling (#8108)

This commit is contained in:
3djc
2020-11-22 08:20:41 +01:00
committed by GitHub
parent c84e2b092b
commit 88d480e453
14 changed files with 454 additions and 6 deletions
+1
View File
@@ -74,6 +74,7 @@ void menuRadioTools(event_t event);
void menuRadioSpectrumAnalyser(event_t event);
void menuRadioPowerMeter(event_t event);
void menuRadioCalibration(event_t event);
void menuGhostModuleConfig(event_t event);
static const MenuHandlerFunc menuTabGeneral[MENU_RADIO_PAGES_COUNT] = {
#if defined(RADIO_TOOLS)
+1
View File
@@ -76,6 +76,7 @@ void menuRadioTools(event_t event);
void menuRadioCalibration(event_t event);
void menuRadioSpectrumAnalyser(event_t event);
void menuRadioPowerMeter(event_t event);
void menuGhostModuleConfig(event_t event);
extern const MenuHandlerFunc menuTabGeneral[MENU_RADIO_PAGES_COUNT];
+4
View File
@@ -79,6 +79,10 @@ if(PXX2 OR MULTIMODULE)
add_gui_src(radio_spectrum_analyser.cpp)
endif()
if(GHOST)
add_gui_src(radio_ghost_module_config.cpp)
endif()
if(LUA STREQUAL YES)
set(SRC ${SRC} lua/widgets.cpp)
# TODO set(GUI_SRC ${GUI_SRC} model_custom_scripts.cpp)
+1 -1
View File
@@ -251,7 +251,7 @@ bool menuAboutView(event_t event);
bool menuMainViewChannelsMonitor(event_t event);
bool menuTextView(event_t event);
bool menuScreensTheme(event_t event);
bool menuRadioSpectrumAnalyser(event_t event);
bool menuRadioGhostModuleConfig(event_t event);
bool menuRadioPowerMeter(event_t event);
extern int8_t checkIncDec_Ret; // global helper vars
@@ -0,0 +1,150 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* 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 "opentx.h"
#include "radio_ghost_module_config.h"
class GhostModuleConfigWindow: public Window
{
public:
GhostModuleConfigWindow(Window * parent, const rect_t & rect) :
Window(parent, rect, FORWARD_SCROLL | FORM_FORWARD_FOCUS | REFRESH_ALWAYS)
{
setFocus(SET_FOCUS_DEFAULT);
}
void paint(BitmapBuffer * dc) override
{
constexpr coord_t xOffset = 140;
constexpr coord_t xOffset2 = 260;
constexpr coord_t yOffset = 20;
constexpr coord_t lineSpacing = 25;
for (uint8_t line = 0; line < GHST_MENU_LINES; line++) {
if (reusableBuffer.ghostMenu.line[line].splitLine) {
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_LABEL_SELECT) {
dc->drawSolidFilledRect(xOffset, yOffset + line * lineSpacing, getTextWidth(reusableBuffer.ghostMenu.line[line].menuText, 0,FONT(L)), getFontHeight(FONT(L)), FONT(L) | FOCUS_BGCOLOR);
dc->drawText(xOffset, yOffset + line * lineSpacing, reusableBuffer.ghostMenu.line[line].menuText, FONT(L) | DEFAULT_BGCOLOR);
}
else {
dc->drawText(xOffset, yOffset + line * lineSpacing, reusableBuffer.ghostMenu.line[line].menuText, FONT(L));
}
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_VALUE_SELECT) {
dc->drawSolidFilledRect(xOffset, yOffset + line * lineSpacing, getTextWidth( &reusableBuffer.ghostMenu.line[line].menuText[reusableBuffer.ghostMenu.line[line].splitLine], 0,FONT(L)), getFontHeight(0), FOCUS_BGCOLOR);
dc->drawText(xOffset, yOffset + line * lineSpacing, &reusableBuffer.ghostMenu.line[line].menuText[reusableBuffer.ghostMenu.line[line].splitLine], FONT(L) | DEFAULT_BGCOLOR);
}
else {
dc->drawText(xOffset2, yOffset + line * lineSpacing, &reusableBuffer.ghostMenu.line[line].menuText[reusableBuffer.ghostMenu.line[line].splitLine], FONT(L) | DEFAULT_COLOR);
}
}
else {
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_LABEL_SELECT) {
dc->drawSolidFilledRect(xOffset, yOffset + line * lineSpacing, getTextWidth(reusableBuffer.ghostMenu.line[line].menuText, 0, FONT(L)), getFontHeight(FONT(L)), FOCUS_BGCOLOR);
dc->drawText(xOffset, yOffset + line * lineSpacing, reusableBuffer.ghostMenu.line[line].menuText, FONT(L) | DEFAULT_BGCOLOR);
}
else if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_VALUE_EDIT) {
if (BLINK_ON_PHASE) {
dc->drawText(xOffset, yOffset + line * lineSpacing, reusableBuffer.ghostMenu.line[line].menuText, FONT(L));
}
}
else {
dc->drawText(xOffset, yOffset + line * lineSpacing, reusableBuffer.ghostMenu.line[line].menuText, FONT(L) | DEFAULT_COLOR);
}
}
}
}
protected:
};
RadioGhostModuleConfig::RadioGhostModuleConfig(uint8_t moduleIdx) :
Page(ICON_RADIO_TOOLS),
moduleIdx(moduleIdx)
{
init();
buildHeader(&header);
buildBody(&body);
setFocus(SET_FOCUS_DEFAULT);
}
void RadioGhostModuleConfig::buildHeader(Window * window)
{
new StaticText(window, {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + 10, LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, "GHOST MODULE", 0, MENU_COLOR);
}
void RadioGhostModuleConfig::buildBody(FormWindow * window)
{
new GhostModuleConfigWindow(window, {0, 0, LCD_W, LCD_H - MENU_HEADER_HEIGHT - 5});
}
#if defined(HARDWARE_KEYS)
void RadioGhostModuleConfig::onEvent(event_t event)
{
switch (event) {
#if defined(ROTARY_ENCODER_NAVIGATION)
case EVT_ROTARY_LEFT:
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYUP;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
#endif
#if defined(ROTARY_ENCODER_NAVIGATION)
case EVT_ROTARY_RIGHT:
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYDOWN;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
#endif
case EVT_KEY_FIRST(KEY_ENTER):
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYPRESS;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
case EVT_KEY_BREAK(KEY_EXIT):
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYLEFT;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
case EVT_KEY_LONG(KEY_EXIT):
menuVerticalOffset = 0;
memclear(&reusableBuffer.ghostMenu, sizeof(reusableBuffer.ghostMenu));
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_NONE;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_CLOSE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
RTOS_WAIT_MS(10);
deleteLater();
break;
}
Page::onEvent(event);
}
#endif
void RadioGhostModuleConfig::init()
{
memclear(&reusableBuffer.ghostMenu, sizeof(reusableBuffer.ghostMenu));
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_NONE;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_OPEN;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
}
@@ -0,0 +1,41 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* 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 "page.h"
class RadioGhostModuleConfig: public Page
{
public:
explicit RadioGhostModuleConfig(uint8_t moduleIdx);
#if defined(HARDWARE_KEYS)
void onEvent(event_t event) override;
#endif
protected:
uint8_t moduleIdx;
void buildHeader(Window * window);
void buildBody(FormWindow * window);
void init();
};
+12
View File
@@ -21,6 +21,7 @@
#include <algorithm>
#include "radio_tools.h"
#include "radio_spectrum_analyser.h"
#include "radio_ghost_module_config.h"
#include "opentx.h"
#include "libopenui.h"
#include "lua/lua_api.h"
@@ -174,5 +175,16 @@ void RadioToolsPage::rebuild(FormWindow * window)
}
#endif
#if defined(GHOST)
if (isModuleGhost(EXTERNAL_MODULE)) {
new StaticText(window, grid.getLabelSlot(), "ghost", BUTTON_BACKGROUND, CENTERED);
new TextButton(window, grid.getFieldSlot(1), "Ghost module config", [=]() -> uint8_t {
new RadioGhostModuleConfig(EXTERNAL_MODULE);
return 0;
}, 0);
grid.nextLine();
}
#endif
window->setInnerHeight(grid.getWindowHeight());
}
@@ -42,6 +42,13 @@ if(PXX2)
)
endif()
if(GHOST)
set(GUI_SRC
${GUI_SRC}
../common/stdlcd/radio_ghost_menu.cpp
)
endif()
if(PXX2 OR MULTIMODULE)
set(GUI_SRC
${GUI_SRC}
@@ -0,0 +1,110 @@
/*
* Copyright (C) OpenTX
*
* Based on code named
* 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 "opentx.h"
void menuGhostModuleConfig(event_t event)
{
switch (event) {
case EVT_ENTRY:
memclear(&reusableBuffer.ghostMenu, sizeof(reusableBuffer.ghostMenu));
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_NONE;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_OPEN;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
#if defined(ROTARY_ENCODER_NAVIGATION)
case EVT_ROTARY_LEFT:
#elif defined(KEYS_GPIO_REG_UP)
case EVT_KEY_BREAK(KEY_UP):
#elif defined(KEYS_GPIO_REG_PLUS)
case EVT_KEY_BREAK(KEY_PLUS):
#endif
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYUP;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
#if defined(ROTARY_ENCODER_NAVIGATION)
case EVT_ROTARY_RIGHT:
#elif defined(KEYS_GPIO_REG_DOWN)
case EVT_KEY_BREAK(KEY_DOWN):
#elif defined(KEYS_GPIO_REG_MINUS)
case EVT_KEY_BREAK(KEY_MINUS):
#endif
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYDOWN;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
case EVT_KEY_FIRST(KEY_ENTER):
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYPRESS;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
case EVT_KEY_BREAK(KEY_EXIT):
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_JOYLEFT;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_NONE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
break;
case EVT_KEY_LONG(KEY_EXIT):
menuVerticalOffset = 0;
memclear(&reusableBuffer.ghostMenu, sizeof(reusableBuffer.ghostMenu));
reusableBuffer.ghostMenu.buttonAction = GHST_BTN_NONE;
reusableBuffer.ghostMenu.menuAction = GHST_MENU_CTRL_CLOSE;
moduleState[EXTERNAL_MODULE].counter = GHST_MENU_CONTROL;
RTOS_WAIT_MS(10);
popMenu();
break;
}
if (reusableBuffer.ghostMenu.menuAction == GHST_MENU_CTRL_CLOSE) {
popMenu();
}
constexpr coord_t xOffset = LCD_W / 3 - 15;
constexpr coord_t xOffset2 = LCD_W / 3 + LCD_W / 4;
constexpr coord_t yOffset = 6;
LcdFlags flags = 0;
for (uint8_t line = 0; line < GHST_MENU_LINES; line++) {
flags = 0;
if (reusableBuffer.ghostMenu.line[line].splitLine) {
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_LABEL_SELECT)
flags = INVERS;
lcdDrawText(xOffset, yOffset + line * FH, reusableBuffer.ghostMenu.line[line].menuText, flags);
flags = 0;
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_VALUE_SELECT)
flags |= INVERS;
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_VALUE_EDIT)
flags |= BLINK;
lcdDrawText(xOffset2, yOffset + line * FH, &reusableBuffer.ghostMenu.line[line].menuText[reusableBuffer.ghostMenu.line[line].splitLine], flags);
}
else {
if (reusableBuffer.ghostMenu.line[line].lineFlags & GHST_LINE_FLAGS_LABEL_SELECT)
flags = INVERS;
lcdDrawText(xOffset, yOffset + line * FH, reusableBuffer.ghostMenu.line[line].menuText, flags);
}
}
}
@@ -129,6 +129,10 @@ void menuRadioTools(event_t event)
addRadioModuleTool(index++, STR_POWER_METER_EXT, menuRadioPowerMeter, EXTERNAL_MODULE);
#endif
#if defined(GHOST)
if (isModuleGhost(EXTERNAL_MODULE))
addRadioModuleTool(index++, "Ghost Menu", menuGhostModuleConfig, EXTERNAL_MODULE);
#endif
if (index == 0) {
lcdDrawCenteredText(LCD_H/2, STR_NO_TOOLS);
}
+8
View File
@@ -1112,6 +1112,14 @@ union ReusableBuffer
uint8_t moduleOFF;
} spectrumAnalyser;
#if defined(GHOST)
struct {
GhostMenuData line[GHST_MENU_LINES + 1];
uint8_t menuAction;
uint8_t buttonAction;
} ghostMenu;
#endif
struct {
uint32_t freq;
int16_t power;
+30 -2
View File
@@ -20,6 +20,31 @@
#include "opentx.h"
uint8_t createGhostMenuControlFrame(uint8_t * frame, int16_t * pulses)
{
moduleState[EXTERNAL_MODULE].counter = GHST_FRAME_CHANNEL;
uint8_t * buf = frame;
#if SPORT_MAX_BAUDRATE < 400000
*buf++ = g_eeGeneral.telemetryBaudrate == GHST_TELEMETRY_RATE_400K ? GHST_ADDR_MODULE_SYM : GHST_ADDR_MODULE_ASYM;
#else
*buf++ = GHST_ADDR_MODULE_SYM;
#endif
*buf++ = GHST_UL_RC_CHANS_SIZE;
uint8_t * crc_start = buf;
*buf++ = GHST_UL_MENU_CTRL;
*buf++ = reusableBuffer.ghostMenu.buttonAction; // Joystick states, Up, Down, Left, Right, Press
*buf++ = reusableBuffer.ghostMenu.menuAction; // menu control, open, close, etc.
for (uint8_t i = 0; i < 8; i++)
*buf++ = 0; // padding to make this the same size as the pulses packet
*buf++ = crc8(crc_start, GHST_UL_RC_CHANS_SIZE - 1);
return buf - frame;
}
// Range for pulses (channels output) is [-1024:+1024]
uint8_t createGhostChannelsFrame(uint8_t * frame, int16_t * pulses)
{
@@ -89,6 +114,9 @@ void setupPulsesGhost()
{
if (telemetryProtocol == PROTOCOL_TELEMETRY_GHOST) {
uint8_t * pulses = extmodulePulsesData.ghost.pulses;
extmodulePulsesData.ghost.length = createGhostChannelsFrame(pulses, &channelOutputs[g_model.moduleData[EXTERNAL_MODULE].channelsStart]);
if (moduleState[EXTERNAL_MODULE].counter == GHST_MENU_CONTROL)
extmodulePulsesData.ghost.length = createGhostMenuControlFrame(pulses, &channelOutputs[g_model.moduleData[EXTERNAL_MODULE].channelsStart]);
else
extmodulePulsesData.ghost.length = createGhostChannelsFrame(pulses, &channelOutputs[g_model.moduleData[EXTERNAL_MODULE].channelsStart]);
}
}
}
+25
View File
@@ -175,6 +175,31 @@ void processGhostTelemetryFrame()
processGhostTelemetryValueString(sensor, vtxBandString);
break;
}
case GHST_DL_MENU_DESC:
{
GhostMenuFrame * packet;
GhostMenuData * lineData;
packet = (GhostMenuFrame * ) telemetryRxBuffer;
lineData = (GhostMenuData *) &reusableBuffer.ghostMenu.line[packet->lineIndex];
lineData->splitLine = 0;
reusableBuffer.ghostMenu.menuAction = packet->menuFlags;
lineData->lineFlags = packet->lineFlags;
for (uint8_t i = 0; i < GHST_MENU_CHARS; i++) {
if (packet->menuText[i] == 0x7C) {
lineData->menuText[i] = '\0';
lineData->splitLine = i + 1;
}
else {
lineData->menuText[i] = packet->menuText[i];
}
}
lineData->menuText[GHST_MENU_CHARS] = '\0';
if (packet->lineIndex == GHST_MENU_LINES - 1)
lineData->menuUpdateNeeded = true;
break;
}
}
}
+60 -3
View File
@@ -18,8 +18,7 @@
* GNU General Public License for more details.
*/
#ifndef _GHOST_H_
#define _GHOST_H_
#pragma once
#include <inttypes.h>
#include "dataconstants.h"
@@ -38,10 +37,12 @@
#define GHST_UL_RC_CHANS_HS4_9TO12 0x11 // High Speed 4 channel (12 bits), plus CH9-12 (8 bits)
#define GHST_UL_RC_CHANS_HS4_13TO16 0x12 // High Speed 4 channel (12 bits), plus CH13-16 (8 bits)
#define GHST_UL_RC_CHANS_SIZE 12 // 1 (type) + 10 (data) + 1 (crc)
#define GHST_UL_MENU_CTRL 0x13
#define GHST_DL_OPENTX_SYNC 0x20
#define GHST_DL_LINK_STAT 0x21
#define GHST_DL_VTX_STAT 0x22
#define GHST_DL_MENU_DESC 0x24
#define GHST_RC_CTR_VAL_12BIT 0x7C0 // 0x3e0 << 1
#define GHST_RC_CTR_VAL_8BIT 0x7C
@@ -113,4 +114,60 @@ enum GhostTelemetryBaudrates
#define GHOST_BAUDRATE 400000
#define GHOST_PERIOD 4
#endif // _GHOST_H_
enum GhostLineFlags
{
GHST_LINE_FLAGS_NONE = 0x00,
GHST_LINE_FLAGS_LABEL_SELECT = 0x01,
GHST_LINE_FLAGS_VALUE_SELECT = 0x02,
GHST_LINE_FLAGS_VALUE_EDIT = 0x04,
};
enum GhostButtons
{
GHST_BTN_NONE = 0x00,
GHST_BTN_JOYPRESS = 0X01,
GHST_BTN_JOYUP = 0X02,
GHST_BTN_JOYDOWN = 0X04,
GHST_BTN_JOYLEFT = 0X08,
GHST_BTN_JOYRIGHT = 0X10,
GHST_BTN_BIND = 0X20 // future, for no-UI Ghost
};
enum GhostMenuControl
{
GHST_MENU_CTRL_NONE = 0X00,
GHST_MENU_CTRL_OPEN = 0X01,
GHST_MENU_CTRL_CLOSE = 0X02,
GHST_MENU_CTRL_REDRAW = 0X04,
};
enum GhostFrames
{
GHST_FRAME_CHANNEL,
GHST_MENU_CONTROL
};
constexpr uint8_t GHST_MENU_LINES = 6;
constexpr uint8_t GHST_MENU_CHARS = 20;
// GHST_DL_MENU_DESC (27 bytes)
struct GhostMenuFrame
{
uint8_t address;
uint8_t length ;
uint8_t packetId;
uint8_t menuFlags; // GHST_MENU_CTRL
uint8_t lineFlags; // Carat states, Inverse, Bold for each of Menu Label, and Value
uint8_t lineIndex; // 0 = first line
unsigned char menuText[GHST_MENU_CHARS];
uint8_t crc;
};
struct GhostMenuData
{
uint8_t menuFlags; // Update Line, Clear Menu, etc.
uint8_t lineFlags; // Carat states, Inverse, Bold for each of Menu Label, and Value
uint8_t splitLine; // Store beginning of Value substring
char menuText[GHST_MENU_CHARS + 1];
uint8_t menuUpdateNeeded;
};