From 68f6b3452e6f68c13fbeee187dab097b2ba8c0fb Mon Sep 17 00:00:00 2001 From: Michael Bisbjerg Date: Mon, 13 May 2024 20:09:31 +0200 Subject: [PATCH] Initial implementation of the AHT10/AHT15/AHT20 sensors --- usermods/AHT10_v2/README.md | 34 +++++ usermods/AHT10_v2/usermod_aht10.h | 201 ++++++++++++++++++++++++++++++ wled00/usermods_list.cpp | 8 ++ 3 files changed, 243 insertions(+) create mode 100644 usermods/AHT10_v2/README.md create mode 100644 usermods/AHT10_v2/usermod_aht10.h diff --git a/usermods/AHT10_v2/README.md b/usermods/AHT10_v2/README.md new file mode 100644 index 000000000..e6f16ddc4 --- /dev/null +++ b/usermods/AHT10_v2/README.md @@ -0,0 +1,34 @@ +# Usermod AHT10 +This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following: +- Temperature +- Humidity + +Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu: +- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39). +- SensorType, one of: + - 0 - AHT10 + - 1 - AHT15 + - 2 - AHT20 +- CheckInterval: Number of seconds between readings +- Decimals: Number of decimals to put in the output + +Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`). +- Libraries + - `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10)) + - `Wire` + +# Compiling + +To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`) +```ini +[env:aht10_example] +extends = env:esp32dev +build_flags = + ${common.build_flags} ${esp32.build_flags} + -D USERMOD_AHT10 + ; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal +lib_deps = + ${esp32.lib_deps} + enjoyneering/AHT10@~1.1.0 + Wire +``` diff --git a/usermods/AHT10_v2/usermod_aht10.h b/usermods/AHT10_v2/usermod_aht10.h new file mode 100644 index 000000000..d6e44a87b --- /dev/null +++ b/usermods/AHT10_v2/usermod_aht10.h @@ -0,0 +1,201 @@ +#pragma once + +#include "wled.h" +#include + +#define AHT10_SUCCESS 1 + +class UsermodAHT10 : public Usermod { + private: + static const char _name[]; + + unsigned long _lastLoopCheck = 0; + bool _initDone = false; + + // Settings. Some of these are stored in a different format than they're user settings - so we don't have to convert at runtime + bool _enabled = false; + uint8_t _i2cAddress = AHT10_ADDRESS_0X38; + ASAIR_I2C_SENSOR _ahtType = AHT10_SENSOR; + uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds + float _decimalFactor = 100; // a power of 10 factor. 1 would be no change, 10 is one decimal, 100 is two etc. User sees a power of 10 (0, 1, 2, ..) + + uint8_t _lastStatus = 0; + float _lastHumidity = 0; + float _lastTemperature = 0; + + AHT10* _aht = nullptr; + + float truncateDecimals(float val) { + return roundf(val * _decimalFactor) / _decimalFactor; + } + + void initializeAht() { + if (_aht != nullptr) { + delete _aht; + } + + _aht = new AHT10(_i2cAddress, _ahtType); + + _lastStatus = 0; + _lastHumidity = 0; + _lastTemperature = 0; + } + + ~UsermodAHT10() { + delete _aht; + _aht = nullptr; + } + + public: + void setup() { + initializeAht(); + } + + void loop() { + // if usermod is disabled or called during strip updating just exit + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!_enabled || strip.isUpdating()) + return; + + // do your magic here + unsigned long currentTime = millis(); + + if (currentTime - _lastLoopCheck < _checkInterval) + return; + _lastLoopCheck = currentTime; + + _lastStatus = _aht->readRawData(); + + if (_lastStatus == AHT10_ERROR) + { + // Perform softReset and retry + DEBUG_PRINTLN("AHTxx returned error, doing softReset"); + if (!_aht->softReset()) + { + DEBUG_PRINTLN("softReset failed"); + return; + } + + _lastStatus = _aht->readRawData(); + } + + if (_lastStatus == AHT10_SUCCESS) + { + float temperature = truncateDecimals(_aht->readTemperature(AHT10_USE_READ_DATA)); + float humidity = truncateDecimals(_aht->readHumidity(AHT10_USE_READ_DATA)); + + // Push to MQTT + + // Store + _lastHumidity = humidity; + _lastTemperature = temperature; + } + } + + void addToJsonInfo(JsonObject& root) override + { + // if "u" object does not exist yet wee need to create it + JsonObject user = root["u"]; + if (user.isNull()) + user = root.createNestedObject("u"); + +#ifdef USERMOD_AHT10_DEBUG + JsonArray temp = user.createNestedArray(F("status")); + temp.add(_lastLoopCheck); + temp.add(F(" / ")); + temp.add(_lastStatus); +#endif + + JsonArray jsonTemp = user.createNestedArray(F("AHT Temperature")); + JsonArray jsonHumidity = user.createNestedArray(F("AHT Humidity")); + + if (_lastLoopCheck == 0) + { + // Before first run + jsonTemp.add(F("Not read yet")); + jsonHumidity.add(F("Not read yet")); + return; + } + + if (_lastStatus != AHT10_SUCCESS) + { + jsonTemp.add(F("An error occurred")); + jsonHumidity.add(F("An error occurred")); + return; + } + + jsonTemp.add(_lastTemperature); + jsonTemp.add(F("°C")); + + jsonHumidity.add(_lastHumidity); + jsonHumidity.add(F("%")); + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[F("Enabled")] = _enabled; + top[F("I2CAddress")] = static_cast(_i2cAddress); + top[F("SensorType")] = _ahtType; + top[F("CheckInterval")] = _checkInterval / 1000; + top[F("Decimals")] = log10f(_decimalFactor); + + DEBUG_PRINTLN(F("AHT10 config saved.")); + } + + bool readFromConfig(JsonObject& root) override + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + if (!configComplete) + return false; + + configComplete &= getJsonValue(top["Enabled"], _enabled); + configComplete &= getJsonValue(top["I2CAddress"], _i2cAddress); + configComplete &= getJsonValue(top["CheckInterval"], _checkInterval); + if (configComplete) + { + if (1 <= _checkInterval && _checkInterval <= 600) + _checkInterval *= 1000; + else + // Invalid input + _checkInterval = 60000; + } + + configComplete &= getJsonValue(top["Decimals"], _decimalFactor); + if (configComplete) + { + if (0 <= _decimalFactor && _decimalFactor <= 5) + _decimalFactor = pow10f(_decimalFactor); + else + // Invalid input + _decimalFactor = 100; + } + + uint8_t tmpAhtType; + configComplete &= getJsonValue(top["SensorType"], tmpAhtType); + if (configComplete) + { + if (0 <= tmpAhtType && tmpAhtType <= 2) + _ahtType = static_cast(tmpAhtType); + else + // Invalid input + _ahtType = 0; + } + + if (_initDone) + { + // Reloading config + initializeAht(); + } + + _initDone = true; + return configComplete; + } +}; + +const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; \ No newline at end of file diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 3f34d18fe..91455e986 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -217,6 +217,10 @@ #include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h" #endif +#ifdef USERMOD_AHT10 + #include "../usermods/AHT10_v2/usermod_aht10.h" +#endif + void registerUsermods() { /* @@ -421,4 +425,8 @@ void registerUsermods() #ifdef USERMOD_TETRISAI usermods.add(new TetrisAIUsermod()); #endif + + #ifdef USERMOD_AHT10 + usermods.add(new UsermodAHT10()); + #endif }