-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3977 from LordMike/feature/aht10_usermod
Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors
- Loading branch information
Showing
7 changed files
with
385 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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 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 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 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 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; | ||
} | ||
}; | ||
|
||
const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; |
Oops, something went wrong.