From 6bb359f48e6d262b6141c65f5bb52172189b4930 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Mon, 4 May 2026 09:34:10 -0500 Subject: [PATCH 1/3] telemetry: guard CRSF ESC RPM/temp against stale data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ESC sensor slots are initialized with dataAge=ESC_DATA_INVALID (255) and age toward 255 when no frames arrive. The previous null-guard on getEscTelemetry() was dead code (it always returns a valid pointer), so stale last-seen RPM and temperature values were forwarded to CRSF regardless of data age — causing random values on disarm and when individual ESCs lose contact in multi-ESC setups. Check dataAge < ESC_DATA_INVALID before forwarding; send 0 / TEMPERATURE_INVALID_VALUE for stale or uninitialized slots instead. Fixes #11517 --- src/main/telemetry/crsf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/telemetry/crsf.c b/src/main/telemetry/crsf.c index e8214fc8c1f..b0daa334dcb 100755 --- a/src/main/telemetry/crsf.c +++ b/src/main/telemetry/crsf.c @@ -350,7 +350,7 @@ static bool crsfRpm(sbuf_t *dst) for (uint8_t i = 0; i < motorCount; i++) { const escSensorData_t *escState = getEscTelemetry(i); - crsfSerialize24(dst, (escState) ? escState->rpm : 0); + crsfSerialize24(dst, escState->dataAge < ESC_DATA_INVALID ? escState->rpm : 0); } return true; } @@ -375,7 +375,7 @@ static bool crsfTemperature(sbuf_t *dst) if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) { for (uint8_t i = 0; i < motorCount && tempCount < MAX_CRSF_TEMPS; i++) { const escSensorData_t *escState = getEscTelemetry(i); - temperatures[tempCount++] = (escState) ? escState->temperature * 10 : TEMPERATURE_INVALID_VALUE; + temperatures[tempCount++] = escState->dataAge < ESC_DATA_INVALID ? (int16_t)(escState->temperature * 10) : TEMPERATURE_INVALID_VALUE; } } #endif From b41c02805f9d19ffa729d9e481e68579ff5953e7 Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Mon, 4 May 2026 09:50:29 -0500 Subject: [PATCH 2/3] telemetry: use ESC_DATA_MAX_AGE threshold for CRSF staleness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ESC_DATA_INVALID (255) is too lenient as a freshness threshold — with 50ms poll cycles it allows stale data to persist 12+ seconds (single motor) before being suppressed. All other ESC data consumers (OSD, battery, jetiexbus, sbus2) use ESC_DATA_MAX_AGE (10) as the freshness cutoff, giving a ~500ms window per motor. Switch CRSF RPM and temperature guards to match. --- src/main/telemetry/crsf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/telemetry/crsf.c b/src/main/telemetry/crsf.c index b0daa334dcb..4762b550859 100755 --- a/src/main/telemetry/crsf.c +++ b/src/main/telemetry/crsf.c @@ -350,7 +350,7 @@ static bool crsfRpm(sbuf_t *dst) for (uint8_t i = 0; i < motorCount; i++) { const escSensorData_t *escState = getEscTelemetry(i); - crsfSerialize24(dst, escState->dataAge < ESC_DATA_INVALID ? escState->rpm : 0); + crsfSerialize24(dst, escState->dataAge <= ESC_DATA_MAX_AGE ? escState->rpm : 0); } return true; } @@ -375,7 +375,7 @@ static bool crsfTemperature(sbuf_t *dst) if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) { for (uint8_t i = 0; i < motorCount && tempCount < MAX_CRSF_TEMPS; i++) { const escSensorData_t *escState = getEscTelemetry(i); - temperatures[tempCount++] = escState->dataAge < ESC_DATA_INVALID ? (int16_t)(escState->temperature * 10) : TEMPERATURE_INVALID_VALUE; + temperatures[tempCount++] = escState->dataAge <= ESC_DATA_MAX_AGE ? (int16_t)(escState->temperature * 10) : TEMPERATURE_INVALID_VALUE; } } #endif From 76a6748d79e471755e371fe9502b8858206c2d5d Mon Sep 17 00:00:00 2001 From: Ray Morris Date: Mon, 4 May 2026 14:53:32 -0500 Subject: [PATCH 3/3] telemetry: use getTelemetryMotorCount() for CRSF ESC frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In listenOnly mode only escSensorData[0] is ever populated. crsfRpm() and crsfTemperature() were using getMotorCount() to size their frames, emitting N-1 zero/invalid slots alongside the single real motor entry — confusing ground station displays. Export getTelemetryMotorCount() from esc_sensor and use it in both CRSF functions so the frame contains only the slots that the ESC sensor actually populates. --- src/main/sensors/esc_sensor.c | 2 +- src/main/sensors/esc_sensor.h | 1 + src/main/telemetry/crsf.c | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/sensors/esc_sensor.c b/src/main/sensors/esc_sensor.c index bc77c281c79..a06814a6846 100644 --- a/src/main/sensors/esc_sensor.c +++ b/src/main/sensors/esc_sensor.c @@ -84,7 +84,7 @@ PG_RESET_TEMPLATE(escSensorConfig_t, escSensorConfig, .listenOnly = SETTING_ESC_SENSOR_LISTEN_ONLY_DEFAULT, ); -static int getTelemetryMotorCount(void) +int getTelemetryMotorCount(void) { if (escSensorConfig()->listenOnly) { return 1; diff --git a/src/main/sensors/esc_sensor.h b/src/main/sensors/esc_sensor.h index a5958c85c16..6bccd340960 100644 --- a/src/main/sensors/esc_sensor.h +++ b/src/main/sensors/esc_sensor.h @@ -48,3 +48,4 @@ void escSensorUpdate(timeUs_t currentTimeUs); escSensorData_t * escSensorGetData(void); escSensorData_t * getEscTelemetry(uint8_t esc); uint32_t computeRpm(int16_t erpm); +int getTelemetryMotorCount(void); diff --git a/src/main/telemetry/crsf.c b/src/main/telemetry/crsf.c index 4762b550859..686d2b1cbc5 100755 --- a/src/main/telemetry/crsf.c +++ b/src/main/telemetry/crsf.c @@ -335,7 +335,7 @@ int24_t rpm_value[]; // 1 - 19 RPM values with negative ones representing static bool crsfRpm(sbuf_t *dst) { const uint8_t MAX_CRSF_RPM_VALUES = 19; // CRSF protocol limit: 1-19 RPM values - uint8_t motorCount = getMotorCount(); + uint8_t motorCount = getTelemetryMotorCount(); if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) { // Enforce protocol limit @@ -371,7 +371,7 @@ static bool crsfTemperature(sbuf_t *dst) int16_t temperatures[20]; #ifdef USE_ESC_SENSOR - uint8_t motorCount = getMotorCount(); + uint8_t motorCount = getTelemetryMotorCount(); if (STATE(ESC_SENSOR_ENABLED) && motorCount > 0) { for (uint8_t i = 0; i < motorCount && tempCount < MAX_CRSF_TEMPS; i++) { const escSensorData_t *escState = getEscTelemetry(i);