diff --git a/.github/workflows/daily_packaging.yml b/.github/workflows/daily_packaging.yml
index 392faeb8a..7df688055 100644
--- a/.github/workflows/daily_packaging.yml
+++ b/.github/workflows/daily_packaging.yml
@@ -35,8 +35,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- - plucky # 25.04
- questing # 25.10
+ - resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: ppa:meshtastic/daily
diff --git a/.github/workflows/release_channels.yml b/.github/workflows/release_channels.yml
index 7f925b67c..a85979c72 100644
--- a/.github/workflows/release_channels.yml
+++ b/.github/workflows/release_channels.yml
@@ -23,8 +23,8 @@ jobs:
series:
- jammy # 22.04 LTS
- noble # 24.04 LTS
- - plucky # 25.04
- questing # 25.10
+ - resolute # 26.04 LTS
uses: ./.github/workflows/package_ppa.yml
with:
ppa_repo: |-
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index 3c996051e..3a09f6f0a 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -187,6 +187,7 @@ Logging:
LogLevel: info # debug, info, warn, error
# TraceFile: /var/log/meshtasticd.json
# JSONFile: /packets.json # File location for JSON output of decoded packets
+# JSONFileRotate: 60 # Rotate JSON file every N minutes, or 0 for no rotation
# JSONFilter: position # filter for packets to save to JSON file
# AsciiLogs: true # default if not specified is !isatty() on stdout
diff --git a/src/configuration.h b/src/configuration.h
index 451bdfb3b..a5f2cd9a9 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -149,6 +149,12 @@ along with this program. If not, see .
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
#endif
+#ifdef USE_KCT8103L_PA
+// Power Amps are often non-linear, so we can use an array of values for the power curve
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 12, 12, 11, 10, 9, 8, 7
+#endif
+
#ifdef RAK13302
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
index a063c08b5..607fd4ef7 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
@@ -120,9 +120,26 @@ void InkHUD::NodeListApplet::onRender(bool full)
// Draw the main node list
// ========================
- // Imaginary vertical line dividing left-side and right-side info
- // Long-name will crop here
- const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
+ // Leave a small gutter between long-name text and right-side card content
+ constexpr uint8_t rightContentGap = 2;
+
+ // Truncate with trailing "...", sized using the current font.
+ auto ellipsizeToWidth = [this](std::string text, uint16_t maxWidth) {
+ constexpr const char *ellipsis = "...";
+ const uint16_t ellipsisW = getTextWidth(ellipsis);
+ uint16_t textW = getTextWidth(text);
+ if (maxWidth == 0)
+ return std::string();
+ if (textW <= maxWidth)
+ return text;
+ if (ellipsisW > maxWidth)
+ return std::string();
+ while (!text.empty() && (textW + ellipsisW > maxWidth)) {
+ text.pop_back();
+ textW = getTextWidth(text);
+ }
+ return text + ellipsis;
+ };
// Y value (top) of the current card. Increases as we draw.
uint16_t cardTopY = headerDivY + padDivH;
@@ -141,7 +158,7 @@ void InkHUD::NodeListApplet::onRender(bool full)
SignalStrength &signal = card->signal;
std::string longName; // handled below
std::string shortName; // handled below
- std::string distance; // handled below;
+ std::string distance; // handled below
const uint8_t &hopsAway = card->hopsAway;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
@@ -185,41 +202,49 @@ void InkHUD::NodeListApplet::onRender(bool full)
setFont(fontMedium);
printAt(0, lineAY, shortName, LEFT, MIDDLE);
- // Print the distance
+ // Right-side labels and long name are rendered in small font.
setFont(fontSmall);
- printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
+ uint16_t rightContentW = 0;
- // If we have a direct connection to the node, draw the signal indicator
+ // Bottom row right: distance.
+ if (!distance.empty()) {
+ rightContentW = std::max(rightContentW, getTextWidth(distance));
+ printAt(width() - 1, lineBY, distance, RIGHT, MIDDLE);
+ }
+
+ // Top row right: direct-link signal only.
if (hopsAway == 0 && signal != SIGNAL_UNKNOWN) {
- uint16_t signalW = getTextWidth("Xkm"); // Indicator should be similar width to distance label
+ uint16_t signalW = getTextWidth("Xkm"); // Indicator width tuned to a short right-side label
uint16_t signalH = fontMedium.lineHeight() * 0.75;
int16_t signalY = lineAY + (fontMedium.lineHeight() / 2) - (fontMedium.lineHeight() * 0.75);
int16_t signalX = width() - signalW;
+ rightContentW = std::max(rightContentW, signalW);
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
- }
- // Otherwise, print "hops away" info, if available
- else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
- std::string hopString = to_string(node->hops_away);
- hopString += " Hop";
- if (node->hops_away != 1)
- hopString += "s"; // Append s for "Hops", rather than "Hop"
-
+ } else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
+ std::string hopString = to_string(hopsAway) + (hopsAway == 1 ? " Hop" : " Hops");
+ rightContentW = std::max(rightContentW, getTextWidth(hopString));
printAt(width() - 1, lineAY, hopString, RIGHT, MIDDLE);
}
- // Print the long name, cropping to prevent overflow onto the right-side info
- setCrop(0, 0, dividerX - 1, height());
- printAt(0, lineBY, longName, LEFT, MIDDLE);
+ // Give long names as much room as possible while still avoiding right side signal and hop space
+ const uint16_t longNameMaxW =
+ (rightContentW + rightContentGap < width()) ? (width() - rightContentW - rightContentGap) : 0;
+ const std::string longNameShown = ellipsizeToWidth(longName, longNameMaxW);
- // GFX effect: "hatch" the right edge of longName area
- // If a longName has been cropped, it will appear to fade out,
- // creating a soft barrier with the right-side info
- const int16_t hatchLeft = dividerX - 1 - (fontSmall.lineHeight());
- const int16_t hatchWidth = fontSmall.lineHeight();
- hatchRegion(hatchLeft, cardTopY, hatchWidth, cardH, 2, WHITE);
+ // Safety crop
+ setCrop(0, cardTopY, longNameMaxW, cardH);
+ printAt(0, lineBY, longNameShown, LEFT, MIDDLE);
+
+ resetCrop();
+
+ // Draw separator between cards
+ const int16_t separatorY = cardTopY + cardH - 1;
+ if (separatorY < height() - 1 && (card + 1) != cards.end()) {
+ for (int16_t xSep = 0; xSep < width(); xSep += 2)
+ drawPixel(xSep, separatorY, BLACK);
+ }
// Prepare to draw the next card
- resetCrop();
cardTopY += cardH;
// Once we've run out of screen, stop drawing cards
@@ -288,4 +313,4 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h
index 8babdba03..baee3f0f4 100644
--- a/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h
+++ b/src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.h
@@ -65,10 +65,10 @@ class NodeListApplet : public Applet, public MeshModule
// Card Dimensions
// - for rendering and for maxCards calc
- uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
+ uint8_t cardMarginH = 1; // Gap between cards (minimal to fit more rows)
uint16_t cardH = fontMedium.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
};
} // namespace NicheGraphics::InkHUD
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mesh/LoRaFEMInterface.cpp b/src/mesh/LoRaFEMInterface.cpp
index a1b56320e..bad795c3a 100644
--- a/src/mesh/LoRaFEMInterface.cpp
+++ b/src/mesh/LoRaFEMInterface.cpp
@@ -41,18 +41,33 @@ void LoRaFEMInterface::init(void)
}
#elif defined(USE_GC1109_PA)
fem_type = GC1109_PA;
+ pinMode(LORA_PA_POWER, OUTPUT);
+ digitalWrite(LORA_PA_POWER, HIGH);
#if defined(ARCH_ESP32)
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
- pinMode(LORA_PA_POWER, OUTPUT);
- digitalWrite(LORA_PA_POWER, HIGH);
delay(1);
pinMode(LORA_GC1109_PA_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_EN, HIGH);
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
+#elif defined(USE_KCT8103L_PA)
+ fem_type = KCT8103L_PA;
+ pinMode(LORA_PA_POWER, OUTPUT);
+ digitalWrite(LORA_PA_POWER, HIGH);
+#if defined(ARCH_ESP32)
+ rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
+ rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
+ rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
+#endif
+ delay(1);
+ pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
+ digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
+ pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
+ digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
+ setLnaCanControl(true);
#endif
}
@@ -73,6 +88,9 @@ void LoRaFEMInterface::setSleepModeEnable(void)
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, LOW);
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
+#elif defined(USE_KCT8103L_PA)
+ // shutdown the PA
+ digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
#endif
}
@@ -89,6 +107,9 @@ void LoRaFEMInterface::setTxModeEnable(void)
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
+#elif defined(USE_KCT8103L_PA)
+ digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
+ digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
#endif
}
@@ -109,6 +130,13 @@ void LoRaFEMInterface::setRxModeEnable(void)
#elif defined(USE_GC1109_PA)
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
+#elif defined(USE_KCT8103L_PA)
+ digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
+ if (lna_enabled) {
+ digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
+ } else {
+ digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
+ }
#endif
}
@@ -143,6 +171,15 @@ void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void)
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
#endif
+#elif defined(USE_KCT8103L_PA)
+ digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
+ rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
+ if (lna_enabled) {
+ digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
+ } else {
+ digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
+ }
+ rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
#endif
}
@@ -169,11 +206,9 @@ int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower)
}
#else
#ifdef ARCH_PORTDUINO
- size_t num_pa_points = portduino_config.num_pa_points;
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
- uint16_t tx_gain_num = num_pa_points;
+ uint16_t tx_gain_num = portduino_config.num_pa_points;
#else
- size_t num_pa_points = NUM_PA_POINTS;
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
uint16_t tx_gain_num = NUM_PA_POINTS;
#endif
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 32544a051..2e4c4d7d9 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -16,6 +16,7 @@
#endif
#include "Default.h"
#if ARCH_PORTDUINO
+#include "Throttle.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
@@ -532,6 +533,25 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
} else if (portduino_config.JSONFilename != "") {
+ if (portduino_config.JSONFileRotate != 0) {
+ static uint32_t fileage = 0;
+
+ if (portduino_config.JSONFileRotate != 0 &&
+ (fileage == 0 || !Throttle::isWithinTimespanMs(fileage, portduino_config.JSONFileRotate * 60 * 1000))) {
+ time_t timestamp = time(NULL);
+ struct tm *timeinfo;
+ char buffer[80];
+ timeinfo = localtime(×tamp);
+ strftime(buffer, 80, "%Y%m%d-%H%M%S", timeinfo);
+
+ std::string datetime(buffer);
+ if (JSONFile.is_open()) {
+ JSONFile.close();
+ }
+ JSONFile.open(portduino_config.JSONFilename + "_" + datetime, std::ios::out | std::ios::app);
+ fileage = millis();
+ }
+ }
if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) {
JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl;
}
diff --git a/src/mesh/udp/UdpMulticastHandler.h b/src/mesh/udp/UdpMulticastHandler.h
index 71bdf488f..f88b48d62 100644
--- a/src/mesh/udp/UdpMulticastHandler.h
+++ b/src/mesh/udp/UdpMulticastHandler.h
@@ -105,8 +105,7 @@ class UdpMulticastHandler final
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
uint8_t buffer[meshtastic_MeshPacket_size];
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
- udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
- return true;
+ return udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
}
private:
diff --git a/src/platform/nrf52/AsyncUDP.cpp b/src/platform/nrf52/AsyncUDP.cpp
index d119ffddb..8c937d71f 100644
--- a/src/platform/nrf52/AsyncUDP.cpp
+++ b/src/platform/nrf52/AsyncUDP.cpp
@@ -33,7 +33,10 @@ bool AsyncUDP::writeTo(const uint8_t *data, size_t len, IPAddress ip, uint16_t p
if (!udp.beginPacket(ip, port))
return false;
udp.write(data, len);
- return udp.endPacket();
+ isSending = true;
+ bool ok = udp.endPacket();
+ isSending = false;
+ return ok;
}
void AsyncUDP::close()
@@ -70,7 +73,7 @@ const uint8_t *AsyncUDPPacket::data()
int32_t AsyncUDP::runOnce()
{
- if (_onPacket && udp.parsePacket() > 0) {
+ if (_onPacket && !isSending && udp.parsePacket() > 0) {
AsyncUDPPacket packet(udp);
_onPacket(packet);
}
diff --git a/src/platform/nrf52/AsyncUDP.h b/src/platform/nrf52/AsyncUDP.h
index ecbcb12fb..eb6083bc4 100644
--- a/src/platform/nrf52/AsyncUDP.h
+++ b/src/platform/nrf52/AsyncUDP.h
@@ -31,6 +31,7 @@ class AsyncUDP : public Print, private concurrency::OSThread
private:
EthernetUDP udp;
uint16_t localPort;
+ volatile bool isSending = false;
std::function _onPacket;
virtual int32_t runOnce() override;
};
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index 24fc11078..4bbdbee7a 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -630,7 +630,9 @@ void portduinoSetup()
}
} else if (portduino_config.JSONFilename != "") {
try {
- JSONFile.open(portduino_config.JSONFilename, std::ios::out | std::ios::app);
+ if (portduino_config.JSONFileRotate == 0) {
+ JSONFile.open(portduino_config.JSONFilename, std::ios::out | std::ios::app);
+ }
} catch (std::ofstream::failure &e) {
std::cout << "*** JSONFile Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
@@ -687,6 +689,7 @@ bool loadConfig(const char *configPath)
}
portduino_config.traceFilename = yamlConfig["Logging"]["TraceFile"].as("");
portduino_config.JSONFilename = yamlConfig["Logging"]["JSONFile"].as("");
+ portduino_config.JSONFileRotate = yamlConfig["Logging"]["JSONFileRotate"].as(0);
portduino_config.JSONFilter = (_meshtastic_PortNum)yamlConfig["Logging"]["JSONFilter"].as(0);
if (yamlConfig["Logging"]["JSONFilter"].as("") == "textmessage")
portduino_config.JSONFilter = meshtastic_PortNum_TEXT_MESSAGE_APP;
diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h
index 8cc5146c6..b38cfca25 100644
--- a/src/platform/portduino/PortduinoGlue.h
+++ b/src/platform/portduino/PortduinoGlue.h
@@ -165,6 +165,7 @@ extern struct portduino_config_struct {
bool ascii_logs_explicit = false;
std::string JSONFilename;
+ int JSONFileRotate = 0;
meshtastic_PortNum JSONFilter = (_meshtastic_PortNum)0;
// Webserver
@@ -472,6 +473,9 @@ extern struct portduino_config_struct {
out << YAML::Key << "TraceFile" << YAML::Value << traceFilename;
if (JSONFilename != "") {
out << YAML::Key << "JSONFile" << YAML::Value << JSONFilename;
+ if (JSONFileRotate != 0)
+ out << YAML::Key << "JSONFileRotate" << YAML::Value << JSONFileRotate;
+
if (JSONFilter == meshtastic_PortNum_TEXT_MESSAGE_APP)
out << YAML::Key << "JSONFilter" << YAML::Value << "textmessage";
else if (JSONFilter == meshtastic_PortNum_TELEMETRY_APP)
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 8843f75c9..83443c5f3 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -65,12 +65,12 @@
// Pin mapping:
// CPS (pin 5) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH)
// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown)
-// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=bypass PA, LOW=LNA)
+// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA)
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
// KCT8103L FEM: TX/RX path switching is handled by DIO2 -> CPS pin (via SX126X_DIO2_AS_RF_SWITCH)
#define LORA_KCT8103L_PA_CSD 2 // CSD - KCT8103L chip enable (HIGH=on)
-#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=bypass PA, LOW=LNA)
+#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA)
#if HAS_TFT
#define USE_TFTDISPLAY 1
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
index 9fb825002..d30e2fcb7 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/pins_arduino.h
@@ -13,8 +13,8 @@
static const uint8_t TX = 43;
static const uint8_t RX = 44;
-static const uint8_t SDA = 5;
-static const uint8_t SCL = 6;
+static const uint8_t SDA = 6;
+static const uint8_t SCL = 17;
static const uint8_t SS = 8;
static const uint8_t MOSI = 10;
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
index 7937039ba..7c797a503 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
@@ -73,29 +73,22 @@
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
-// ---- GC1109 RF FRONT END CONFIGURATION ----
-// The Heltec Wireless Tracker V2 uses a GC1109 FEM chip with integrated PA and LNA
-// RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna
-// Measured net TX gain (non-linear due to PA compression):
-// +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out)
-// +10dB at 16-17dBm input
-// +9dB at 18-19dBm input
-// +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max)
-// Control logic (from GC1109 datasheet):
+// ---- KCT8103L RF FRONT END CONFIGURATION ----
+// The heltec_wireless_tracker_v2 uses a KCT8103L FEM chip with integrated PA and LNA
+// RF path: SX1262 -> Pi attenuator -> KCT8103L PA -> Antenna
+// Control logic (from KCT8103L datasheet):
+// Transmit PA: CSD=1, CTX=1, CPS=1
+// Receive LNA: CSD=1, CTX=0, CPS=X (21dB gain, 1.9dB NF)
+// Receive bypass: CSD=1, CTX=1, CPS=0
// Shutdown: CSD=0, CTX=X, CPS=X
-// Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF)
-// Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA)
-// Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled)
// Pin mapping:
-// CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH)
+// CPS (pin 5) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH)
// CSD (pin 4) -> GPIO4: Chip enable (HIGH=on, LOW=shutdown)
-// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
+// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA)
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
-#define USE_GC1109_PA
-#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
-#define LORA_GC1109_PA_EN 4 // CSD - GC1109 chip enable (HIGH=on)
-#define LORA_GC1109_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
+// KCT8103L FEM: TX/RX path switching is handled by DIO2 -> CPS pin (via SX126X_DIO2_AS_RF_SWITCH)
-// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
-// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
-// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
\ No newline at end of file
+#define USE_KCT8103L_PA
+#define LORA_PA_POWER 7 // VFEM_Ctrl - KCT8103L LDO power enable
+#define LORA_KCT8103L_PA_CSD 4 // CSD - KCT8103L chip enable (HIGH=on)
+#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=RX bypass, LOW=RX LNA)
\ No newline at end of file