-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors #3977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
68f6b34
Initial implementation of the AHT10/AHT15/AHT20 sensors
LordMike f71d839
Address comments
LordMike 02bf590
Add MQTT and HASS support for AHT10
LordMike 74e2732
Replace 5 booleans with 1 byte flags field
LordMike 2eff389
Handle feedback
LordMike f51da4f
Change reporting to be on significant changes
LordMike f9467ce
Fix state issue on change reporting, add override sample ini
LordMike 1dd9c67
Cleanup unecessary struct
LordMike 85702a8
Merge branch '0_15' into feature/aht10_usermod
blazoncek 77e6a6d
Revert .gitignore
blazoncek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,4 +21,4 @@ wled-update.sh | |
| /wled00/my_config.h | ||
| /wled00/Release | ||
| /wled00/wled00.ino.cpp | ||
| /wled00/html_*.h | ||
| /wled00/html_*.h | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # 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` | ||
|
|
||
| ## Author | ||
| [@LordMike](https://github.com/LordMike) | ||
|
|
||
| # 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 | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| [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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,327 @@ | ||
| #pragma once | ||
|
|
||
| #include "wled.h" | ||
| #include <AHT10.h> | ||
|
|
||
| #define AHT10_SUCCESS 1 | ||
|
|
||
| class UsermodAHT10 : public Usermod | ||
| { | ||
| private: | ||
| static const char _name[]; | ||
|
|
||
| unsigned long _lastLoopCheck = 0; | ||
|
|
||
| bool _settingEnabled : 1; // Enable the usermod | ||
| bool _mqttPublish : 1; // Publish mqtt values | ||
| bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change | ||
| bool _mqttHomeAssistant : 1; // Enable Home Assistant docs | ||
| bool _initDone : 1; // Initialization is done | ||
|
|
||
| // Settings. Some of these are stored in a different format than they're user settings - so we don't have to convert at runtime | ||
| 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; | ||
|
|
||
| #ifndef WLED_MQTT_DISABLE | ||
| float _lastHumiditySent = 0; | ||
| float _lastTemperatureSent = 0; | ||
| #endif | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| void mqttInitialize() | ||
| { | ||
| // This is a generic "setup mqtt" function, So we must abort if we're not to do mqtt | ||
| if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant) | ||
| return; | ||
|
|
||
| char topic[128]; | ||
| snprintf_P(topic, 127, "%s/temperature", mqttDeviceTopic); | ||
| mqttCreateHassSensor(F("Temperature"), topic, F("temperature"), F("°C")); | ||
|
|
||
| snprintf_P(topic, 127, "%s/humidity", mqttDeviceTopic); | ||
| mqttCreateHassSensor(F("Humidity"), topic, F("humidity"), F("%")); | ||
| } | ||
|
|
||
| void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange) | ||
| { | ||
| // Check if MQTT Connected, otherwise it will crash the 8266 | ||
| // Only report if the change is larger than the required diff | ||
| if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange)) | ||
| { | ||
| char subuf[128]; | ||
| snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic); | ||
| mqtt->publish(subuf, 0, false, String(state).c_str()); | ||
|
|
||
| lastState = state; | ||
| } | ||
| } | ||
|
|
||
| // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. | ||
| void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) | ||
| { | ||
| String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config"); | ||
|
|
||
| StaticJsonDocument<600> doc; | ||
|
|
||
| doc[F("name")] = name; | ||
| doc[F("state_topic")] = topic; | ||
| doc[F("unique_id")] = String(mqttClientID) + name; | ||
| if (unitOfMeasurement != "") | ||
| doc[F("unit_of_measurement")] = unitOfMeasurement; | ||
| if (deviceClass != "") | ||
| doc[F("device_class")] = deviceClass; | ||
| doc[F("expire_after")] = 1800; | ||
|
|
||
| JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device | ||
| device[F("name")] = serverDescription; | ||
| device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); | ||
| device[F("manufacturer")] = F(WLED_BRAND); | ||
| device[F("model")] = F(WLED_PRODUCT_NAME); | ||
| device[F("sw_version")] = versionString; | ||
|
|
||
| String temp; | ||
| serializeJson(doc, temp); | ||
| DEBUG_PRINTLN(t); | ||
| DEBUG_PRINTLN(temp); | ||
|
|
||
| mqtt->publish(t.c_str(), 0, true, temp.c_str()); | ||
| } | ||
| #endif | ||
|
|
||
| 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 (!_settingEnabled || 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(F("AHTxx returned error, doing softReset")); | ||
| if (!_aht->softReset()) | ||
| { | ||
| DEBUG_PRINTLN(F("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)); | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| // Push to MQTT | ||
|
|
||
| // We can avoid reporting if the change is insignificant. The threshold chosen is below the level of accuracy, but way above 0.01 which is the precision of the value provided. | ||
| // The AHT10/15/20 has an accuracy of 0.3C in the temperature readings | ||
| mqttPublishIfChanged(F("temperature"), _lastTemperatureSent, temperature, 0.1f); | ||
|
|
||
| // The AHT10/15/20 has an accuracy in the humidity sensor of 2% | ||
| mqttPublishIfChanged(F("humidity"), _lastHumiditySent, humidity, 0.5f); | ||
| #endif | ||
|
|
||
| // Store | ||
| _lastTemperature = temperature; | ||
| _lastHumidity = humidity; | ||
| } | ||
| } | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| void onMqttConnect(bool sessionPresent) | ||
| { | ||
| mqttInitialize(); | ||
| } | ||
| #endif | ||
|
|
||
| uint16_t getId() | ||
| { | ||
| return USERMOD_ID_AHT10; | ||
| } | ||
|
|
||
| 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("AHT last loop")); | ||
| temp.add(_lastLoopCheck); | ||
|
|
||
| temp = user.createNestedArray(F("AHT last status")); | ||
| temp.add(_lastStatus); | ||
| #endif | ||
|
|
||
| JsonArray jsonTemp = user.createNestedArray(F("Temperature")); | ||
| JsonArray jsonHumidity = user.createNestedArray(F("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")] = _settingEnabled; | ||
| top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress); | ||
| top[F("SensorType")] = _ahtType; | ||
| top[F("CheckInterval")] = _checkInterval / 1000; | ||
| top[F("Decimals")] = log10f(_decimalFactor); | ||
| #ifndef WLED_DISABLE_MQTT | ||
| top[F("MqttPublish")] = _mqttPublish; | ||
| top[F("MqttPublishAlways")] = _mqttPublishAlways; | ||
| top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant; | ||
| #endif | ||
|
|
||
| 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; | ||
|
|
||
| bool tmpBool = false; | ||
| configComplete &= getJsonValue(top[F("Enabled")], tmpBool); | ||
| if (configComplete) | ||
| _settingEnabled = tmpBool; | ||
|
|
||
| configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress); | ||
| configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval); | ||
| if (configComplete) | ||
| { | ||
| if (1 <= _checkInterval && _checkInterval <= 600) | ||
| _checkInterval *= 1000; | ||
| else | ||
| // Invalid input | ||
| _checkInterval = 60000; | ||
| } | ||
|
|
||
| configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor); | ||
| if (configComplete) | ||
| { | ||
| if (0 <= _decimalFactor && _decimalFactor <= 5) | ||
| _decimalFactor = pow10f(_decimalFactor); | ||
| else | ||
| // Invalid input | ||
| _decimalFactor = 100; | ||
| } | ||
|
|
||
| uint8_t tmpAhtType; | ||
| configComplete &= getJsonValue(top[F("SensorType")], tmpAhtType); | ||
| if (configComplete) | ||
| { | ||
| if (0 <= tmpAhtType && tmpAhtType <= 2) | ||
| _ahtType = static_cast<ASAIR_I2C_SENSOR>(tmpAhtType); | ||
| else | ||
| // Invalid input | ||
| _ahtType = ASAIR_I2C_SENSOR::AHT10_SENSOR; | ||
| } | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool); | ||
| if (configComplete) | ||
| _mqttPublish = tmpBool; | ||
|
|
||
| configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool); | ||
| if (configComplete) | ||
| _mqttPublishAlways = tmpBool; | ||
|
|
||
| configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool); | ||
| if (configComplete) | ||
| _mqttHomeAssistant = tmpBool; | ||
| #endif | ||
|
|
||
| if (_initDone) | ||
| { | ||
| // Reloading config | ||
| initializeAht(); | ||
|
|
||
| #ifndef WLED_DISABLE_MQTT | ||
| mqttInitialize(); | ||
| #endif | ||
| } | ||
|
|
||
| _initDone = true; | ||
| return configComplete; | ||
| } | ||
| }; | ||
LordMike marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.