Compare commits
3
Commits
main
...
ground-station
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0d51db58b | ||
|
|
f067d8b887 | ||
|
|
b45295bbb4 |
@@ -259,6 +259,7 @@ enum UartModes {
|
|||||||
UART_MODE_DEBUG,
|
UART_MODE_DEBUG,
|
||||||
UART_MODE_SPACEMOUSE,
|
UART_MODE_SPACEMOUSE,
|
||||||
UART_MODE_EXT_MODULE,
|
UART_MODE_EXT_MODULE,
|
||||||
|
UART_MODE_GROUND_STATION,
|
||||||
UART_MODE_COUNT SKIP,
|
UART_MODE_COUNT SKIP,
|
||||||
UART_MODE_MAX SKIP = UART_MODE_COUNT-1
|
UART_MODE_MAX SKIP = UART_MODE_COUNT-1
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#include "edgetx.h"
|
||||||
|
#include "groundstation.h"
|
||||||
|
#include "os/timer.h"
|
||||||
|
#include "tasks/mixer_task.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr uint8_t kMagic0 = 'G';
|
||||||
|
constexpr uint8_t kMagic1 = 'S';
|
||||||
|
constexpr uint8_t kVersion = 1;
|
||||||
|
constexpr uint8_t kTypeRCChannels = 1;
|
||||||
|
constexpr uint8_t kChannels = 16;
|
||||||
|
constexpr uint8_t kFrameSize = 10 + kChannels * 2 + 2;
|
||||||
|
constexpr uint32_t kPeriodMs = 40; // 25 Hz
|
||||||
|
|
||||||
|
const etx_serial_driver_t *driver = nullptr;
|
||||||
|
void *driverCtx = nullptr;
|
||||||
|
timer_handle_t timer = TIMER_INITIALIZER;
|
||||||
|
uint16_t sequence = 0;
|
||||||
|
|
||||||
|
uint16_t crc16(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
uint16_t crc = 0xffff;
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
crc ^= uint16_t(data[i]) << 8;
|
||||||
|
for (uint8_t bit = 0; bit < 8; ++bit)
|
||||||
|
crc = (crc & 0x8000) ? uint16_t((crc << 1) ^ 0x1021) : uint16_t(crc << 1);
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendRCFrame()
|
||||||
|
{
|
||||||
|
if (!driver || !driver->sendByte || !mixerTaskRunning()) return;
|
||||||
|
|
||||||
|
uint8_t frame[kFrameSize] = { kMagic0, kMagic1, kVersion, kTypeRCChannels };
|
||||||
|
frame[4] = sequence & 0xff;
|
||||||
|
frame[5] = sequence >> 8;
|
||||||
|
const uint32_t timestamp = get_tmr10ms() * 10;
|
||||||
|
frame[6] = timestamp & 0xff;
|
||||||
|
frame[7] = (timestamp >> 8) & 0xff;
|
||||||
|
frame[8] = (timestamp >> 16) & 0xff;
|
||||||
|
frame[9] = (timestamp >> 24) & 0xff;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < kChannels; ++i) {
|
||||||
|
const uint16_t value = channelOutputs[i];
|
||||||
|
frame[10 + i * 2] = value & 0xff;
|
||||||
|
frame[11 + i * 2] = value >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t crc = crc16(frame, kFrameSize - 2);
|
||||||
|
frame[kFrameSize - 2] = crc & 0xff;
|
||||||
|
frame[kFrameSize - 1] = crc >> 8;
|
||||||
|
for (uint8_t byte : frame) driver->sendByte(driverCtx, byte);
|
||||||
|
++sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
void timerCb(timer_handle_t *)
|
||||||
|
{
|
||||||
|
// Drain input now so the future profile-control parser has a single owner.
|
||||||
|
// The baseline intentionally does not act on received bytes yet.
|
||||||
|
if (driver && driver->getByte) {
|
||||||
|
uint8_t ignored;
|
||||||
|
while (driver->getByte(driverCtx, &ignored)) {}
|
||||||
|
}
|
||||||
|
sendRCFrame();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void groundstationSetSerialDriver(void *ctx, const etx_serial_driver_t *drv)
|
||||||
|
{
|
||||||
|
driverCtx = ctx;
|
||||||
|
driver = drv;
|
||||||
|
sequence = 0;
|
||||||
|
if (!timer_is_created(&timer))
|
||||||
|
timer_create(&timer, timerCb, "gstation", kPeriodMs, true);
|
||||||
|
timer_start(&timer);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "hal/serial_port.h"
|
||||||
|
|
||||||
|
void groundstationSetSerialDriver(void *ctx, const etx_serial_driver_t *drv);
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
#if !defined(BOOT)
|
#if !defined(BOOT)
|
||||||
#include "edgetx.h"
|
#include "edgetx.h"
|
||||||
|
#include "groundstation.h"
|
||||||
#include "lua/lua_api.h"
|
#include "lua/lua_api.h"
|
||||||
#else
|
#else
|
||||||
#include "dataconstants.h"
|
#include "dataconstants.h"
|
||||||
@@ -257,6 +258,10 @@ static void serialSetCallBacks(int mode, void* ctx, const etx_serial_port_t* por
|
|||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
case UART_MODE_GROUND_STATION:
|
||||||
|
groundstationSetSerialDriver(ctx, drv);
|
||||||
|
break;
|
||||||
|
|
||||||
#if defined(CONFIGURABLE_MODULE_PORT) and !defined(BOOT)
|
#if defined(CONFIGURABLE_MODULE_PORT) and !defined(BOOT)
|
||||||
case UART_MODE_EXT_MODULE:
|
case UART_MODE_EXT_MODULE:
|
||||||
if (port && !ctx) { // de-init
|
if (port && !ctx) { // de-init
|
||||||
@@ -354,6 +359,11 @@ static void serialSetupPort(int mode, etx_serial_init& params)
|
|||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
case UART_MODE_GROUND_STATION:
|
||||||
|
params.baudrate = 115200;
|
||||||
|
params.direction = ETX_Dir_TX_RX;
|
||||||
|
break;
|
||||||
|
|
||||||
#endif // BOOT
|
#endif // BOOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2683,6 +2683,7 @@ static const struct YamlIdStr enum_UartModes[] = {
|
|||||||
{ UART_MODE_DEBUG, "DEBUG" },
|
{ UART_MODE_DEBUG, "DEBUG" },
|
||||||
{ UART_MODE_SPACEMOUSE, "SPACEMOUSE" },
|
{ UART_MODE_SPACEMOUSE, "SPACEMOUSE" },
|
||||||
{ UART_MODE_EXT_MODULE, "EXT_MODULE" },
|
{ UART_MODE_EXT_MODULE, "EXT_MODULE" },
|
||||||
|
{ UART_MODE_GROUND_STATION, "GROUND_STATION" },
|
||||||
{ 0, NULL }
|
{ 0, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ elseif(PCB STREQUAL X7)
|
|||||||
set(PXX2 ON)
|
set(PXX2 ON)
|
||||||
set(ENABLE_SERIAL_PASSTHROUGH ON CACHE BOOL "Enable serial passthrough")
|
set(ENABLE_SERIAL_PASSTHROUGH ON CACHE BOOL "Enable serial passthrough")
|
||||||
set(FLYSKY_GIMBAL YES)
|
set(FLYSKY_GIMBAL YES)
|
||||||
|
set(SRC ${SRC} groundstation.cpp)
|
||||||
if (NOT BLUETOOTH)
|
if (NOT BLUETOOTH)
|
||||||
set(AUX_SERIAL ON)
|
set(AUX_SERIAL ON)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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 "gtests.h"
|
||||||
|
|
||||||
|
#include "storage/yaml/yaml_tree_walker.h"
|
||||||
|
#include "storage/yaml/yaml_parser.h"
|
||||||
|
#include "storage/yaml/yaml_datastructs.h"
|
||||||
|
|
||||||
|
static bool yamlStringWriter(void* opaque, const char* str, size_t len)
|
||||||
|
{
|
||||||
|
auto* out = reinterpret_cast<std::string*>(opaque);
|
||||||
|
out->append(str, len);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string serialPortToYaml()
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
YamlTreeWalker tree;
|
||||||
|
tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral);
|
||||||
|
EXPECT_TRUE(tree.generate(yamlStringWriter, &out));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadRadioYamlStr(const char* str)
|
||||||
|
{
|
||||||
|
YamlTreeWalker tree;
|
||||||
|
tree.reset(get_radiodata_nodes(), (uint8_t*)&g_eeGeneral);
|
||||||
|
|
||||||
|
YamlParser yp;
|
||||||
|
yp.init(YamlTreeWalker::get_parser_calls(), &tree);
|
||||||
|
|
||||||
|
yp.parse(str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getPortMode(uint8_t port_nr)
|
||||||
|
{
|
||||||
|
uint32_t cfg = g_eeGeneral.serialPort;
|
||||||
|
cfg >>= (port_nr * SERIAL_CONF_BITS_PER_PORT);
|
||||||
|
return cfg & SERIAL_CONF_MODE_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SerialModeTest : public EdgeTxTest {};
|
||||||
|
|
||||||
|
// Regression: the Ground Station mode on AUX1 was dropped when the radio
|
||||||
|
// settings YAML was written, so the selection reset to OFF after a reboot.
|
||||||
|
TEST(SerialModeTest, GroundStationAux1ModeSurvivesSaveReload)
|
||||||
|
{
|
||||||
|
uint32_t savedSerialPort = g_eeGeneral.serialPort;
|
||||||
|
|
||||||
|
serialSetMode(SP_AUX1, UART_MODE_GROUND_STATION);
|
||||||
|
|
||||||
|
std::string yaml = serialPortToYaml();
|
||||||
|
EXPECT_NE(yaml.find("mode: GROUND_STATION"), std::string::npos)
|
||||||
|
<< yaml;
|
||||||
|
|
||||||
|
// simulate a reboot: defaults are loaded, then the YAML file is read back
|
||||||
|
g_eeGeneral.serialPort = 0;
|
||||||
|
loadRadioYamlStr(yaml.c_str());
|
||||||
|
EXPECT_EQ(getPortMode(SP_AUX1), UART_MODE_GROUND_STATION);
|
||||||
|
EXPECT_EQ(getPortMode(SP_AUX2), (int)UART_MODE_NONE);
|
||||||
|
|
||||||
|
g_eeGeneral.serialPort = savedSerialPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mode table change must not disturb the other AUX1 modes.
|
||||||
|
TEST(SerialModeTest, AllAux1ModesSurviveSaveReload)
|
||||||
|
{
|
||||||
|
uint32_t savedSerialPort = g_eeGeneral.serialPort;
|
||||||
|
|
||||||
|
for (int mode = 0; mode <= UART_MODE_MAX; mode++) {
|
||||||
|
g_eeGeneral.serialPort = 0;
|
||||||
|
serialSetMode(SP_AUX1, mode);
|
||||||
|
|
||||||
|
std::string yaml = serialPortToYaml();
|
||||||
|
|
||||||
|
g_eeGeneral.serialPort = 0;
|
||||||
|
loadRadioYamlStr(yaml.c_str());
|
||||||
|
EXPECT_EQ(getPortMode(SP_AUX1), mode) << "mode " << mode << ", yaml:" << yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_eeGeneral.serialPort = savedSerialPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy attribute (pre serialPort array) must still be migrated on load.
|
||||||
|
TEST(SerialModeTest, LegacyAuxSerialModeAttrStillMigrates)
|
||||||
|
{
|
||||||
|
static const char _legacy[] = "auxSerialMode: MODE_LUA\r\n";
|
||||||
|
loadRadioYamlStr(_legacy);
|
||||||
|
EXPECT_EQ(getPortMode(SP_AUX1), (int)UART_MODE_LUA);
|
||||||
|
}
|
||||||
@@ -144,6 +144,7 @@
|
|||||||
#define TR_AUX_SERIAL_MODES_9 "Debug"
|
#define TR_AUX_SERIAL_MODES_9 "Debug"
|
||||||
#define TR_AUX_SERIAL_MODES_10 "SpaceMouse"
|
#define TR_AUX_SERIAL_MODES_10 "SpaceMouse"
|
||||||
#define TR_AUX_SERIAL_MODES_11 "External module"
|
#define TR_AUX_SERIAL_MODES_11 "External module"
|
||||||
|
#define TR_AUX_SERIAL_MODES_12 "Ground Station"
|
||||||
#define TR_SWTYPES_1 "None"
|
#define TR_SWTYPES_1 "None"
|
||||||
#define TR_SWTYPES_2 "Toggle"
|
#define TR_SWTYPES_2 "Toggle"
|
||||||
#define TR_SWTYPES_3 "2POS"
|
#define TR_SWTYPES_3 "2POS"
|
||||||
|
|||||||
@@ -145,6 +145,7 @@
|
|||||||
#define TR_AUX_SERIAL_MODES_9 "Debug"
|
#define TR_AUX_SERIAL_MODES_9 "Debug"
|
||||||
#define TR_AUX_SERIAL_MODES_10 "SpaceMouse"
|
#define TR_AUX_SERIAL_MODES_10 "SpaceMouse"
|
||||||
#define TR_AUX_SERIAL_MODES_11 "Moduł zewnętrzny"
|
#define TR_AUX_SERIAL_MODES_11 "Moduł zewnętrzny"
|
||||||
|
#define TR_AUX_SERIAL_MODES_12 "Ground Station"
|
||||||
#define TR_SWTYPES_1 "Brak"
|
#define TR_SWTYPES_1 "Brak"
|
||||||
#define TR_SWTYPES_2 "Chwil."
|
#define TR_SWTYPES_2 "Chwil."
|
||||||
#define TR_SWTYPES_3 "2POZ"
|
#define TR_SWTYPES_3 "2POZ"
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
|
|
||||||
#define TR_VCELLINDEX SA11(TR_VCELLINDEX)
|
#define TR_VCELLINDEX SA11(TR_VCELLINDEX)
|
||||||
|
|
||||||
#define TR_AUX_SERIAL_MODES SA11(TR_AUX_SERIAL_MODES)
|
#define TR_AUX_SERIAL_MODES SA11(TR_AUX_SERIAL_MODES), "Ground Station"
|
||||||
|
|
||||||
#define TR_VFORMULAS SA9(TR_VFORMULAS)
|
#define TR_VFORMULAS SA9(TR_VFORMULAS)
|
||||||
#define TR_FS_COLOR_LIST SA9(TR_FS_COLOR_LIST)
|
#define TR_FS_COLOR_LIST SA9(TR_FS_COLOR_LIST)
|
||||||
|
|||||||
Reference in New Issue
Block a user