feat(v16): add CI1302 voice control (VGR/VFL, motion control, menu navigation) (#7501)
Co-authored-by: Peter Feerick <5500713+pfeerick@users.noreply.github.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Peter Feerick
Claude Sonnet 5
parent
f364c6ef63
commit
4aa9a258ee
@@ -135,7 +135,8 @@ void BoardJson::afterLoadFixups(Board::Type board, InputsTable * inputs, Switche
|
||||
}
|
||||
|
||||
// Flex switches are not listed in json file for these radios
|
||||
int count = IS_RADIOMASTER_TX16S(board) || IS_RADIOMASTER_MT12(board) ? 2 : 0;
|
||||
int count = IS_RADIOMASTER_TX16S(board) || IS_RADIOMASTER_MT12(board) ||
|
||||
IS_HELLORADIOSKY_V16(board) ? 2 : 0;
|
||||
|
||||
for (int i = 1; i <= count; i++) {
|
||||
QString tag = QString("FL%1").arg(i);
|
||||
@@ -146,6 +147,26 @@ void BoardJson::afterLoadFixups(Board::Type board, InputsTable * inputs, Switche
|
||||
switches->insert(switches->end(), defn);
|
||||
}
|
||||
}
|
||||
|
||||
// CI1302 voice control virtual switches are not listed in json file
|
||||
// They must follow the flex switches above: firmware indexes them at
|
||||
// switchGetMaxSwitches() + MAX_FLEX_SWITCHES
|
||||
// VGR (voice gear) is a 2POS switch, VFL (voice flap) is 3POS -- see
|
||||
// isTwoPosVoiceSwitch() in radio/src/drivers/CI1302.cpp
|
||||
if (IS_HELLORADIOSKY_V16(board)) {
|
||||
for (const auto &sw : {std::make_pair(QStringLiteral("VGR"), Board::SWITCH_2POS),
|
||||
std::make_pair(QStringLiteral("VFL"), Board::SWITCH_3POS)}) {
|
||||
const QString &tag = sw.first;
|
||||
if (getSwitchIndex(switches, tag, Board::LVT_TAG) < 0) {
|
||||
SwitchDefn defn;
|
||||
defn.tag = tag.toStdString();
|
||||
defn.name = defn.tag;
|
||||
defn.type = sw.second;
|
||||
defn.dflt = sw.second;
|
||||
switches->insert(switches->end(), defn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called from Boards::getCapability if no capability match
|
||||
|
||||
@@ -180,7 +180,10 @@ RawSwitch YamlRawSwitchDecode(const std::string& sw_str)
|
||||
val[val_len - 1] >= '0' && val[val_len - 1] <= '2') ||
|
||||
(val_len >= 3 && val[0] == 'S' &&
|
||||
val[1] >= 'A' && val[1] <= 'Z' &&
|
||||
val[2] >= '0' && val[2] <= '2')) {
|
||||
val[2] >= '0' && val[2] <= '2') ||
|
||||
(val_len == 4 &&
|
||||
(sw_str_tmp.substr(0, 3) == "VGR" || sw_str_tmp.substr(0, 3) == "VFL") &&
|
||||
val[3] >= '0' && val[3] <= '2')) {
|
||||
|
||||
std::string tmp = sw_str_tmp.substr(0, val_len - 1);
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ QString RawSource::toString(const ModelData * model, const GeneralSettings * con
|
||||
};
|
||||
|
||||
static const QString special[] = {
|
||||
"", tr("Batt"), tr("Time"), tr("GPS"), tr("Reserved1"), tr("Reserved2"), tr("Reserved3"), tr("Reserved4")
|
||||
"", tr("Batt"), tr("Time"), tr("GPS"), tr("VGR"), tr("VFL"), tr("Reserved3"), tr("Reserved4")
|
||||
};
|
||||
|
||||
static const QString rotary[] = { "", tr("REa"), tr("REb") };
|
||||
@@ -355,8 +355,21 @@ bool RawSource::isAvailable(const ModelData * const model,
|
||||
if (type == SOURCE_TYPE_SWITCH && abs(index) > b.getCapability(Board::Switches))
|
||||
return false;
|
||||
|
||||
if (type == SOURCE_TYPE_SPECIAL && abs(index) >= SOURCE_TYPE_SPECIAL_FIRST_RESERVED)
|
||||
return false;
|
||||
if (type == SOURCE_TYPE_SPECIAL && abs(index) >= SOURCE_TYPE_SPECIAL_FIRST_RESERVED) {
|
||||
// RESERVED1/2 are used as the CI1302 voice control VGR/VFL sources on this board
|
||||
bool isVoiceSource = IS_HELLORADIOSKY_V16(board) &&
|
||||
abs(index) <= SOURCE_TYPE_SPECIAL_RESERVED2;
|
||||
if (!isVoiceSource)
|
||||
return false;
|
||||
|
||||
// None on the Hardware Switches screen disables VGR/VFL as a source too
|
||||
if (gs) {
|
||||
QString tag = (abs(index) == SOURCE_TYPE_SPECIAL_RESERVED1) ? "VGR" : "VFL";
|
||||
int swIdx = Boards::getSwitchIndex(tag, Board::LVT_TAG, board);
|
||||
if (swIdx >= 0 && gs->switchConfig[swIdx].type == Board::SWITCH_NOT_AVAILABLE)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == SOURCE_TYPE_TIMER && abs(index) > CPN_MAX_TIMERS)
|
||||
return false;
|
||||
@@ -475,8 +488,8 @@ tbl.insert(tbl.end(), {
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_TX_BATT), "TX_VOLTAGE"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_TX_TIME), "TX_TIME"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_TX_GPS), "TX_GPS"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED1), "RESERVED1"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED2), "RESERVED2"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED1), "VGR"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED2), "VFL"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED3), "RESERVED3"},
|
||||
{std::to_string(SOURCE_TYPE_SPECIAL_RESERVED4), "RESERVED4"},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// CI1302 voice-control (VGR/VFL) source and switch coverage for the
|
||||
// HelloRadioSky V16 board. Companion reimplements the firmware's YAML
|
||||
// grammar independently, so these are the only guard against VGR/VFL
|
||||
// silently decoding to NONE and getting lost on save.
|
||||
|
||||
#include "gtests.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
#include "firmwares/eeprominterface.h"
|
||||
#include "firmwares/edgetx/edgetxinterface.h"
|
||||
#include "firmwares/edgetx/yaml_rawsource.h"
|
||||
#include "firmwares/edgetx/yaml_rawswitch.h"
|
||||
#include "firmwares/boardjson.h"
|
||||
#include "firmwares/generalsettings.h"
|
||||
#include "firmwares/mixdata.h"
|
||||
|
||||
namespace {
|
||||
|
||||
ModelData roundTrip(const ModelData& in, QByteArray& yamlOut)
|
||||
{
|
||||
writeModelToYaml(in, yamlOut);
|
||||
ModelData out;
|
||||
out.clear();
|
||||
loadModelFromYaml(out, yamlOut);
|
||||
return out;
|
||||
}
|
||||
|
||||
class VoiceControlV16 : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
Firmware::setCurrentVariant(Firmware::getFirmwareForFlavour("v16"));
|
||||
ASSERT_NE(getCurrentFirmware(), nullptr)
|
||||
<< "v16 (HelloRadioSky V16) firmware must be registered for this test";
|
||||
ASSERT_TRUE(IS_HELLORADIOSKY_V16(getCurrentBoard()));
|
||||
}
|
||||
};
|
||||
|
||||
class VoiceControlOtherBoard : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
Firmware::setCurrentVariant(Firmware::getFirmwareForFlavour("tx16s"));
|
||||
ASSERT_NE(getCurrentFirmware(), nullptr)
|
||||
<< "tx16s firmware must be registered for this test";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// VGR/VFL reuse the SOURCE_TYPE_SPECIAL RESERVED1/RESERVED2 slots; yaml tags
|
||||
// must match the firmware's exactly ("VGR"/"VFL").
|
||||
TEST_F(VoiceControlV16, MixSrcEncodeDecodeRoundTrips)
|
||||
{
|
||||
const RawSource vgr(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED1);
|
||||
const RawSource vfl(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED2);
|
||||
|
||||
EXPECT_EQ(YamlRawSourceEncode(vgr), "VGR");
|
||||
EXPECT_EQ(YamlRawSourceEncode(vfl), "VFL");
|
||||
|
||||
RawSource vgrBack = YamlRawSourceDecode("VGR");
|
||||
EXPECT_EQ(vgrBack.type, SOURCE_TYPE_SPECIAL);
|
||||
EXPECT_EQ(vgrBack.index, SOURCE_TYPE_SPECIAL_RESERVED1);
|
||||
|
||||
RawSource vflBack = YamlRawSourceDecode("VFL");
|
||||
EXPECT_EQ(vflBack.type, SOURCE_TYPE_SPECIAL);
|
||||
EXPECT_EQ(vflBack.index, SOURCE_TYPE_SPECIAL_RESERVED2);
|
||||
}
|
||||
|
||||
// VGR/VFL are offered on the V16 board; RESERVED3/4 stay excluded.
|
||||
TEST_F(VoiceControlV16, MixSrcAvailableOnlyForVoiceReservedSlots)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
const RawSource vgr(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED1);
|
||||
const RawSource vfl(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED2);
|
||||
const RawSource reserved3(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED3);
|
||||
const RawSource reserved4(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED4);
|
||||
|
||||
EXPECT_TRUE(vgr.isAvailable(nullptr, nullptr, board));
|
||||
EXPECT_TRUE(vfl.isAvailable(nullptr, nullptr, board));
|
||||
EXPECT_FALSE(reserved3.isAvailable(nullptr, nullptr, board));
|
||||
EXPECT_FALSE(reserved4.isAvailable(nullptr, nullptr, board));
|
||||
}
|
||||
|
||||
// A mix already using VGR as its source must not keep tracking it once the
|
||||
// user disables VGR on the Hardware Switches screen.
|
||||
TEST_F(VoiceControlV16, MixSrcNotAvailableWhenSwitchDisabled)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
int vgrIdx = Boards::getSwitchYamlIndex(QStringLiteral("VGR"), BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(vgrIdx, 0);
|
||||
|
||||
const RawSource vgr(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED1);
|
||||
const RawSource vfl(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED2);
|
||||
|
||||
GeneralSettings gs;
|
||||
EXPECT_TRUE(vgr.isAvailable(nullptr, &gs, board));
|
||||
|
||||
gs.switchConfig[vgrIdx].type = Board::SWITCH_NOT_AVAILABLE;
|
||||
EXPECT_FALSE(vgr.isAvailable(nullptr, &gs, board));
|
||||
EXPECT_TRUE(vfl.isAvailable(nullptr, &gs, board));
|
||||
}
|
||||
|
||||
// VGR/VFL must not leak into other radios' source pickers.
|
||||
TEST_F(VoiceControlOtherBoard, MixSrcNotAvailableOnOtherBoards)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
ASSERT_FALSE(IS_HELLORADIOSKY_V16(board));
|
||||
|
||||
const RawSource vgr(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED1);
|
||||
const RawSource vfl(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED2);
|
||||
|
||||
EXPECT_FALSE(vgr.isAvailable(nullptr, nullptr, board));
|
||||
EXPECT_FALSE(vfl.isAvailable(nullptr, nullptr, board));
|
||||
}
|
||||
|
||||
// Look the base switch index up rather than hardcoding it, since it depends
|
||||
// on how many physical switches precede VGR/VFL in the table.
|
||||
TEST_F(VoiceControlV16, SwitchEncodeDecodeRoundTrips)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
|
||||
for (const QString& tag : {QStringLiteral("VGR"), QStringLiteral("VFL")}) {
|
||||
int swIdx = Boards::getSwitchYamlIndex(tag, BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(swIdx, 0) << tag.toStdString() << " switch not registered for this board";
|
||||
|
||||
for (int pos = 0; pos < 3; pos++) {
|
||||
RawSwitch rs(SWITCH_TYPE_SWITCH, swIdx * 3 + pos + 1);
|
||||
|
||||
std::string expected = QString(tag + QString::number(pos)).toStdString();
|
||||
EXPECT_EQ(YamlRawSwitchEncode(rs), expected);
|
||||
|
||||
RawSwitch back = YamlRawSwitchDecode(expected);
|
||||
EXPECT_TRUE(back == rs) << "round-trip mismatch for " << expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A model using VGR as a mix source and VFL2 as its switch must survive a
|
||||
// full YAML round-trip unchanged.
|
||||
TEST_F(VoiceControlV16, ModelUsingVoiceSourceAndSwitchRoundTrips)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
int vflIdx = Boards::getSwitchYamlIndex(QStringLiteral("VFL"), BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(vflIdx, 0);
|
||||
|
||||
ModelData m;
|
||||
m.clear();
|
||||
m.used = true;
|
||||
m.mixData[0].destCh = 1;
|
||||
m.mixData[0].weight = 100;
|
||||
m.mixData[0].srcRaw = RawSource(SOURCE_TYPE_SPECIAL, SOURCE_TYPE_SPECIAL_RESERVED1); // VGR
|
||||
m.mixData[0].swtch = RawSwitch(SWITCH_TYPE_SWITCH, vflIdx * 3 + 2 + 1); // VFL2
|
||||
|
||||
QByteArray y1;
|
||||
ModelData m2 = roundTrip(m, y1);
|
||||
|
||||
EXPECT_EQ(m2.mixData[0].destCh, m.mixData[0].destCh);
|
||||
EXPECT_EQ(m2.mixData[0].srcRaw.type, m.mixData[0].srcRaw.type);
|
||||
EXPECT_EQ(m2.mixData[0].srcRaw.index, m.mixData[0].srcRaw.index);
|
||||
EXPECT_TRUE(m2.mixData[0].swtch == m.mixData[0].swtch)
|
||||
<< "VFL2 switch was lost/changed on round-trip";
|
||||
|
||||
// Fixed point: re-serialising the reloaded model must not lose it either.
|
||||
QByteArray y2;
|
||||
ModelData m3 = roundTrip(m2, y2);
|
||||
EXPECT_EQ(m3.mixData[0].srcRaw.index, m2.mixData[0].srcRaw.index);
|
||||
EXPECT_TRUE(m3.mixData[0].swtch == m2.mixData[0].swtch);
|
||||
}
|
||||
|
||||
// Simulator switch widgets address switches by index, so these must match
|
||||
// firmware's initVoiceMappedSwitches() (radio/src/drivers/CI1302.cpp):
|
||||
// switchGetMaxSwitches() + MAX_FLEX_SWITCHES, where V16 has 10 physical
|
||||
// switches and 2 flex switches.
|
||||
TEST_F(VoiceControlV16, VoiceSwitchIndexesFollowFlexSwitches)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
|
||||
EXPECT_EQ(Boards::getCapability(board, Board::FlexSwitches), 2);
|
||||
EXPECT_EQ(Boards::getSwitchYamlIndex(QStringLiteral("FL1"), BoardJson::YLT_REF, board), 10);
|
||||
EXPECT_EQ(Boards::getSwitchYamlIndex(QStringLiteral("FL2"), BoardJson::YLT_REF, board), 11);
|
||||
EXPECT_EQ(Boards::getSwitchYamlIndex(QStringLiteral("VGR"), BoardJson::YLT_REF, board), 12);
|
||||
EXPECT_EQ(Boards::getSwitchYamlIndex(QStringLiteral("VFL"), BoardJson::YLT_REF, board), 13);
|
||||
}
|
||||
|
||||
// VGR (voice gear) is a 2POS switch on real hardware -- see
|
||||
// isTwoPosVoiceSwitch()/VSW_GEAR in radio/src/drivers/CI1302.cpp -- VFL
|
||||
// (voice flap) is 3POS.
|
||||
TEST_F(VoiceControlV16, VgrIsTwoPosVflIsThreePos)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
int vgrIdx = Boards::getSwitchYamlIndex(QStringLiteral("VGR"), BoardJson::YLT_REF, board);
|
||||
int vflIdx = Boards::getSwitchYamlIndex(QStringLiteral("VFL"), BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(vgrIdx, 0);
|
||||
ASSERT_GE(vflIdx, 0);
|
||||
|
||||
EXPECT_EQ(Boards::getSwitchInfo(vgrIdx, board).dflt, Board::SWITCH_2POS);
|
||||
EXPECT_EQ(Boards::getSwitchInfo(vflIdx, board).dflt, Board::SWITCH_3POS);
|
||||
}
|
||||
|
||||
// Firmware (radio/src/drivers/CI1302.cpp, CI1302_voiceSwitchSetDefaults())
|
||||
// now default-initialises and genuinely persists VGR/VFL's type like any
|
||||
// other switch, and its own settings.yml always includes an explicit entry
|
||||
// for them (switchIsActive() already covers switchGetMaxAllSwitches()).
|
||||
// A real device's file therefore always states VGR/VFL's type explicitly
|
||||
// -- Companion must round-trip whatever value is there, including an
|
||||
// explicit NONE if the user disabled one on the radio.
|
||||
TEST_F(VoiceControlV16, GeneralSettingsRoundTripHonoursVoiceSwitchType)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
int vgrIdx = Boards::getSwitchYamlIndex(QStringLiteral("VGR"), BoardJson::YLT_REF, board);
|
||||
int vflIdx = Boards::getSwitchYamlIndex(QStringLiteral("VFL"), BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(vgrIdx, 0);
|
||||
ASSERT_GE(vflIdx, 0);
|
||||
|
||||
const QByteArray deviceYaml =
|
||||
"board: v16\n"
|
||||
"switchConfig:\n"
|
||||
" SA:\n"
|
||||
" type: 3POS\n"
|
||||
" VGR:\n"
|
||||
" type: NONE\n"
|
||||
" VFL:\n"
|
||||
" type: 3POS\n";
|
||||
|
||||
GeneralSettings gs;
|
||||
ASSERT_TRUE(loadRadioSettingsFromYaml(gs, deviceYaml));
|
||||
|
||||
EXPECT_EQ(gs.switchConfig[vgrIdx].type, Board::SWITCH_NOT_AVAILABLE)
|
||||
<< "the user disabled VGR on the radio; Companion must not resurrect it";
|
||||
EXPECT_EQ(gs.switchConfig[vflIdx].type, Board::SWITCH_3POS);
|
||||
|
||||
RawSwitch vgrUp(SWITCH_TYPE_SWITCH, vgrIdx * 3 + 0 + 1);
|
||||
RawSwitch vflUp(SWITCH_TYPE_SWITCH, vflIdx * 3 + 0 + 1);
|
||||
RawSwitch vflMid(SWITCH_TYPE_SWITCH, vflIdx * 3 + 1 + 1);
|
||||
RawSwitch vflDown(SWITCH_TYPE_SWITCH, vflIdx * 3 + 2 + 1);
|
||||
|
||||
EXPECT_FALSE(vgrUp.isAvailable(nullptr, &gs, board))
|
||||
<< "VGR is configured NONE; it must not appear in the switches picker";
|
||||
|
||||
EXPECT_TRUE(vflUp.isAvailable(nullptr, &gs, board));
|
||||
EXPECT_TRUE(vflMid.isAvailable(nullptr, &gs, board))
|
||||
<< "VFL is a 3POS switch; all 3 positions must be available in the switches picker";
|
||||
EXPECT_TRUE(vflDown.isAvailable(nullptr, &gs, board));
|
||||
}
|
||||
|
||||
// A device's settings.yml with no switchConfig entry at all for VGR/VFL
|
||||
// (e.g. hand-written, or predating the firmware default-init fix) falls
|
||||
// back to None, same as it would for any other switch missing from the
|
||||
// file -- there's no special-casing left for these two on the Companion side.
|
||||
TEST_F(VoiceControlV16, GeneralSettingsRoundTripDefaultsMissingVoiceSwitchesToNone)
|
||||
{
|
||||
Board::Type board = getCurrentBoard();
|
||||
int vgrIdx = Boards::getSwitchYamlIndex(QStringLiteral("VGR"), BoardJson::YLT_REF, board);
|
||||
ASSERT_GE(vgrIdx, 0);
|
||||
|
||||
const QByteArray deviceYaml =
|
||||
"board: v16\n"
|
||||
"switchConfig:\n"
|
||||
" SA:\n"
|
||||
" type: 3POS\n";
|
||||
|
||||
GeneralSettings gs;
|
||||
ASSERT_TRUE(loadRadioSettingsFromYaml(gs, deviceYaml));
|
||||
|
||||
EXPECT_EQ(gs.switchConfig[vgrIdx].type, Board::SWITCH_NOT_AVAILABLE);
|
||||
}
|
||||
+5
-1
@@ -202,7 +202,11 @@ const char * const audioFilenames[] = {
|
||||
"mixwarn3",
|
||||
"timovr1",
|
||||
"timovr2",
|
||||
"timovr3"
|
||||
"timovr3",
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
"helloradio",
|
||||
"okay",
|
||||
#endif
|
||||
};
|
||||
|
||||
constexpr unsigned int MAX_SWITCH_POSITIONS =
|
||||
|
||||
@@ -537,6 +537,11 @@ void audioTimerCountdown(uint8_t timer, int value);
|
||||
#define AUDIO_TRAINER_LOST() audioEvent(AU_TRAINER_LOST)
|
||||
#define AUDIO_TRAINER_BACK() audioEvent(AU_TRAINER_BACK)
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#define AUDIO_HELLORADIO() audioPlay(AU_HELLORADIO)
|
||||
#define AUDIO_OKAY() audioPlay(AU_OKAY)
|
||||
#endif
|
||||
|
||||
#else // AUDIO
|
||||
|
||||
#define AUDIO_TIMER_COUNTDOWN(idx, val)
|
||||
|
||||
@@ -527,6 +527,13 @@ enum MixSources {
|
||||
MIXSRC_LIGHT,
|
||||
#endif
|
||||
|
||||
// Must stay below MIXSRC_LAST or the mixer's source picker can't offer them
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
MIXSRC_VGR,
|
||||
MIXSRC_VFL,
|
||||
MIXSRC_LAST_VOICE SKIP = MIXSRC_VFL,
|
||||
#endif
|
||||
|
||||
MIXSRC_FIRST_HELI SKIP,
|
||||
MIXSRC_LAST_HELI SKIP = MIXSRC_FIRST_HELI + 2,
|
||||
|
||||
@@ -598,6 +605,9 @@ enum SrcTypes {
|
||||
SRC_TIMER = 1 << 17,
|
||||
SRC_TELEM = 1 << 18,
|
||||
SRC_LIGHT = 1 << 19,
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
SRC_VOICE = 1 << 20,
|
||||
#endif
|
||||
SRC_NONE = 1 << 30,
|
||||
SRC_INVERT = 1 << 31,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,890 @@
|
||||
/*
|
||||
* 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 "edgetx.h"
|
||||
#include "CI1302.h"
|
||||
#include "timers_driver.h"
|
||||
#include "debug.h"
|
||||
#include "audio.h"
|
||||
|
||||
#define VOICE_FRAME_DELAY_US 500
|
||||
|
||||
#define VOICE_START_BYTE 0x55
|
||||
#define VOICE_STARTCN_BYTE 0xAA
|
||||
#define VOICE_STARTEN_BYTE 0xAB
|
||||
|
||||
#define VOICE_FRAME_SIZE 6
|
||||
|
||||
#define VOICE_END_BYTE1 0xA5
|
||||
#define VOICE_END_BYTE2 0x5A
|
||||
|
||||
#define VOICE_WKUP 0x01
|
||||
#define VOICE_password 0x02
|
||||
#define VOICE_Aileron_adjustment 0x03
|
||||
#define VOICE_Pitch_adjustment 0x04
|
||||
#define VOICE_Rotating_adjustment 0x05
|
||||
#define VOICE_Up 0x0A
|
||||
#define VOICE_Down 0x0B
|
||||
#define VOICE_Left_one 0x14
|
||||
#define VOICE_Right_one 0x15
|
||||
#define VOICE_Confirm 0x1A
|
||||
#define VOICE_Cancel 0x1B
|
||||
#define VOICE_Model_Menu 0x21
|
||||
#define VOICE_System_Menu 0x22
|
||||
#define VOICE_Channel_Setup 0x23
|
||||
#define VOICE_Channel_Monitor 0X32
|
||||
#define VOICE_Internal_Module 0X33
|
||||
#define VOICE_External_Module 0X34
|
||||
#define VOICE_Aileron_Reverse 0x4A
|
||||
#define VOICE_Elevator_Reverse 0x4B
|
||||
#define VOICE_Rudder_Reverse 0x4C
|
||||
#define VOICE_Motion_Control 0x55
|
||||
#define VOICE_Telemetry 0x5B
|
||||
#define VOICE_Open_Gear 0x7E
|
||||
#define VOICE_Close_Gear 0x80
|
||||
#define VOICE_Open_Flap 0x82
|
||||
#define VOICE_Close_Flap 0x85
|
||||
#define VOICE_POWEROFF 0x9E
|
||||
#define VOICE_Start_recording 0x88
|
||||
#define VOICE_End_recording 0x89
|
||||
#define VOICE_Photo 0x8A
|
||||
#define VOICE_Take_a_Photo 0x8B
|
||||
#define VOICE_go_home 0x8C
|
||||
#define VOICE_GPS_Return 0x91
|
||||
#define VOICE_hi_take_off 0x9F
|
||||
#define VOICE_hi_land 0xA1
|
||||
#define VOICE_Sensorstatus 0xA2
|
||||
|
||||
struct voice_cmd_inputs_t {
|
||||
uint8_t VOICESTARTBYTE1;
|
||||
uint8_t VOICELanguage;
|
||||
uint8_t VOICECMD;
|
||||
uint8_t VOICECMDR;
|
||||
uint8_t VOICEENDBYTE1;
|
||||
uint8_t VOICEENDBYTE2;
|
||||
};
|
||||
|
||||
bool VoiceRunStatus=true;
|
||||
bool VoiceGearStatus=false;
|
||||
uint8_t VoiceFlapStatus=false;
|
||||
|
||||
static uint32_t wkup_delay=0;
|
||||
|
||||
#define wkup_delayms 10000
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
static void voiceMotionControlEnable();
|
||||
static void voiceControlGuiHandleCmd(uint8_t voiceCmd);
|
||||
bool CI1302_voiceSwitchBootGuardActive();
|
||||
#endif
|
||||
|
||||
void processVoiceFrame(uint8_t * voicedata, uint32_t size)
|
||||
{
|
||||
voice_cmd_inputs_t *voiceframe=(voice_cmd_inputs_t*)voicedata;
|
||||
|
||||
if (size != VOICE_FRAME_SIZE || voiceframe->VOICESTARTBYTE1 != VOICE_START_BYTE ||
|
||||
voiceframe->VOICEENDBYTE1 != VOICE_END_BYTE1||
|
||||
voiceframe->VOICEENDBYTE2 != VOICE_END_BYTE2) {
|
||||
return;
|
||||
}
|
||||
if((voiceframe->VOICECMD+voiceframe->VOICECMDR)!=0xff)return;
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
// Motion control: no wake word required — call directly anytime.
|
||||
if ((voiceframe->VOICELanguage == VOICE_STARTEN_BYTE ||
|
||||
voiceframe->VOICELanguage == VOICE_STARTCN_BYTE) &&
|
||||
voiceframe->VOICECMD == VOICE_Motion_Control) {
|
||||
voiceMotionControlEnable();
|
||||
AUDIO_OKAY();
|
||||
return;
|
||||
}
|
||||
|
||||
if (voiceframe->VOICELanguage == VOICE_STARTEN_BYTE || voiceframe->VOICELanguage == VOICE_STARTCN_BYTE) {
|
||||
if(voiceframe->VOICECMD==VOICE_WKUP){
|
||||
wkup_delay=timersGetMsTick();
|
||||
AUDIO_HELLORADIO();
|
||||
}
|
||||
else if (voiceframe->VOICECMD == VOICE_Open_Gear) {
|
||||
if (!CI1302_voiceSwitchBootGuardActive()) {
|
||||
wkup_delay = timersGetMsTick();
|
||||
VoiceGearStatus = true;
|
||||
}
|
||||
}
|
||||
else if (voiceframe->VOICECMD == VOICE_Close_Gear) {
|
||||
if (!CI1302_voiceSwitchBootGuardActive()) {
|
||||
wkup_delay = timersGetMsTick();
|
||||
VoiceGearStatus = false;
|
||||
}
|
||||
}
|
||||
else if (voiceframe->VOICECMD == VOICE_Open_Flap) {
|
||||
if (!CI1302_voiceSwitchBootGuardActive()) {
|
||||
wkup_delay = timersGetMsTick();
|
||||
if (VoiceFlapStatus < 2) {
|
||||
VoiceFlapStatus++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (voiceframe->VOICECMD == VOICE_Close_Flap) {
|
||||
if (!CI1302_voiceSwitchBootGuardActive()) {
|
||||
wkup_delay = timersGetMsTick();
|
||||
if (VoiceFlapStatus > 0) {
|
||||
VoiceFlapStatus--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (voiceframe->VOICECMD == VOICE_Telemetry ||
|
||||
voiceframe->VOICECMD == VOICE_Sensorstatus ||
|
||||
voiceframe->VOICECMD == VOICE_Channel_Monitor ||
|
||||
voiceframe->VOICECMD == VOICE_Internal_Module ||
|
||||
voiceframe->VOICECMD == VOICE_External_Module ||
|
||||
voiceframe->VOICECMD == VOICE_Model_Menu ||
|
||||
voiceframe->VOICECMD == VOICE_System_Menu ||
|
||||
voiceframe->VOICECMD == VOICE_Confirm ||
|
||||
voiceframe->VOICECMD == VOICE_Cancel) {
|
||||
wkup_delay = timersGetMsTick();
|
||||
voiceControlGuiHandleCmd(voiceframe->VOICECMD);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// These call into the real UART driver (only compiled in for VOICE_CONTROL_USART
|
||||
// && !defined(SIMU) below), so they're excluded from SIMU too.
|
||||
#if !defined(SIMU)
|
||||
|
||||
extern "C" int GetVoiceInput(uint8_t *rxchar)
|
||||
{
|
||||
auto _getByte = voiceGetByte;
|
||||
if (_getByte && (_getByte(rxchar) > 0)) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void processUpdataInput(void)
|
||||
{
|
||||
uint8_t rxchar;
|
||||
auto _getByte = dbgGetByte;
|
||||
while (_getByte && (_getByte(&rxchar) > 0)) {
|
||||
voiceSerialPutc(rxchar);
|
||||
}
|
||||
}
|
||||
|
||||
void processVoiceInput(void)
|
||||
{
|
||||
static uint8_t VoiceIndex = 0;
|
||||
static uint32_t VoiceTimer;
|
||||
static uint8_t VoiceFrame[VOICE_FRAME_SIZE];
|
||||
|
||||
uint32_t active = 0;
|
||||
|
||||
if(VoiceRunStatus==false)return;
|
||||
|
||||
uint8_t rxchar;
|
||||
auto _getByte = voiceGetByte;
|
||||
while (_getByte && (_getByte(&rxchar) > 0)) {
|
||||
active = 1;
|
||||
if (VoiceIndex > VOICE_FRAME_SIZE - 1) {
|
||||
VoiceIndex = VOICE_FRAME_SIZE - 1;
|
||||
}
|
||||
VoiceFrame[VoiceIndex++] = rxchar;
|
||||
TRACE("0x%x", rxchar);
|
||||
}
|
||||
|
||||
if (wkup_delay)
|
||||
{
|
||||
if ((uint32_t)(timersGetMsTick() - wkup_delay) > wkup_delayms) {
|
||||
wkup_delay = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (active) {
|
||||
VoiceTimer = timersGetUsTick();
|
||||
return;
|
||||
}
|
||||
if (VoiceIndex) {
|
||||
if ((uint32_t)(timersGetUsTick() - VoiceTimer) > VOICE_FRAME_DELAY_US) {
|
||||
processVoiceFrame(VoiceFrame, VoiceIndex);
|
||||
VoiceIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !SIMU
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#include "globals.h"
|
||||
#include "gyro.h"
|
||||
#include "input_mapping.h"
|
||||
#include "keys.h"
|
||||
#include "myeeprom.h"
|
||||
#include "storage/yaml/yaml_bits.h"
|
||||
#include <string.h>
|
||||
|
||||
#if defined(COLORLCD)
|
||||
#include "module_setup.h"
|
||||
#include "quick_menu.h"
|
||||
#include "view_channels.h"
|
||||
#include "view_main.h"
|
||||
#include "LvglWrapper.h"
|
||||
#endif
|
||||
|
||||
// --- motion control ---
|
||||
|
||||
bool MotionControlStatus = false;
|
||||
|
||||
static constexpr int16_t VOICE_MOTION_RELEASE_THRESHOLD = RESX / 4;
|
||||
static constexpr uint8_t STICK_ROLE_ELE = 1;
|
||||
static constexpr uint8_t STICK_ROLE_AIL = 3;
|
||||
|
||||
static int16_t gyroBaselineX = 0;
|
||||
static int16_t gyroBaselineY = 0;
|
||||
|
||||
static void voiceMotionControlEnable()
|
||||
{
|
||||
#if defined(IMU)
|
||||
gyroBaselineX = gyroScaledX();
|
||||
gyroBaselineY = gyroScaledY();
|
||||
#endif
|
||||
MotionControlStatus = true;
|
||||
}
|
||||
|
||||
void CI1302_voiceMotionControlApplyToInputs(uint8_t pots_offset)
|
||||
{
|
||||
#if defined(IMU)
|
||||
if (!MotionControlStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t deltaX = gyroScaledX() - gyroBaselineX;
|
||||
const int16_t deltaY = gyroScaledY() - gyroBaselineY;
|
||||
|
||||
for (uint8_t phys = 0; phys < pots_offset; phys++) {
|
||||
const uint8_t role = inputMappingConvertMode(phys);
|
||||
const int16_t stick = calibratedAnalogs[phys];
|
||||
|
||||
if ((role == STICK_ROLE_AIL || role == STICK_ROLE_ELE) &&
|
||||
abs(stick) > VOICE_MOTION_RELEASE_THRESHOLD) {
|
||||
MotionControlStatus = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint8_t phys = 0; phys < pots_offset; phys++) {
|
||||
const uint8_t role = inputMappingConvertMode(phys);
|
||||
const int16_t stick = calibratedAnalogs[phys];
|
||||
|
||||
if (role == STICK_ROLE_AIL) {
|
||||
calibratedAnalogs[phys] =
|
||||
limit<int16_t>(-RESX, stick + deltaX, RESX);
|
||||
} else if (role == STICK_ROLE_ELE) {
|
||||
calibratedAnalogs[phys] =
|
||||
limit<int16_t>(-RESX, stick + deltaY, RESX);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)pots_offset;
|
||||
MotionControlStatus = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// --- virtual voice switches (VGR / VFL) ---
|
||||
|
||||
enum VoiceSwitchId {
|
||||
VSW_GEAR = 0,
|
||||
VSW_FLAP,
|
||||
};
|
||||
|
||||
static int8_t voiceSwitchIdx[VOICE_SWITCH_COUNT] = {
|
||||
-2, -2,
|
||||
};
|
||||
|
||||
static const char* const voiceSwitchNames[VOICE_SWITCH_COUNT] = {
|
||||
"VGR", "VFL",
|
||||
};
|
||||
|
||||
static constexpr uint32_t VOICE_BOOT_GUARD_MS = 5000;
|
||||
|
||||
static uint32_t VoiceBootGuardUntil = 0;
|
||||
|
||||
static void initVoiceMappedSwitches()
|
||||
{
|
||||
if (voiceSwitchIdx[VSW_GEAR] != -2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t base = (int8_t)(switchGetMaxSwitches() + MAX_FLEX_SWITCHES);
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
voiceSwitchIdx[i] = base + i;
|
||||
}
|
||||
}
|
||||
|
||||
static int8_t voiceSwitchIndex(VoiceSwitchId id)
|
||||
{
|
||||
initVoiceMappedSwitches();
|
||||
return voiceSwitchIdx[id];
|
||||
}
|
||||
|
||||
uint8_t CI1302_voiceSwitchExtraCount()
|
||||
{
|
||||
return VOICE_SWITCH_COUNT;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchIsIndex(uint8_t idx)
|
||||
{
|
||||
initVoiceMappedSwitches();
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
if ((int8_t)idx == voiceSwitchIdx[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static VoiceSwitchId voiceSwitchIdFromIndex(uint8_t idx)
|
||||
{
|
||||
initVoiceMappedSwitches();
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
if ((int8_t)idx == voiceSwitchIdx[i]) {
|
||||
return (VoiceSwitchId)i;
|
||||
}
|
||||
}
|
||||
return VSW_GEAR;
|
||||
}
|
||||
|
||||
static bool isTwoPosVoiceSwitch(VoiceSwitchId id)
|
||||
{
|
||||
return id != VSW_FLAP;
|
||||
}
|
||||
|
||||
// Whether the user has this voice switch configured as anything but None.
|
||||
static bool voiceSwitchIsEnabled(VoiceSwitchId id)
|
||||
{
|
||||
int8_t idx = voiceSwitchIndex(id);
|
||||
if (idx < 0 || idx >= MAX_SWITCHES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return g_eeGeneral.switchConfig[idx].type != SWITCH_NONE;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchIsPositionAvailable(uint8_t idx, uint8_t position)
|
||||
{
|
||||
if (!CI1302_voiceSwitchIsIndex(idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isTwoPosVoiceSwitch(voiceSwitchIdFromIndex(idx)) && position == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(SIMU)
|
||||
SwitchHwPos boardSwitchGetPosition(uint8_t idx);
|
||||
#endif
|
||||
|
||||
static SwitchHwPos voiceSwitchPosition(uint8_t idx)
|
||||
{
|
||||
#if defined(SIMU)
|
||||
// No CI1302 in the simulator: use the simulated switch widgets instead.
|
||||
return boardSwitchGetPosition(idx);
|
||||
#endif
|
||||
switch (voiceSwitchIdFromIndex(idx)) {
|
||||
case VSW_GEAR:
|
||||
return VoiceGearStatus ? SWITCH_HW_DOWN : SWITCH_HW_UP;
|
||||
|
||||
case VSW_FLAP: {
|
||||
uint8_t flapPos = (VoiceFlapStatus >= 2) ? 2 : VoiceFlapStatus;
|
||||
return (SwitchHwPos)flapPos;
|
||||
}
|
||||
|
||||
default:
|
||||
return SWITCH_HW_UP;
|
||||
}
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchTryGetPosition(uint8_t idx, SwitchHwPos* pos)
|
||||
{
|
||||
if (!CI1302_voiceSwitchIsIndex(idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*pos = voiceSwitchPosition(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* CI1302_voiceSwitchTryGetName(uint8_t idx)
|
||||
{
|
||||
initVoiceMappedSwitches();
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
if ((int8_t)idx == voiceSwitchIdx[i]) {
|
||||
return voiceSwitchNames[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int8_t CI1302_voiceSwitchTryGetIndexFromName(const char* name)
|
||||
{
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
if (strcmp(name, voiceSwitchNames[i]) == 0) {
|
||||
return voiceSwitchIndex((VoiceSwitchId)i);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchTryGetHwType(uint8_t idx, SwitchHwType* type)
|
||||
{
|
||||
if (!CI1302_voiceSwitchIsIndex(idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// VGR is 2POS (bool), VFL is 3POS.
|
||||
*type = isTwoPosVoiceSwitch(voiceSwitchIdFromIndex(idx)) ? SWITCH_HW_2POS
|
||||
: SWITCH_HW_3POS;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nothing else default-inits switchConfig[] for these two indices.
|
||||
void CI1302_voiceSwitchSetDefaults()
|
||||
{
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
int8_t idx = voiceSwitchIndex((VoiceSwitchId)i);
|
||||
if (idx < 0 || idx >= MAX_SWITCHES) {
|
||||
continue;
|
||||
}
|
||||
|
||||
g_eeGeneral.switchConfig[idx].type =
|
||||
isTwoPosVoiceSwitch((VoiceSwitchId)i) ? SWITCH_2POS : SWITCH_3POS;
|
||||
g_eeGeneral.switchConfig[idx].name[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchTryIsAvailable(int swtch, int context, bool* available)
|
||||
{
|
||||
int idx = swtch;
|
||||
|
||||
if (idx < 0) {
|
||||
idx = -idx;
|
||||
}
|
||||
|
||||
if (idx < SWSRC_FIRST_SWITCH || idx > SWSRC_LAST_SWITCH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
idx -= SWSRC_FIRST_SWITCH;
|
||||
div_t swinfo = div(idx, 3);
|
||||
|
||||
if (!CI1302_voiceSwitchIsIndex(swinfo.quot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context == GeneralCustomFunctionsContext) {
|
||||
*available = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!voiceSwitchIsEnabled(voiceSwitchIdFromIndex(swinfo.quot))) {
|
||||
*available = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
*available = CI1302_voiceSwitchIsPositionAvailable(swinfo.quot, swinfo.rem);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool voiceSwitchTryParseYamlTag(const char* val, uint8_t val_len,
|
||||
const char* tag, VoiceSwitchId id,
|
||||
int32_t* ival)
|
||||
{
|
||||
const size_t tagLen = strlen(tag);
|
||||
if (val_len <= tagLen || strncmp(val, tag, tagLen) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val[tagLen] < '0' || val[tagLen] > '2') {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t sw = voiceSwitchIndex(id) * 3;
|
||||
if (sw < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*ival = sw + yaml_str2int(val + tagLen, val_len - tagLen) + SWSRC_FIRST_SWITCH;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchTryParseYamlSrc(const char* val, uint8_t val_len, int32_t* ival)
|
||||
{
|
||||
for (uint8_t i = 0; i < VOICE_SWITCH_COUNT; i++) {
|
||||
if (voiceSwitchTryParseYamlTag(val, val_len, voiceSwitchNames[i],
|
||||
(VoiceSwitchId)i, ival)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CI1302_voiceSwitchInitBootGuard()
|
||||
{
|
||||
VoiceBootGuardUntil = timersGetMsTick() + VOICE_BOOT_GUARD_MS;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchBootGuardActive()
|
||||
{
|
||||
return VoiceBootGuardUntil != 0 && timersGetMsTick() < VoiceBootGuardUntil;
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchSuppressMovedAudio(uint8_t idx, bool startup)
|
||||
{
|
||||
if (!CI1302_voiceSwitchIsIndex(idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return startup || CI1302_voiceSwitchBootGuardActive();
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchIsSwitchSource(swsrc_t swtch)
|
||||
{
|
||||
int idx = swtch;
|
||||
if (idx < 0) {
|
||||
idx = -idx;
|
||||
}
|
||||
|
||||
if (idx < SWSRC_FIRST_SWITCH || idx > SWSRC_LAST_SWITCH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
div_t swinfo = div(idx - SWSRC_FIRST_SWITCH, 3);
|
||||
return CI1302_voiceSwitchIsIndex(swinfo.quot);
|
||||
}
|
||||
|
||||
bool CI1302_voiceSwitchShouldSuppressFunctionAudio(swsrc_t swtch, bool risingEdge)
|
||||
{
|
||||
if (!risingEdge || !CI1302_voiceSwitchBootGuardActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return CI1302_voiceSwitchIsSwitchSource(swtch);
|
||||
}
|
||||
|
||||
// --- voice menu navigation ---
|
||||
|
||||
#if defined(COLORLCD)
|
||||
static bool voiceControlGuiOnMainScreen()
|
||||
{
|
||||
auto viewMain = ViewMain::instance();
|
||||
if (!viewMain) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Window* top = Window::topWindow();
|
||||
if (!top) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return top == viewMain || top == viewMain->getTopbar();
|
||||
}
|
||||
#endif
|
||||
|
||||
static void voiceControlGuiHandleCmd(uint8_t voiceCmd)
|
||||
{
|
||||
#if defined(COLORLCD)
|
||||
switch (voiceCmd) {
|
||||
case VOICE_Channel_Monitor:
|
||||
AUDIO_OKAY();
|
||||
new ChannelsViewMenu();
|
||||
return;
|
||||
|
||||
case VOICE_Telemetry:
|
||||
case VOICE_Sensorstatus:
|
||||
AUDIO_OKAY();
|
||||
QuickMenu::openPage(QM_MODEL_TELEMETRY);
|
||||
return;
|
||||
|
||||
case VOICE_Internal_Module:
|
||||
AUDIO_OKAY();
|
||||
new ModulePage(INTERNAL_MODULE);
|
||||
return;
|
||||
|
||||
case VOICE_External_Module:
|
||||
AUDIO_OKAY();
|
||||
new ModulePage(EXTERNAL_MODULE);
|
||||
return;
|
||||
|
||||
case VOICE_Model_Menu:
|
||||
AUDIO_OKAY();
|
||||
QuickMenu::openPage(QM_MODEL_SETUP);
|
||||
return;
|
||||
|
||||
case VOICE_System_Menu:
|
||||
AUDIO_OKAY();
|
||||
QuickMenu::openPage(QM_RADIO_SETUP);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (voiceCmd) {
|
||||
case VOICE_Confirm:
|
||||
AUDIO_OKAY();
|
||||
#if defined(COLORLCD)
|
||||
if (voiceControlGuiOnMainScreen()) {
|
||||
pushEvent(EVT_KEY_BREAK(KEY_ENTER));
|
||||
} else {
|
||||
lvglRequestKeyboardEnter();
|
||||
}
|
||||
#else
|
||||
pushEvent(EVT_KEY_BREAK(KEY_ENTER));
|
||||
#endif
|
||||
break;
|
||||
|
||||
case VOICE_Cancel:
|
||||
#if defined(COLORLCD)
|
||||
if (voiceControlGuiOnMainScreen()) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
AUDIO_OKAY();
|
||||
pushEvent(EVT_KEY_BREAK(KEY_EXIT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- shared-code integration hooks ---
|
||||
|
||||
void CI1302_voiceIntegrationPer10ms()
|
||||
{
|
||||
#if !defined(SIMU)
|
||||
processVoiceInput();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CI1302_voiceIntegrationOnFlightReset()
|
||||
{
|
||||
CI1302_voiceSwitchInitBootGuard();
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationMixSrcValue(mixsrc_t i, getvalue_t* val)
|
||||
{
|
||||
VoiceSwitchId id;
|
||||
if (i == MIXSRC_VGR) {
|
||||
id = VSW_GEAR;
|
||||
} else if (i == MIXSRC_VFL) {
|
||||
id = VSW_FLAP;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!voiceSwitchIsEnabled(id)) return false;
|
||||
|
||||
switch (voiceSwitchPosition(voiceSwitchIndex(id))) {
|
||||
case SWITCH_HW_UP:
|
||||
*val = -1024;
|
||||
break;
|
||||
case SWITCH_HW_MID:
|
||||
*val = 0;
|
||||
break;
|
||||
default:
|
||||
*val = 1024;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationSourceAvailable(mixsrc_t i)
|
||||
{
|
||||
if (i == MIXSRC_VGR) return voiceSwitchIsEnabled(VSW_GEAR);
|
||||
if (i == MIXSRC_VFL) return voiceSwitchIsEnabled(VSW_FLAP);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* CI1302_voiceIntegrationMixSrcName(mixsrc_t idx)
|
||||
{
|
||||
if (idx == MIXSRC_VGR) {
|
||||
return "VGR";
|
||||
}
|
||||
|
||||
if (idx == MIXSRC_VFL) {
|
||||
return "VFL";
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationMixSrcParseYaml(const char* val, uint8_t val_len, uint32_t* out)
|
||||
{
|
||||
if (val_len == 3 && val[0] == 'V' && val[1] == 'G' && val[2] == 'R') {
|
||||
*out = MIXSRC_VGR;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (val_len == 3 && val[0] == 'V' && val[1] == 'F' && val[2] == 'L') {
|
||||
*out = MIXSRC_VFL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationMixSrcWriteYaml(uint32_t val, const char** str)
|
||||
{
|
||||
if (val == MIXSRC_VGR) {
|
||||
*str = "VGR";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (val == MIXSRC_VFL) {
|
||||
*str = "VFL";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationSwitchSrcParseYaml(const char* val, uint8_t val_len, int32_t* ival)
|
||||
{
|
||||
return CI1302_voiceSwitchTryParseYamlSrc(val, val_len, ival);
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationIsSwitchAvailable(int swtch, int context, bool* available)
|
||||
{
|
||||
return CI1302_voiceSwitchTryIsAvailable(swtch, context, available);
|
||||
}
|
||||
|
||||
bool CI1302_voiceIntegrationIsSwitchSwitchAvailable(int swtch, bool* available)
|
||||
{
|
||||
return CI1302_voiceSwitchTryIsAvailable(swtch + SWSRC_FIRST_SWITCH, 0, available);
|
||||
}
|
||||
|
||||
#endif // VOICE_CONTROL_SENSOR
|
||||
|
||||
#if defined(VOICE_CONTROL_USART) && !defined(SIMU)
|
||||
|
||||
#include "hal/gpio.h"
|
||||
#include "hal/serial_driver.h"
|
||||
#include "stm32_gpio.h"
|
||||
#include "stm32_serial_driver.h"
|
||||
|
||||
#define VOICE_USART_IRQ_PRIORITY 6
|
||||
#define VOICE_RX_BUFFER_SIZE 64
|
||||
#define VOICE_TX_BUFFER_SIZE 1
|
||||
|
||||
static const stm32_usart_t _voiceUSART = {
|
||||
.USARTx = VOICE_CONTROL_USART,
|
||||
.txGPIO = VOICE_CONTROL_TX_GPIO,
|
||||
.rxGPIO = VOICE_CONTROL_RX_GPIO,
|
||||
.IRQn = VOICE_CONTROL_USART_IRQn,
|
||||
.IRQ_Prio = VOICE_USART_IRQ_PRIORITY,
|
||||
.txDMA = nullptr,
|
||||
.txDMA_Stream = 0,
|
||||
.txDMA_Channel = 0,
|
||||
.rxDMA = nullptr,
|
||||
.rxDMA_Stream = 0,
|
||||
.rxDMA_Channel = 0,
|
||||
};
|
||||
|
||||
DEFINE_STM32_SERIAL_PORT(VoiceControl, _voiceUSART, VOICE_RX_BUFFER_SIZE,
|
||||
VOICE_TX_BUFFER_SIZE);
|
||||
|
||||
static void* _voice_usart_ctx = nullptr;
|
||||
static bool _voice_power_gpio_inited = false;
|
||||
|
||||
extern "C" void voiceControlPowerOn(void)
|
||||
{
|
||||
if (!_voice_power_gpio_inited) {
|
||||
gpio_init(VOICE_CONTROL_PWR_GPIO, GPIO_OUT, GPIO_PIN_SPEED_LOW);
|
||||
_voice_power_gpio_inited = true;
|
||||
}
|
||||
gpio_set(VOICE_CONTROL_PWR_GPIO);
|
||||
}
|
||||
|
||||
extern "C" void voiceControlPowerOff(void)
|
||||
{
|
||||
if (!_voice_power_gpio_inited) {
|
||||
gpio_init(VOICE_CONTROL_PWR_GPIO, GPIO_OUT, GPIO_PIN_SPEED_LOW);
|
||||
_voice_power_gpio_inited = true;
|
||||
}
|
||||
gpio_clear(VOICE_CONTROL_PWR_GPIO);
|
||||
|
||||
if (_voice_usart_ctx) {
|
||||
STM32SerialDriver.deinit(_voice_usart_ctx);
|
||||
_voice_usart_ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void voiceControlInit(void)
|
||||
{
|
||||
voiceControlPowerOn();
|
||||
|
||||
etx_serial_init cfg = {
|
||||
.baudrate = VOICE_CTRL_BAUD,
|
||||
.encoding = ETX_Encoding_8N1,
|
||||
.direction = ETX_Dir_TX_RX,
|
||||
.polarity = ETX_Pol_Normal,
|
||||
};
|
||||
|
||||
if (!_voice_usart_ctx) {
|
||||
_voice_usart_ctx =
|
||||
STM32SerialDriver.init(REF_STM32_SERIAL_PORT(VoiceControl), &cfg);
|
||||
} else {
|
||||
STM32SerialDriver.setBaudrate(_voice_usart_ctx, VOICE_CTRL_BAUD);
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
CI1302_voiceSwitchInitBootGuard();
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" int voiceGetByte(uint8_t* byte)
|
||||
{
|
||||
if (!_voice_usart_ctx) {
|
||||
return 0;
|
||||
}
|
||||
return STM32SerialDriver.getByte(_voice_usart_ctx, byte);
|
||||
}
|
||||
|
||||
extern "C" void voiceSerialPutc(char c)
|
||||
{
|
||||
if (!_voice_usart_ctx) {
|
||||
return;
|
||||
}
|
||||
STM32SerialDriver.sendByte(_voice_usart_ctx, (uint8_t)c);
|
||||
}
|
||||
|
||||
extern "C" void voiceSerialPutstr(uint8_t* data, uint32_t len)
|
||||
{
|
||||
if (!_voice_usart_ctx || !data || !len) {
|
||||
return;
|
||||
}
|
||||
STM32SerialDriver.sendBuffer(_voice_usart_ctx, data, len);
|
||||
}
|
||||
|
||||
extern "C" int dbgGetByte(uint8_t* byte)
|
||||
{
|
||||
(void)byte;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // VOICE_CONTROL_USART
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _CI1302_H_
|
||||
#define _CI1302_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void voiceControlPowerOn(void);
|
||||
void voiceControlPowerOff(void);
|
||||
void voiceSerialPutc(char c);
|
||||
int voiceGetByte(uint8_t* byte);
|
||||
int GetVoiceInput(uint8_t *rxchar);
|
||||
int dbgGetByte(uint8_t* byte);
|
||||
void voiceSerialPutstr(uint8_t* byte, uint32_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
extern bool VoiceGearStatus;
|
||||
extern uint8_t VoiceFlapStatus;
|
||||
extern bool VoicePowerStatus;
|
||||
extern bool VoiceRunStatus;
|
||||
extern bool MotionControlStatus;
|
||||
|
||||
void processUpdataInput(void);
|
||||
void processVoiceInput(void);
|
||||
void voiceControlInit(void);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) EdgeTX
|
||||
*
|
||||
* CI1302 voice control hooks for shared EdgeTX code (VOICE_CONTROL_SENSOR).
|
||||
* Implementation lives in drivers/CI1302.cpp (CI1302_SENSOR targets).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
|
||||
#include "edgetx_constants.h"
|
||||
#include "edgetx_types.h"
|
||||
#include "hal/switch_driver.h"
|
||||
|
||||
#define VOICE_SWITCH_COUNT 2
|
||||
|
||||
// --- shared-code integration hooks ---
|
||||
|
||||
void CI1302_voiceIntegrationPer10ms();
|
||||
void CI1302_voiceIntegrationOnFlightReset();
|
||||
|
||||
bool CI1302_voiceIntegrationMixSrcValue(mixsrc_t i, getvalue_t* val);
|
||||
bool CI1302_voiceIntegrationSourceAvailable(mixsrc_t i);
|
||||
const char* CI1302_voiceIntegrationMixSrcName(mixsrc_t idx);
|
||||
|
||||
bool CI1302_voiceIntegrationMixSrcParseYaml(const char* val, uint8_t val_len, uint32_t* out);
|
||||
bool CI1302_voiceIntegrationMixSrcWriteYaml(uint32_t val, const char** str);
|
||||
|
||||
bool CI1302_voiceIntegrationSwitchSrcParseYaml(const char* val, uint8_t val_len, int32_t* ival);
|
||||
|
||||
bool CI1302_voiceIntegrationIsSwitchAvailable(int swtch, int context, bool* available);
|
||||
bool CI1302_voiceIntegrationIsSwitchSwitchAvailable(int swtch, bool* available);
|
||||
|
||||
// --- virtual voice switches (VGR / VFL) ---
|
||||
|
||||
uint8_t CI1302_voiceSwitchExtraCount();
|
||||
void CI1302_voiceSwitchSetDefaults();
|
||||
|
||||
bool CI1302_voiceSwitchTryGetPosition(uint8_t idx, SwitchHwPos* pos);
|
||||
const char* CI1302_voiceSwitchTryGetName(uint8_t idx);
|
||||
int8_t CI1302_voiceSwitchTryGetIndexFromName(const char* name);
|
||||
bool CI1302_voiceSwitchTryGetHwType(uint8_t idx, SwitchHwType* type);
|
||||
|
||||
bool CI1302_voiceSwitchIsIndex(uint8_t idx);
|
||||
bool CI1302_voiceSwitchIsPositionAvailable(uint8_t idx, uint8_t position);
|
||||
|
||||
bool CI1302_voiceSwitchTryIsAvailable(int swtch, int context, bool* available);
|
||||
bool CI1302_voiceSwitchTryParseYamlSrc(const char* val, uint8_t val_len, int32_t* ival);
|
||||
|
||||
void CI1302_voiceSwitchInitBootGuard();
|
||||
bool CI1302_voiceSwitchBootGuardActive();
|
||||
bool CI1302_voiceSwitchSuppressMovedAudio(uint8_t idx, bool startup);
|
||||
|
||||
bool CI1302_voiceSwitchIsSwitchSource(swsrc_t swtch);
|
||||
bool CI1302_voiceSwitchShouldSuppressFunctionAudio(swsrc_t swtch, bool risingEdge);
|
||||
|
||||
// --- motion control (mixer hook) ---
|
||||
|
||||
void CI1302_voiceMotionControlApplyToInputs(uint8_t pots_offset);
|
||||
|
||||
#endif
|
||||
@@ -46,6 +46,10 @@
|
||||
#include "input_mapping.h"
|
||||
#include "trainer.h"
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
|
||||
#include "tasks.h"
|
||||
#include "tasks/mixer_task.h"
|
||||
#include "os/async.h"
|
||||
@@ -231,6 +235,9 @@ void per10ms()
|
||||
#if defined(CSD203_SENSOR) && !defined(SIMU)
|
||||
readCSD203();
|
||||
#endif
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
CI1302_voiceIntegrationPer10ms();
|
||||
#endif
|
||||
|
||||
telemetryInterrupt10ms();
|
||||
|
||||
@@ -312,6 +319,10 @@ void generalDefaultSwitches()
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
CI1302_voiceSwitchSetDefaults();
|
||||
#endif
|
||||
}
|
||||
|
||||
void generalDefaultUILanguage()
|
||||
@@ -1139,6 +1150,10 @@ void flightReset(uint8_t check)
|
||||
|
||||
START_SILENCE_PERIOD();
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
CI1302_voiceIntegrationOnFlightReset();
|
||||
#endif
|
||||
|
||||
RESET_THR_TRACE();
|
||||
|
||||
logicalSwitchesReset();
|
||||
|
||||
@@ -528,6 +528,11 @@ enum AUDIO_SOUNDS {
|
||||
AU_TIMER2_ELAPSED,
|
||||
AU_TIMER3_ELAPSED,
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
AU_HELLORADIO,
|
||||
AU_OKAY,
|
||||
#endif
|
||||
|
||||
AU_SPECIAL_SOUND_FIRST,
|
||||
AU_SPECIAL_SOUND_BEEP1 = AU_SPECIAL_SOUND_FIRST,
|
||||
AU_SPECIAL_SOUND_BEEP2,
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
#include "switches.h"
|
||||
#include "tasks/mixer_task.h"
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
|
||||
#include "hal/audio_driver.h"
|
||||
#include "os/time.h"
|
||||
|
||||
@@ -343,7 +347,13 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
|
||||
#endif
|
||||
{
|
||||
if (isRepeatDelayElapsed(functions, functionsContext, i)) {
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
bool risingEdge = !(functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << i));
|
||||
if (!CI1302_voiceSwitchShouldSuppressFunctionAudio(swtch, risingEdge) &&
|
||||
!IS_PLAYING(PLAY_INDEX)) {
|
||||
#else
|
||||
if (!IS_PLAYING(PLAY_INDEX)) {
|
||||
#endif
|
||||
if (CFN_FUNC(cfn) == FUNC_PLAY_SOUND) {
|
||||
AUDIO_PLAY(AU_SPECIAL_SOUND_FIRST + CFN_PARAM(cfn));
|
||||
} else if (CFN_FUNC(cfn) == FUNC_PLAY_VALUE) {
|
||||
@@ -365,7 +375,13 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
|
||||
case FUNC_BACKGND_MUSIC:
|
||||
if (!(newActiveFunctions & (1 << FUNCTION_BACKGND_MUSIC))) {
|
||||
newActiveFunctions |= (1 << FUNCTION_BACKGND_MUSIC);
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
bool risingEdge = !(functionsContext.activeSwitches & ((MASK_CFN_TYPE)1 << i));
|
||||
if (!CI1302_voiceSwitchShouldSuppressFunctionAudio(swtch, risingEdge) &&
|
||||
!IS_PLAYING(PLAY_INDEX)) {
|
||||
#else
|
||||
if (!IS_PLAYING(PLAY_INDEX)) {
|
||||
#endif
|
||||
playCustomFunctionFile(cfn, PLAY_INDEX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,37 @@ static void keyboardDriverRead(lv_indev_drv_t *drv, lv_indev_data_t *data)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
static bool voiceEnterPending = false;
|
||||
|
||||
static void lvglInjectKeyboardEnter()
|
||||
{
|
||||
if (!keyboardDevice) return;
|
||||
|
||||
auto obj = get_focus_obj(keyboardDevice);
|
||||
if (obj) {
|
||||
lv_group_t* g = keyboardDevice->group;
|
||||
if (g && !lv_obj_has_state(obj, LV_STATE_DISABLED)) {
|
||||
// Same sequence as lv_indev keypad Enter (press then release).
|
||||
lv_group_send_data(g, LV_KEY_ENTER);
|
||||
lv_event_send(obj, LV_EVENT_PRESSED, keyboardDevice);
|
||||
lv_event_send(obj, LV_EVENT_RELEASED, keyboardDevice);
|
||||
lv_event_send(obj, LV_EVENT_SHORT_CLICKED, keyboardDevice);
|
||||
lv_event_send(obj, LV_EVENT_CLICKED, keyboardDevice);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto w = Window::topWindow();
|
||||
dispatch_kb_event(w, EVT_KEY_BREAK(KEY_ENTER));
|
||||
}
|
||||
|
||||
void lvglRequestKeyboardEnter()
|
||||
{
|
||||
voiceEnterPending = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" void touchDriverRead(lv_indev_drv_t *drv, lv_indev_data_t *data)
|
||||
{
|
||||
#if defined(HARDWARE_TOUCH)
|
||||
@@ -370,6 +401,12 @@ void LvglWrapper::run()
|
||||
if (!updating) {
|
||||
// Normal UI loop - call lgvl timer handler
|
||||
updating = true;
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
if (voiceEnterPending) {
|
||||
voiceEnterPending = false;
|
||||
lvglInjectKeyboardEnter();
|
||||
}
|
||||
#endif
|
||||
lv_timer_handler();
|
||||
updating = false;
|
||||
} else {
|
||||
|
||||
@@ -37,3 +37,7 @@ class LvglWrapper
|
||||
LvglWrapper();
|
||||
~LvglWrapper() {}
|
||||
};
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
void lvglRequestKeyboardEnter();
|
||||
#endif
|
||||
|
||||
@@ -54,6 +54,9 @@ class SourceChoiceMenuToolbar : public MenuToolbar
|
||||
[=](int16_t index) {
|
||||
#if defined(LUMINOSITY_SENSOR)
|
||||
if (index == MIXSRC_LIGHT) return true;
|
||||
#endif
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
if (index >= MIXSRC_VGR && index <= MIXSRC_LAST_VOICE) return true;
|
||||
#endif
|
||||
return (index >= MIXSRC_MIN && index <= MIXSRC_MAX) ||
|
||||
(index >= MIXSRC_TX_VOLTAGE && index <= MIXSRC_LAST_TIMER);
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
|
||||
#include "edgetx.h"
|
||||
#include "switches.h"
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
#include "mixes.h"
|
||||
#include "os/sleep.h"
|
||||
|
||||
@@ -206,9 +209,19 @@ static bool isSourceTrimAvailable(int source) {
|
||||
}
|
||||
|
||||
static bool isSourceSwitchAvailable(int source) {
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
// VGR/VFL have their own dedicated sources
|
||||
if (CI1302_voiceSwitchIsIndex(source)) return false;
|
||||
#endif
|
||||
return SWITCH_EXISTS(source);
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
static bool isSourceVoiceAvailable(int source) {
|
||||
return CI1302_voiceIntegrationSourceAvailable(MIXSRC_VGR + source);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FUNCTION_SWITCHES)
|
||||
static bool isSourceFuncSwitchAvailable(int source) {
|
||||
return getSwitchCountInFSGroup(source + 1) > 0;
|
||||
@@ -281,6 +294,9 @@ static struct sourceAvailableCheck sourceChecks[] = {
|
||||
{ MIXSRC_FIRST_CH, MIXSRC_LAST_CH, SRC_CHANNEL_ALL, sourceIsAvailable },
|
||||
{ MIXSRC_FIRST_GVAR, MIXSRC_LAST_GVAR, SRC_GVAR, isSourceGvarAvailable },
|
||||
{ MIXSRC_TX_VOLTAGE, MIXSRC_TX_GPS, SRC_TX, sourceIsAvailable },
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
{ MIXSRC_VGR, MIXSRC_LAST_VOICE, SRC_VOICE, isSourceVoiceAvailable },
|
||||
#endif
|
||||
{ MIXSRC_FIRST_TIMER, MIXSRC_LAST_TIMER, SRC_TIMER, isSourceTimerAvailable },
|
||||
{ MIXSRC_FIRST_TELEM, MIXSRC_LAST_TELEM, SRC_TELEM, isSourceTelemAvailable },
|
||||
{ MIXSRC_NONE, MIXSRC_NONE, SRC_NONE, sourceIsAvailable },
|
||||
@@ -307,7 +323,11 @@ bool checkSourceAvailable(int source, uint32_t sourceTypes)
|
||||
bool isSourceAvailable(int source)
|
||||
{
|
||||
return checkSourceAvailable(source,
|
||||
SRC_COMMON | SRC_INPUT | SRC_LUA | SRC_HELI | SRC_CHANNEL | SRC_TX | SRC_TIMER | SRC_TELEM | SRC_NONE
|
||||
SRC_COMMON | SRC_INPUT | SRC_LUA | SRC_HELI | SRC_CHANNEL | SRC_TX | SRC_TIMER | SRC_TELEM
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
| SRC_VOICE
|
||||
#endif
|
||||
| SRC_NONE
|
||||
);
|
||||
}
|
||||
|
||||
@@ -341,6 +361,13 @@ bool isSwitchAvailable(int swtch, SwitchContext context)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
bool voiceAvailable;
|
||||
if (CI1302_voiceIntegrationIsSwitchAvailable(swtch, (int)context, &voiceAvailable)) {
|
||||
return voiceAvailable;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!SWITCH_EXISTS(swinfo.quot)) {
|
||||
return false;
|
||||
}
|
||||
@@ -418,6 +445,13 @@ static bool isSwitchSwitchAvailable(int swtch, bool invert) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
bool voiceAvailable;
|
||||
if (CI1302_voiceIntegrationIsSwitchSwitchAvailable(swtch, &voiceAvailable)) {
|
||||
return voiceAvailable;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!SWITCH_EXISTS(swinfo.quot)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#include "dataconstants.h"
|
||||
#include "edgetx_helpers.h"
|
||||
#include "edgetx.h"
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
|
||||
// Board API
|
||||
void boardInitSwitches();
|
||||
@@ -85,7 +88,11 @@ uint8_t switchGetMaxSwitches()
|
||||
|
||||
uint8_t switchGetMaxAllSwitches()
|
||||
{
|
||||
return boardGetMaxSwitches() + MAX_FLEX_SWITCHES;
|
||||
uint8_t n = boardGetMaxSwitches() + MAX_FLEX_SWITCHES;
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
n += CI1302_voiceSwitchExtraCount();
|
||||
#endif
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t switchState(uint8_t pos_idx)
|
||||
@@ -99,6 +106,13 @@ uint32_t switchState(uint8_t pos_idx)
|
||||
|
||||
SwitchHwPos switchGetPosition(uint8_t sw_idx)
|
||||
{
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
SwitchHwPos pos;
|
||||
if (CI1302_voiceSwitchTryGetPosition(sw_idx, &pos)) {
|
||||
return pos;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto idx = (int)sw_idx;
|
||||
auto max_switches = switchGetMaxSwitches();
|
||||
if (idx < max_switches) {
|
||||
@@ -137,6 +151,12 @@ static_assert(DIM(_flex_sw_canon_names) >= MAX_FLEX_SWITCHES,
|
||||
|
||||
const char* switchGetDefaultName(uint8_t sw_idx)
|
||||
{
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
if (const char* name = CI1302_voiceSwitchTryGetName(sw_idx)) {
|
||||
return name;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto idx = (int)sw_idx;
|
||||
auto max_switches = switchGetMaxSwitches();
|
||||
if (idx < max_switches) {
|
||||
@@ -161,11 +181,27 @@ int8_t switchGetIndexFromName(const char* name)
|
||||
if (strcmp(name, boardSwitchGetName(i)) == 0)
|
||||
return i;
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
{
|
||||
int8_t idx = CI1302_voiceSwitchTryGetIndexFromName(name);
|
||||
if (idx >= 0) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
SwitchHwType switchGetHwType(uint8_t sw_idx)
|
||||
{
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
SwitchHwType type;
|
||||
if (CI1302_voiceSwitchTryGetHwType(sw_idx, &type)) {
|
||||
return type;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto idx = (int)sw_idx;
|
||||
auto max_switches = switchGetMaxSwitches();
|
||||
if (idx < max_switches) {
|
||||
@@ -183,7 +219,13 @@ SwitchHwType switchGetHwType(uint8_t sw_idx)
|
||||
bool switchIsFlex(uint8_t idx)
|
||||
{
|
||||
auto max_switches = switchGetMaxSwitches();
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
if (idx < max_switches) return false;
|
||||
idx -= max_switches;
|
||||
return idx < MAX_FLEX_SWITCHES;
|
||||
#else
|
||||
return idx >= max_switches && idx < switchGetMaxAllSwitches();
|
||||
#endif
|
||||
}
|
||||
|
||||
void switchConfigFlex_raw(uint8_t idx, int8_t channel)
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "edgetx.h"
|
||||
#include "edgetx_types.h"
|
||||
#include "timers.h"
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
#include "switches.h"
|
||||
#include "input_mapping.h"
|
||||
#include "mixes.h"
|
||||
@@ -403,6 +406,16 @@ getvalue_t _getValue(mixsrc_t i, bool* valid)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
else if (i == MIXSRC_VGR || i == MIXSRC_VFL) {
|
||||
getvalue_t voiceVal = 0;
|
||||
// false also means "configured as None" here, not just "unknown source".
|
||||
if (!CI1302_voiceIntegrationMixSrcValue(i, &voiceVal) && valid != nullptr)
|
||||
*valid = false;
|
||||
return voiceVal;
|
||||
}
|
||||
#endif
|
||||
|
||||
else if (i == MIXSRC_MIN) {
|
||||
return -RESX;
|
||||
}
|
||||
@@ -642,6 +655,10 @@ void evalInputs(uint8_t mode)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
CI1302_voiceMotionControlApplyToInputs(pots_offset);
|
||||
#endif
|
||||
|
||||
// EXPOs
|
||||
applyExpos(anas, mode);
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#include "hal/adc_driver.h"
|
||||
#include "hal/audio_driver.h"
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
|
||||
#if defined(COLORLCD)
|
||||
#include "radio_tools.h"
|
||||
#endif
|
||||
@@ -291,6 +295,15 @@ static uint32_t r_mixSrcRaw(const YamlNode* node, const char* val, uint8_t val_l
|
||||
return MIXSRC_FIRST_TRIM + (val[1] - '1');
|
||||
}
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
{
|
||||
uint32_t voiceSrc;
|
||||
if (CI1302_voiceIntegrationMixSrcParseYaml(val, val_len, &voiceSrc)) {
|
||||
return voiceSrc;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
auto idx = analogLookupCanonicalIdx(ADC_INPUT_MAIN, val, val_len);
|
||||
if (idx >= 0) return idx + MIXSRC_FIRST_STICK;
|
||||
|
||||
@@ -407,6 +420,11 @@ static bool w_mixSrcRaw(const YamlNode* node, uint32_t val, yaml_writer_func wf,
|
||||
return false;
|
||||
str = closing_parenthesis;
|
||||
}
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
else if (CI1302_voiceIntegrationMixSrcWriteYaml(val, &str)) {
|
||||
// handled
|
||||
}
|
||||
#endif
|
||||
else if (val >= MIXSRC_FIRST_TIMER
|
||||
&& val <= MIXSRC_LAST_TIMER) {
|
||||
if (!wf(opaque, "Tmr", 3)) return false;
|
||||
@@ -1395,6 +1413,11 @@ static uint32_t r_swtchSrc(const YamlNode* node, const char* val, uint8_t val_le
|
||||
ival += SWSRC_FIRST_SWITCH;
|
||||
|
||||
}
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
else if (CI1302_voiceIntegrationSwitchSrcParseYaml(val, val_len, &ival)) {
|
||||
// parsed
|
||||
}
|
||||
#endif
|
||||
else if (val_len > 3
|
||||
&& val[0] == '6'
|
||||
&& val[1] == 'P'
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "hal/switch_driver.h"
|
||||
#include "edgetx.h"
|
||||
#include "switches.h"
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
|
||||
static char _static_str_buffer[32];
|
||||
static const char s_charTab[] = "_-.,";
|
||||
@@ -744,6 +747,12 @@ char *getSourceString(char (&destRef)[L], mixsrc_t idx, bool defaultOnly)
|
||||
else if (idx == MIXSRC_LIGHT) {
|
||||
strncpy(dest, STR_SRC_LIGHT, dest_len - 1);
|
||||
}
|
||||
#endif
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
else if (idx >= MIXSRC_VGR && idx <= MIXSRC_LAST_VOICE) {
|
||||
const char* voiceName = CI1302_voiceIntegrationMixSrcName(idx);
|
||||
strncpy(dest, voiceName ? voiceName : "", dest_len - 1);
|
||||
}
|
||||
#endif
|
||||
else if (idx <= MIXSRC_LAST_HELI) {
|
||||
idx -= MIXSRC_FIRST_HELI;
|
||||
|
||||
+13
-2
@@ -28,6 +28,9 @@
|
||||
#include "edgetx_constants.h"
|
||||
#include "os/sleep.h"
|
||||
#include "switches.h"
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302_voice_integration.h"
|
||||
#endif
|
||||
#include "input_mapping.h"
|
||||
#include "inactivity_timer.h"
|
||||
#include "tasks/mixer_task.h"
|
||||
@@ -303,7 +306,12 @@ int switchLookupIdx(char c)
|
||||
|
||||
int switchLookupIdx(const char* name, size_t len)
|
||||
{
|
||||
if (len < 2 || (name[0] != 'S' && name[0] != 'F')) return -1;
|
||||
if (len < 2) return -1;
|
||||
if (name[0] != 'S' && name[0] != 'F'
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
&& name[0] != 'V' // VGR/VFL
|
||||
#endif
|
||||
) return -1;
|
||||
|
||||
auto max_switches = switchGetMaxAllSwitches();
|
||||
for (int i = 0; i < max_switches; i++) {
|
||||
@@ -380,7 +388,10 @@ static uint64_t checkSwitchPosition(uint8_t idx, bool startup)
|
||||
}
|
||||
|
||||
if (!(switchesPos & result)) {
|
||||
PLAY_SWITCH_MOVED(index);
|
||||
#if defined(VOICE_CONTROL_SENSOR) && !defined(SIMU)
|
||||
if (!CI1302_voiceSwitchSuppressMovedAudio(idx, startup))
|
||||
#endif
|
||||
PLAY_SWITCH_MOVED(index);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -116,6 +116,7 @@ if (PCB STREQUAL X10)
|
||||
set(FLEXSW "2" CACHE STRING "Max flex inputs usable as switches")
|
||||
set(AUX2_SERIAL OFF)
|
||||
set(CSD203_SENSOR ON)
|
||||
set(CI1302_SENSOR ON)
|
||||
set(FLYSKY_GIMBAL ON)
|
||||
add_definitions(-DIMU_ICM42627)
|
||||
add_definitions(-DIMU_SC7U22)
|
||||
@@ -350,6 +351,14 @@ if (MULTIMODULE)
|
||||
add_definitions(-DMULTI_PROTOLIST)
|
||||
endif()
|
||||
|
||||
if(CI1302_SENSOR)
|
||||
# Via SRC (not target_sources(board ...)), same as io/frsky_firmware_update.cpp
|
||||
# below: made available to SIMU/WASM builds, not just firmware. Live-sensor
|
||||
# call sites are SIMU-guarded individually.
|
||||
set(SRC ${SRC} drivers/CI1302.cpp)
|
||||
add_definitions(-DVOICE_CONTROL_SENSOR)
|
||||
endif()
|
||||
|
||||
set(SRC ${SRC}
|
||||
io/frsky_firmware_update.cpp
|
||||
io/multi_firmware_update.cpp
|
||||
|
||||
@@ -115,6 +115,10 @@ void boardBLInit()
|
||||
#if !defined(BOOT)
|
||||
#include "edgetx.h"
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#include "drivers/CI1302.h"
|
||||
#endif
|
||||
|
||||
#if defined(PCBX12S)
|
||||
static void audio_set_rst_pin(bool set)
|
||||
{
|
||||
@@ -239,6 +243,10 @@ void boardInit()
|
||||
bluetoothInit(BLUETOOTH_DEFAULT_BAUDRATE, true);
|
||||
#endif
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
voiceControlInit();
|
||||
#endif
|
||||
|
||||
#if defined(VIDEO_SWITCH)
|
||||
videoSwitchInit();
|
||||
#endif
|
||||
|
||||
@@ -485,6 +485,20 @@
|
||||
#define FLYSKY_HALL_DMA_Stream_TX LL_DMA_STREAM_4
|
||||
#endif
|
||||
|
||||
#if defined(VOICE_CONTROL_SENSOR)
|
||||
#if defined(RADIO_V16)
|
||||
#define VOICE_CTRL_BAUD 115200
|
||||
#define VOICE_CONTROL_USART UART7
|
||||
#define VOICE_CONTROL_USART_IRQn UART7_IRQn
|
||||
#define VOICE_CONTROL_GPIO GPIOF
|
||||
#define VOICE_CONTROL_TX_GPIO GPIO_PIN(GPIOF, 7) // PF.7
|
||||
#define VOICE_CONTROL_RX_GPIO GPIO_PIN(GPIOF, 6) // PF.6
|
||||
#define VOICE_CONTROL_PWR_GPIO GPIO_PIN(GPIOH, 13) // PH.13 1=ON, 0=OFF
|
||||
#else
|
||||
#error VOICE_CONTROL_SENSOR is only supported on RADIO_V16
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Internal Module
|
||||
#define INTMODULE_PWR_GPIO GPIO_PIN(GPIOA, 8) // PA.08
|
||||
#define INTMODULE_TX_GPIO GPIO_PIN(GPIOB, 6) // PB.06
|
||||
|
||||
@@ -40,7 +40,13 @@ uint16_t getLuxSensorValue()
|
||||
|
||||
uint16_t getBatteryVoltage()
|
||||
{
|
||||
if (adcGetMaxInputs(ADC_INPUT_VBAT) < 1) return 0;
|
||||
if (adcGetMaxInputs(ADC_INPUT_VBAT) < 1) {
|
||||
// Battery is not an ADC input on this board (e.g. read from an I2C
|
||||
// sensor on hardware), so report the same default as simu_start_conversion
|
||||
uint16_t vBatWarn = BATTERY_WARN;
|
||||
if (g_eeGeneral.vBatWarn > 0) vBatWarn = g_eeGeneral.vBatWarn;
|
||||
return (vBatWarn + 5) * 10;
|
||||
}
|
||||
return anaIn(adcGetInputOffset(ADC_INPUT_VBAT));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user