diff --git a/src/driver/drv_aht2x.c b/src/driver/drv_aht2x.c
index e2cfef56c4..820f922a5b 100644
--- a/src/driver/drv_aht2x.c
+++ b/src/driver/drv_aht2x.c
@@ -4,58 +4,218 @@
#include "drv_aht2x.h"
#define AHT2X_I2C_ADDR (0x38 << 1)
+#define AHT2X_CRC_MODE_OFF 0
+#define AHT2X_CRC_MODE_AUTO 1
+#define AHT2X_CRC_MODE_REQUIRED 2
+#define AHT2X_MAX_MEASURE_FAILURES 3
-static byte g_aht_secondsUntilNextMeasurement = 1, g_aht_secondsBetweenMeasurements = 10,
- max_retries = 20, channel_temp = 0, channel_humid = 0;
+static byte max_retries = 20;
+static int g_aht_secondsUntilNextMeasurement = 1, g_aht_secondsBetweenMeasurements = 10, channel_temp = 0, channel_humid = 0;
static float g_temp = 0.0, g_humid = 0.0, g_calTemp = 0.0, g_calHum = 0.0;
static softI2C_t g_softI2C;
static bool isWorking = false;
+static bool g_crcSupported = false;
+static byte g_crcMode = AHT2X_CRC_MODE_OFF;
+static uint8_t g_lastStatus = 0;
+static byte g_measureFailures = 0;
-void AHT2X_SoftReset()
+static bool AHT2X_IsReadyAndCalibrated(uint8_t status)
+{
+ return ((status & AHT2X_DAT_BUSY) == 0) && ((status & AHT2X_DAT_CALIBRATED) == AHT2X_DAT_CALIBRATED);
+}
+
+static bool AHT2X_WriteCommand(uint8_t cmd)
{
- Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_CMD_RST);
+ bool ok = Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR);
+ if(ok)
+ {
+ ok = Soft_I2C_WriteByte(&g_softI2C, cmd);
+ }
Soft_I2C_Stop(&g_softI2C);
- rtos_delay_milliseconds(20);
+ return ok;
}
-void AHT2X_Initialization()
+static bool AHT2X_WriteCommand3(uint8_t cmd, uint8_t data1, uint8_t data2)
{
- AHT2X_SoftReset();
+ bool ok = Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR);
+ if(ok)
+ {
+ ok = Soft_I2C_WriteByte(&g_softI2C, cmd);
+ }
+ if(ok)
+ {
+ ok = Soft_I2C_WriteByte(&g_softI2C, data1);
+ }
+ if(ok)
+ {
+ ok = Soft_I2C_WriteByte(&g_softI2C, data2);
+ }
+ Soft_I2C_Stop(&g_softI2C);
+ return ok;
+}
- Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_CMD_INI);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_DAT_INI1);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_DAT_INI2);
+static bool AHT2X_ReadBytes(uint8_t *data, int len)
+{
+ bool ok = Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR | 1);
+ if(ok)
+ {
+ Soft_I2C_ReadBytes(&g_softI2C, data, len);
+ }
Soft_I2C_Stop(&g_softI2C);
+ return ok;
+}
- uint8_t data = AHT2X_DAT_BUSY;
+static bool AHT2X_ReadStatus(uint8_t *status)
+{
+ bool ok = Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR | 1);
+ if(ok)
+ {
+ *status = Soft_I2C_ReadByte(&g_softI2C, true);
+ g_lastStatus = *status;
+ }
+ Soft_I2C_Stop(&g_softI2C);
+ return ok;
+}
+
+static bool AHT2X_WaitReady(uint8_t *status)
+{
uint8_t attempts = 0;
+ *status = AHT2X_DAT_BUSY;
- while(data & AHT2X_DAT_BUSY)
+ while(*status & AHT2X_DAT_BUSY)
{
rtos_delay_milliseconds(20);
- Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR | 1);
- data = Soft_I2C_ReadByte(&g_softI2C, true);
- Soft_I2C_Stop(&g_softI2C);
+ if(!AHT2X_ReadStatus(status))
+ {
+ return false;
+ }
attempts++;
if(attempts > max_retries)
{
- ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Sensor timed out.", __func__);
- isWorking = false;
- break;
+ return false;
}
}
- if((data & 0x68) != 0x08)
+ return true;
+}
+
+static uint8_t AHT2X_CalcCrc8(const uint8_t *data, int len)
+{
+ uint8_t crc = AHT2X_CRC8_INIT;
+ for(int idx = 0; idx < len; idx++)
{
- ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Initialization failed.", __func__);
- isWorking = false;
+ crc ^= data[idx];
+ for(uint8_t bit = 8; bit > 0; bit--)
+ {
+ if(crc & 0x80)
+ {
+ crc = (crc << 1) ^ AHT2X_CRC8_POLY;
+ }
+ else
+ {
+ crc <<= 1;
+ }
+ }
}
- else
+ return crc;
+}
+
+static bool AHT2X_ValidateCrcIfPresent(const uint8_t *data)
+{
+ if(g_crcMode == AHT2X_CRC_MODE_OFF)
+ {
+ return true;
+ }
+
+ uint8_t crc = AHT2X_CalcCrc8(data, AHT2X_READ_LEN);
+ if(crc == data[AHT2X_READ_LEN])
+ {
+ if(!g_crcSupported)
+ {
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "AHT2X: CRC byte detected and will be validated.");
+ }
+ g_crcSupported = true;
+ return true;
+ }
+
+ if(g_crcMode == AHT2X_CRC_MODE_REQUIRED || g_crcSupported)
+ {
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "AHT2X: CRC mismatch, expected 0x%02X got 0x%02X; measurement ignored.", crc, data[AHT2X_READ_LEN]);
+ return false;
+ }
+
+ /*
+ * Some AHT1x/AHT2x modules expose only the six measurement bytes described
+ * by older datasheets/libraries. When CRC auto mode is explicitly enabled,
+ * a non-matching seventh byte is treated as "CRC not present" rather than as
+ * a sensor fault.
+ */
+ return true;
+}
+
+void AHT2X_SoftReset()
+{
+ if(!AHT2X_WriteCommand(AHT2X_CMD_RST))
+ {
+ ADDLOG_DEBUG(LOG_FEATURE_SENSOR, "%s: Sensor did not ACK soft reset.", __func__);
+ }
+ rtos_delay_milliseconds(20);
+}
+
+static bool AHT2X_TryInitializationCommand(uint8_t initCmd, const char *name)
+{
+ uint8_t status = AHT2X_DAT_BUSY;
+
+ if(!AHT2X_WriteCommand3(initCmd, AHT2X_DAT_INI1, AHT2X_DAT_INI2))
+ {
+ ADDLOG_DEBUG(LOG_FEATURE_SENSOR, "%s: %s init command was not ACKed.", __func__, name);
+ return false;
+ }
+
+ if(!AHT2X_WaitReady(&status))
+ {
+ ADDLOG_DEBUG(LOG_FEATURE_SENSOR, "%s: %s init timed out or sensor did not ACK status read.", __func__, name);
+ return false;
+ }
+
+ if(!AHT2X_IsReadyAndCalibrated(status))
+ {
+ ADDLOG_DEBUG(LOG_FEATURE_SENSOR, "%s: %s init returned unexpected status 0x%02X.", __func__, name, status);
+ return false;
+ }
+
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Initialization successful using %s command set, status 0x%02X.", __func__, name, status);
+ return true;
+}
+
+void AHT2X_Initialization()
+{
+ isWorking = false;
+ g_crcSupported = false;
+ g_lastStatus = 0;
+ g_measureFailures = 0;
+
+ AHT2X_SoftReset();
+
+ /*
+ * Keep the original AHT2X/AHT20-style init first for OTA compatibility.
+ * AHT20/AHT21-style datasheets document 0xBE 0x08 0x00; AHT10/AHT15
+ * datasheets document 0xE1 0x08 0x00. Falling back to 0xE1 fixes
+ * stricter AHT1x parts without changing existing AHT2X start commands.
+ */
+ if(AHT2X_TryInitializationCommand(AHT2X_CMD_INI, "AHT2X-compatible"))
{
- ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Initialization successful.", __func__);
isWorking = true;
+ return;
}
+
+ AHT2X_SoftReset();
+ if(AHT2X_TryInitializationCommand(AHT2X_CMD_INI_AHT1X, "AHT1X-compatible"))
+ {
+ isWorking = true;
+ return;
+ }
+
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Initialization failed. Last status 0x%02X.", __func__, g_lastStatus);
}
void AHT2X_StopDriver()
@@ -63,25 +223,43 @@ void AHT2X_StopDriver()
AHT2X_SoftReset();
}
-void AHT2X_Measure()
+static void AHT2X_RecordMeasurementFailure(const char *caller, const char *reason)
{
- uint8_t data[6] = { 0, };
+ g_measureFailures++;
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: %s Consecutive failures %u/%u.", caller, reason, (unsigned)g_measureFailures, AHT2X_MAX_MEASURE_FAILURES);
- Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_CMD_TMS);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_DAT_TMS1);
- Soft_I2C_WriteByte(&g_softI2C, AHT2X_DAT_TMS2);
- Soft_I2C_Stop(&g_softI2C);
+ if(g_measureFailures >= AHT2X_MAX_MEASURE_FAILURES)
+ {
+ ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Reinitializing sensor after %u consecutive communication failures.", caller, (unsigned)g_measureFailures);
+ AHT2X_Initialization();
+ }
+}
+void AHT2X_Measure()
+{
+ uint8_t data[AHT2X_READ_LEN_CRC] = { 0, };
+ int readLen = (g_crcMode == AHT2X_CRC_MODE_OFF) ? AHT2X_READ_LEN : AHT2X_READ_LEN_CRC;
bool ready = false;
+ /* AHT-family measurement trigger documented as 0xAC 0x33 0x00. */
+ if(!AHT2X_WriteCommand3(AHT2X_CMD_TMS, AHT2X_DAT_TMS1, AHT2X_DAT_TMS2))
+ {
+ AHT2X_RecordMeasurementFailure(__func__, "Sensor did not ACK measurement command.");
+ return;
+ }
+
+ /* AHT20/AHT30 datasheets specify an 80 ms conversion wait after trigger. */
rtos_delay_milliseconds(80);
for(uint8_t i = 0; i < 10; i++)
{
- Soft_I2C_Start(&g_softI2C, AHT2X_I2C_ADDR | 1);
- Soft_I2C_ReadBytes(&g_softI2C, data, 6);
- Soft_I2C_Stop(&g_softI2C);
+ if(!AHT2X_ReadBytes(data, readLen))
+ {
+ AHT2X_RecordMeasurementFailure(__func__, "Sensor did not ACK measurement read.");
+ return;
+ }
+
+ g_lastStatus = data[0];
if((data[0] & AHT2X_DAT_BUSY) != AHT2X_DAT_BUSY)
{
ready = true;
@@ -96,7 +274,12 @@ void AHT2X_Measure()
if(!ready)
{
- ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Measurements reading timed out.", __func__);
+ AHT2X_RecordMeasurementFailure(__func__, "Measurement read timed out.");
+ return;
+ }
+
+ if(!AHT2X_ValidateCrcIfPresent(data))
+ {
return;
}
@@ -106,21 +289,24 @@ void AHT2X_Measure()
return;
}
- uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
+ uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
- g_humid = ((float)raw_humidity * 100.0f / 1048576.0f) + g_calHum;
- g_temp = (((200.0f * (float)raw_temperature) / 1048576.0f) - 50.0f) + g_calTemp;
+ g_humid = ((float)raw_humidity * 100.0f / AHT2X_RAW_DIVISOR) + g_calHum;
+ g_temp = (((200.0f * (float)raw_temperature) / AHT2X_RAW_DIVISOR) - 50.0f) + g_calTemp;
- if(channel_temp > -1)
+ /* Preserve the existing MQTT/HA path: same channel indexes and same scaling. */
+ if(channel_temp >= 0)
{
CHANNEL_Set(channel_temp, (int)(g_temp * 10), 0);
}
- if(channel_humid > -1)
+ if(channel_humid >= 0)
{
CHANNEL_Set(channel_humid, (int)(g_humid), 0);
}
+ g_measureFailures = 0;
+ isWorking = true;
ADDLOG_INFO(LOG_FEATURE_SENSOR, "%s: Temperature:%fC Humidity:%f%%", __func__, g_temp, g_humid);
}
@@ -162,6 +348,28 @@ commandResult_t AHT2X_Cycle(const void* context, const char* cmd, const char* ar
return CMD_RES_OK;
}
+commandResult_t AHT2X_CRC(const void* context, const char* cmd, const char* args, int cmdFlags)
+{
+ Tokenizer_TokenizeString(args, TOKENIZER_ALLOW_QUOTES | TOKENIZER_DONT_EXPAND);
+ if(Tokenizer_CheckArgsCountAndPrintWarning(cmd, 1))
+ {
+ return CMD_RES_NOT_ENOUGH_ARGUMENTS;
+ }
+
+ int mode = Tokenizer_GetArgInteger(0);
+ if(mode < AHT2X_CRC_MODE_OFF || mode > AHT2X_CRC_MODE_REQUIRED)
+ {
+ ADDLOG_INFO(LOG_FEATURE_CMD, "%s: Use 0=off, 1=auto, 2=require CRC.", __func__);
+ return CMD_RES_BAD_ARGUMENT;
+ }
+
+ g_crcMode = (byte)mode;
+ g_crcSupported = false;
+ ADDLOG_INFO(LOG_FEATURE_CMD, "%s: CRC mode is %i (0=off, 1=auto, 2=require).", __func__, g_crcMode);
+
+ return CMD_RES_OK;
+}
+
commandResult_t AHT2X_Force(const void* context, const char* cmd, const char* args, int cmdFlags)
{
g_aht_secondsUntilNextMeasurement = g_aht_secondsBetweenMeasurements;
@@ -194,15 +402,20 @@ void AHT2X_Init()
AHT2X_Initialization();
//cmddetail:{"name":"AHT2X_Calibrate","args":"[DeltaTemp][DeltaHumidity]",
- //cmddetail:"descr":"Calibrate the AHT2X Sensor as Tolerance is +/-2 degrees C.",
+ //cmddetail:"descr":"Calibrate the AHT-family sensor reading without changing channel scaling.",
//cmddetail:"fn":"AHT2X_Calibrate","file":"driver/drv_aht2x.c","requires":"",
//cmddetail:"examples":"AHT2X_Calibrate -4 10
meaning -4 on current temp reading and +10 on current humidity reading"}
CMD_RegisterCommand("AHT2X_Calibrate", AHT2X_Calibrate, NULL);
//cmddetail:{"name":"AHT2X_Cycle","args":"[IntervalSeconds]",
- //cmddetail:"descr":"This is the interval between measurements in seconds, by default 1. Max is 255.",
+ //cmddetail:"descr":"This is the interval between measurements in seconds, by default 10.",
//cmddetail:"fn":"AHT2X_Cycle","file":"driver/drv_aht2x.c","requires":"",
//cmddetail:"examples":"AHT2X_Cycle 60
measurement is taken every 60 seconds"}
CMD_RegisterCommand("AHT2X_Cycle", AHT2X_Cycle, NULL);
+ //cmddetail:{"name":"AHT2X_CRC","args":"[0/1/2]",
+ //cmddetail:"descr":"Set AHT CRC handling. 0 = off/legacy six-byte read (default), 1 = automatic when a valid CRC byte is detected, 2 = require CRC validation.",
+ //cmddetail:"fn":"AHT2X_CRC","file":"driver/drv_aht2x.c","requires":"",
+ //cmddetail:"examples":"AHT2X_CRC 1
enable automatic CRC validation where the sensor provides a valid CRC byte"}
+ CMD_RegisterCommand("AHT2X_CRC", AHT2X_CRC, NULL);
//cmddetail:{"name":"AHT2X_Measure","args":"",
//cmddetail:"descr":"Retrieve OneShot measurement.",
//cmddetail:"fn":"AHT2X_Force","file":"driver/drv_aht2x.c","requires":"",
@@ -235,7 +448,7 @@ void AHT2X_AppendInformationToHTTPIndexPage(http_request_t* request, int bPreSta
hprintf255(request, "