diff --git a/src/configuration.h b/src/configuration.h
index 7b7c603ba96..14281334d28 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -219,6 +219,10 @@ along with this program. If not, see .
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
+#define SHT21_ADDR 0x40 // same address as INA_ADDR
+#ifndef HAS_SHT21
+#define HAS_SHT21 0 // set to one to override detection of INA219/260 devices at this address
+#endif
#define PMSA003I_ADDR 0x12
#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 0edd5e7a781..ed78de23c93 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -32,6 +32,7 @@ class ScanI2C
MAX17048,
MCP9808,
SHT31,
+ SHT21,
SHT4X,
SHTC3,
LPS22HB,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 445e9342c66..00ab27d9921 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -308,7 +308,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
#endif
#if !defined(M5STACK_UNITC6L)
+#if !HAS_SHT21
case INA_ADDR:
+#endif
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
@@ -401,6 +403,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
+#if HAS_SHT21
+ case SHT21_ADDR:
+ // SHT21 / HTU21D family commonly at 0x40
+ logFoundDevice("SHT21/HTU21D", (uint8_t)addr.address);
+ type = SHT21;
+ break;
+#endif
+
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3", (uint8_t)addr.address)
case RCWL9620_ADDR:
// get MAX30102 PARTID
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index dc9d876dcc6..77afd38840f 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -103,7 +103,9 @@ typedef enum _meshtastic_TelemetrySensorType {
/* TSL2561 light sensor */
meshtastic_TelemetrySensorType_TSL2561 = 44,
/* BH1750 light sensor */
- meshtastic_TelemetrySensorType_BH1750 = 45
+ meshtastic_TelemetrySensorType_BH1750 = 45,
+ /* STH21 Temperature and R. Humidity sensor */
+ meshtastic_TelemetrySensorType_SHT21 = 46
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -461,8 +463,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT21
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT21+1))
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 140c2c17e7b..dea9276e054 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -69,6 +69,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/SHT31Sensor.h"
#endif
+#if HAS_SHT21
+#include "Sensor/SHT21Sensor.h"
+#endif
+
#if __has_include()
#include "Sensor/LPS22HBSensor.h"
#endif
@@ -202,6 +206,9 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::SHT31);
#endif
+#if HAS_SHT21
+ addSensor(i2cScanner, ScanI2C::DeviceType::SHT21);
+#endif
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::LPS22HB);
#endif
diff --git a/src/modules/Telemetry/Sensor/SHT21Sensor.cpp b/src/modules/Telemetry/Sensor/SHT21Sensor.cpp
new file mode 100644
index 00000000000..e28f1af1dfb
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SHT21Sensor.cpp
@@ -0,0 +1,85 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "SHT21Sensor.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+#include
+
+// SHT21 I2C address and commands (use no-hold commands to avoid clock-stretch issues)
+static const uint8_t CMD_TEMP_NOHOLD = 0xF3;
+static const uint8_t CMD_HUM_NOHOLD = 0xF5;
+
+static bool read_sht21_raw(TwoWire *bus, uint8_t addr, uint8_t cmd, uint16_t &raw, uint16_t waitMs)
+{
+ bus->beginTransmission(addr);
+ bus->write(cmd);
+ if (bus->endTransmission() != 0)
+ return false;
+ // Wait for measurement to complete (no-hold mode)
+ delay(waitMs);
+ int toRead = 3; // two data bytes + CRC
+ bus->requestFrom((int)addr, toRead);
+ if (bus->available() < 2)
+ return false;
+ uint8_t msb = bus->read();
+ uint8_t lsb = bus->read();
+ if (bus->available())
+ (void)bus->read();
+
+ raw = ((uint16_t)msb << 8) | lsb;
+ // clear status bits per datasheet
+ raw &= ~0x0003;
+ return true;
+}
+
+SHT21Sensor::SHT21Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT21, "SHT21") {}
+
+bool SHT21Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+ LOG_INFO("Init sensor: %s", sensorName);
+ i2c = bus;
+ i2c_addr = dev->address.address ? dev->address.address : SHT21_ADDR;
+
+ uint16_t raw;
+ // use no-hold command and wait for conversion (max ~85ms for temp)
+ if (!read_sht21_raw(i2c, i2c_addr, CMD_TEMP_NOHOLD, raw, 85)) {
+ return false;
+ }
+ float temp = -46.85f + 175.72f * ((float)raw / 65536.0f);
+ // basic sanity check
+ if (temp < -50.0f || temp > 150.0f)
+ return false;
+
+ status = true;
+ initI2CSensor();
+ return true;
+}
+
+bool SHT21Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ measurement->variant.environment_metrics.has_temperature = true;
+ measurement->variant.environment_metrics.has_relative_humidity = true;
+
+ LOG_DEBUG("SHT21 getMetrics");
+
+ uint16_t raw_t = 0, raw_h = 0;
+ if (!read_sht21_raw(i2c, i2c_addr, CMD_TEMP_NOHOLD, raw_t, 85))
+ return false;
+ // humidity max conversion time ~29ms
+ if (!read_sht21_raw(i2c, i2c_addr, CMD_HUM_NOHOLD, raw_h, 30))
+ return false;
+
+ float temp = -46.85f + 175.72f * ((float)raw_t / 65536.0f);
+ float rh = -6.0f + 125.0f * ((float)raw_h / 65536.0f);
+
+ // Round to 2 decimal places for telemetry
+ measurement->variant.environment_metrics.temperature = roundf(temp * 100.0f) / 100.0f;
+ measurement->variant.environment_metrics.relative_humidity = roundf(rh * 100.0f) / 100.0f;
+
+ return true;
+}
+
+#endif
diff --git a/src/modules/Telemetry/Sensor/SHT21Sensor.h b/src/modules/Telemetry/Sensor/SHT21Sensor.h
new file mode 100644
index 00000000000..80760dc6deb
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SHT21Sensor.h
@@ -0,0 +1,22 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class SHT21Sensor : public TelemetrySensor
+{
+ private:
+ TwoWire *i2c = nullptr;
+ uint8_t i2c_addr = 0;
+ bool status = false;
+
+ public:
+ SHT21Sensor();
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+};
+
+#endif