Skip to content
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

Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors #3977

Merged
merged 10 commits into from
May 21, 2024
34 changes: 34 additions & 0 deletions usermods/AHT10_v2/README.md
Original file line number Diff line number Diff line change
@@ -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
```
LordMike marked this conversation as resolved.
Show resolved Hide resolved
201 changes: 201 additions & 0 deletions usermods/AHT10_v2/usermod_aht10.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#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 _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");
LordMike marked this conversation as resolved.
Show resolved Hide resolved
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<uint8_t>(_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<ASAIR_I2C_SENSOR>(tmpAhtType);
else
// Invalid input
_ahtType = 0;
}

if (_initDone)
{
// Reloading config
initializeAht();
}

_initDone = true;
return configComplete;
}
};
LordMike marked this conversation as resolved.
Show resolved Hide resolved

const char UsermodAHT10::_name[] PROGMEM = "AHTxx";
8 changes: 8 additions & 0 deletions wled00/usermods_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
/*
Expand Down Expand Up @@ -421,4 +425,8 @@ void registerUsermods()
#ifdef USERMOD_TETRISAI
usermods.add(new TetrisAIUsermod());
#endif

#ifdef USERMOD_AHT10
usermods.add(new UsermodAHT10());
#endif
}