Skip to content

Commit 31b232d

Browse files
authored
Merge pull request #3977 from LordMike/feature/aht10_usermod
Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors
2 parents 0bcdc96 + 77e6a6d commit 31b232d

7 files changed

Lines changed: 385 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ wled-update.sh
2121
/wled00/my_config.h
2222
/wled00/Release
2323
/wled00/wled00.ino.cpp
24-
/wled00/html_*.h
24+
/wled00/html_*.h

platformio_override.sample.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ build_flags = ${common.build_flags} ${esp8266.build_flags}
9494
; -D USERMOD_AUTO_SAVE
9595
; -D AUTOSAVE_AFTER_SEC=90
9696
;
97+
; Use AHT10/AHT15/AHT20 usermod
98+
; -D USERMOD_AHT10
99+
;
97100
; Use INA226 usermod
98101
; -D USERMOD_INA226
99102
;

usermods/AHT10_v2/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Usermod AHT10
2+
This Usermod is designed to read a `AHT10`, `AHT15` or `AHT20` sensor and output the following:
3+
- Temperature
4+
- Humidity
5+
6+
Configuration is performed via the Usermod menu. The following settings can be configured in the Usermod Menu:
7+
- I2CAddress: The i2c address in decimal. Set it to either 56 (0x38, the default) or 57 (0x39).
8+
- SensorType, one of:
9+
- 0 - AHT10
10+
- 1 - AHT15
11+
- 2 - AHT20
12+
- CheckInterval: Number of seconds between readings
13+
- Decimals: Number of decimals to put in the output
14+
15+
Dependencies, These must be added under `lib_deps` in your `platform.ini` (or `platform_override.ini`).
16+
- Libraries
17+
- `enjoyneering/AHT10@~1.1.0` (by [enjoyneering](https://registry.platformio.org/libraries/enjoyneering/AHT10))
18+
- `Wire`
19+
20+
## Author
21+
[@LordMike](https://github.com/LordMike)
22+
23+
# Compiling
24+
25+
To enable, compile with `USERMOD_AHT10` defined (e.g. in `platformio_override.ini`)
26+
```ini
27+
[env:aht10_example]
28+
extends = env:esp32dev
29+
build_flags =
30+
${common.build_flags} ${esp32.build_flags}
31+
-D USERMOD_AHT10
32+
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
33+
lib_deps =
34+
${esp32.lib_deps}
35+
enjoyneering/AHT10@~1.1.0
36+
```
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[env:aht10_example]
2+
extends = env:esp32dev
3+
build_flags =
4+
${common.build_flags} ${esp32.build_flags}
5+
-D USERMOD_AHT10
6+
; -D USERMOD_AHT10_DEBUG ; -- add a debug status to the info modal
7+
lib_deps =
8+
${esp32.lib_deps}
9+
enjoyneering/AHT10@~1.1.0

usermods/AHT10_v2/usermod_aht10.h

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
#pragma once
2+
3+
#include "wled.h"
4+
#include <AHT10.h>
5+
6+
#define AHT10_SUCCESS 1
7+
8+
class UsermodAHT10 : public Usermod
9+
{
10+
private:
11+
static const char _name[];
12+
13+
unsigned long _lastLoopCheck = 0;
14+
15+
bool _settingEnabled : 1; // Enable the usermod
16+
bool _mqttPublish : 1; // Publish mqtt values
17+
bool _mqttPublishAlways : 1; // Publish always, regardless if there is a change
18+
bool _mqttHomeAssistant : 1; // Enable Home Assistant docs
19+
bool _initDone : 1; // Initialization is done
20+
21+
// Settings. Some of these are stored in a different format than they're user settings - so we don't have to convert at runtime
22+
uint8_t _i2cAddress = AHT10_ADDRESS_0X38;
23+
ASAIR_I2C_SENSOR _ahtType = AHT10_SENSOR;
24+
uint16_t _checkInterval = 60000; // milliseconds, user settings is in seconds
25+
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, ..)
26+
27+
uint8_t _lastStatus = 0;
28+
float _lastHumidity = 0;
29+
float _lastTemperature = 0;
30+
31+
#ifndef WLED_MQTT_DISABLE
32+
float _lastHumiditySent = 0;
33+
float _lastTemperatureSent = 0;
34+
#endif
35+
36+
AHT10 *_aht = nullptr;
37+
38+
float truncateDecimals(float val)
39+
{
40+
return roundf(val * _decimalFactor) / _decimalFactor;
41+
}
42+
43+
void initializeAht()
44+
{
45+
if (_aht != nullptr)
46+
{
47+
delete _aht;
48+
}
49+
50+
_aht = new AHT10(_i2cAddress, _ahtType);
51+
52+
_lastStatus = 0;
53+
_lastHumidity = 0;
54+
_lastTemperature = 0;
55+
}
56+
57+
~UsermodAHT10()
58+
{
59+
delete _aht;
60+
_aht = nullptr;
61+
}
62+
63+
#ifndef WLED_DISABLE_MQTT
64+
void mqttInitialize()
65+
{
66+
// This is a generic "setup mqtt" function, So we must abort if we're not to do mqtt
67+
if (!WLED_MQTT_CONNECTED || !_mqttPublish || !_mqttHomeAssistant)
68+
return;
69+
70+
char topic[128];
71+
snprintf_P(topic, 127, "%s/temperature", mqttDeviceTopic);
72+
mqttCreateHassSensor(F("Temperature"), topic, F("temperature"), F("°C"));
73+
74+
snprintf_P(topic, 127, "%s/humidity", mqttDeviceTopic);
75+
mqttCreateHassSensor(F("Humidity"), topic, F("humidity"), F("%"));
76+
}
77+
78+
void mqttPublishIfChanged(const __FlashStringHelper *topic, float &lastState, float state, float minChange)
79+
{
80+
// Check if MQTT Connected, otherwise it will crash the 8266
81+
// Only report if the change is larger than the required diff
82+
if (WLED_MQTT_CONNECTED && _mqttPublish && (_mqttPublishAlways || fabsf(lastState - state) > minChange))
83+
{
84+
char subuf[128];
85+
snprintf_P(subuf, 127, PSTR("%s/%s"), mqttDeviceTopic, (const char *)topic);
86+
mqtt->publish(subuf, 0, false, String(state).c_str());
87+
88+
lastState = state;
89+
}
90+
}
91+
92+
// Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop.
93+
void mqttCreateHassSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement)
94+
{
95+
String t = String(F("homeassistant/sensor/")) + mqttClientID + "/" + name + F("/config");
96+
97+
StaticJsonDocument<600> doc;
98+
99+
doc[F("name")] = name;
100+
doc[F("state_topic")] = topic;
101+
doc[F("unique_id")] = String(mqttClientID) + name;
102+
if (unitOfMeasurement != "")
103+
doc[F("unit_of_measurement")] = unitOfMeasurement;
104+
if (deviceClass != "")
105+
doc[F("device_class")] = deviceClass;
106+
doc[F("expire_after")] = 1800;
107+
108+
JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device
109+
device[F("name")] = serverDescription;
110+
device[F("identifiers")] = "wled-sensor-" + String(mqttClientID);
111+
device[F("manufacturer")] = F(WLED_BRAND);
112+
device[F("model")] = F(WLED_PRODUCT_NAME);
113+
device[F("sw_version")] = versionString;
114+
115+
String temp;
116+
serializeJson(doc, temp);
117+
DEBUG_PRINTLN(t);
118+
DEBUG_PRINTLN(temp);
119+
120+
mqtt->publish(t.c_str(), 0, true, temp.c_str());
121+
}
122+
#endif
123+
124+
public:
125+
void setup()
126+
{
127+
initializeAht();
128+
}
129+
130+
void loop()
131+
{
132+
// if usermod is disabled or called during strip updating just exit
133+
// NOTE: on very long strips strip.isUpdating() may always return true so update accordingly
134+
if (!_settingEnabled || strip.isUpdating())
135+
return;
136+
137+
// do your magic here
138+
unsigned long currentTime = millis();
139+
140+
if (currentTime - _lastLoopCheck < _checkInterval)
141+
return;
142+
_lastLoopCheck = currentTime;
143+
144+
_lastStatus = _aht->readRawData();
145+
146+
if (_lastStatus == AHT10_ERROR)
147+
{
148+
// Perform softReset and retry
149+
DEBUG_PRINTLN(F("AHTxx returned error, doing softReset"));
150+
if (!_aht->softReset())
151+
{
152+
DEBUG_PRINTLN(F("softReset failed"));
153+
return;
154+
}
155+
156+
_lastStatus = _aht->readRawData();
157+
}
158+
159+
if (_lastStatus == AHT10_SUCCESS)
160+
{
161+
float temperature = truncateDecimals(_aht->readTemperature(AHT10_USE_READ_DATA));
162+
float humidity = truncateDecimals(_aht->readHumidity(AHT10_USE_READ_DATA));
163+
164+
#ifndef WLED_DISABLE_MQTT
165+
// Push to MQTT
166+
167+
// 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.
168+
// The AHT10/15/20 has an accuracy of 0.3C in the temperature readings
169+
mqttPublishIfChanged(F("temperature"), _lastTemperatureSent, temperature, 0.1f);
170+
171+
// The AHT10/15/20 has an accuracy in the humidity sensor of 2%
172+
mqttPublishIfChanged(F("humidity"), _lastHumiditySent, humidity, 0.5f);
173+
#endif
174+
175+
// Store
176+
_lastTemperature = temperature;
177+
_lastHumidity = humidity;
178+
}
179+
}
180+
181+
#ifndef WLED_DISABLE_MQTT
182+
void onMqttConnect(bool sessionPresent)
183+
{
184+
mqttInitialize();
185+
}
186+
#endif
187+
188+
uint16_t getId()
189+
{
190+
return USERMOD_ID_AHT10;
191+
}
192+
193+
void addToJsonInfo(JsonObject &root) override
194+
{
195+
// if "u" object does not exist yet wee need to create it
196+
JsonObject user = root["u"];
197+
if (user.isNull())
198+
user = root.createNestedObject("u");
199+
200+
#ifdef USERMOD_AHT10_DEBUG
201+
JsonArray temp = user.createNestedArray(F("AHT last loop"));
202+
temp.add(_lastLoopCheck);
203+
204+
temp = user.createNestedArray(F("AHT last status"));
205+
temp.add(_lastStatus);
206+
#endif
207+
208+
JsonArray jsonTemp = user.createNestedArray(F("Temperature"));
209+
JsonArray jsonHumidity = user.createNestedArray(F("Humidity"));
210+
211+
if (_lastLoopCheck == 0)
212+
{
213+
// Before first run
214+
jsonTemp.add(F("Not read yet"));
215+
jsonHumidity.add(F("Not read yet"));
216+
return;
217+
}
218+
219+
if (_lastStatus != AHT10_SUCCESS)
220+
{
221+
jsonTemp.add(F("An error occurred"));
222+
jsonHumidity.add(F("An error occurred"));
223+
return;
224+
}
225+
226+
jsonTemp.add(_lastTemperature);
227+
jsonTemp.add(F("°C"));
228+
229+
jsonHumidity.add(_lastHumidity);
230+
jsonHumidity.add(F("%"));
231+
}
232+
233+
void addToConfig(JsonObject &root)
234+
{
235+
JsonObject top = root.createNestedObject(FPSTR(_name));
236+
top[F("Enabled")] = _settingEnabled;
237+
top[F("I2CAddress")] = static_cast<uint8_t>(_i2cAddress);
238+
top[F("SensorType")] = _ahtType;
239+
top[F("CheckInterval")] = _checkInterval / 1000;
240+
top[F("Decimals")] = log10f(_decimalFactor);
241+
#ifndef WLED_DISABLE_MQTT
242+
top[F("MqttPublish")] = _mqttPublish;
243+
top[F("MqttPublishAlways")] = _mqttPublishAlways;
244+
top[F("MqttHomeAssistantDiscovery")] = _mqttHomeAssistant;
245+
#endif
246+
247+
DEBUG_PRINTLN(F("AHT10 config saved."));
248+
}
249+
250+
bool readFromConfig(JsonObject &root) override
251+
{
252+
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
253+
// 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)
254+
255+
JsonObject top = root[FPSTR(_name)];
256+
257+
bool configComplete = !top.isNull();
258+
if (!configComplete)
259+
return false;
260+
261+
bool tmpBool = false;
262+
configComplete &= getJsonValue(top[F("Enabled")], tmpBool);
263+
if (configComplete)
264+
_settingEnabled = tmpBool;
265+
266+
configComplete &= getJsonValue(top[F("I2CAddress")], _i2cAddress);
267+
configComplete &= getJsonValue(top[F("CheckInterval")], _checkInterval);
268+
if (configComplete)
269+
{
270+
if (1 <= _checkInterval && _checkInterval <= 600)
271+
_checkInterval *= 1000;
272+
else
273+
// Invalid input
274+
_checkInterval = 60000;
275+
}
276+
277+
configComplete &= getJsonValue(top[F("Decimals")], _decimalFactor);
278+
if (configComplete)
279+
{
280+
if (0 <= _decimalFactor && _decimalFactor <= 5)
281+
_decimalFactor = pow10f(_decimalFactor);
282+
else
283+
// Invalid input
284+
_decimalFactor = 100;
285+
}
286+
287+
uint8_t tmpAhtType;
288+
configComplete &= getJsonValue(top[F("SensorType")], tmpAhtType);
289+
if (configComplete)
290+
{
291+
if (0 <= tmpAhtType && tmpAhtType <= 2)
292+
_ahtType = static_cast<ASAIR_I2C_SENSOR>(tmpAhtType);
293+
else
294+
// Invalid input
295+
_ahtType = ASAIR_I2C_SENSOR::AHT10_SENSOR;
296+
}
297+
298+
#ifndef WLED_DISABLE_MQTT
299+
configComplete &= getJsonValue(top[F("MqttPublish")], tmpBool);
300+
if (configComplete)
301+
_mqttPublish = tmpBool;
302+
303+
configComplete &= getJsonValue(top[F("MqttPublishAlways")], tmpBool);
304+
if (configComplete)
305+
_mqttPublishAlways = tmpBool;
306+
307+
configComplete &= getJsonValue(top[F("MqttHomeAssistantDiscovery")], tmpBool);
308+
if (configComplete)
309+
_mqttHomeAssistant = tmpBool;
310+
#endif
311+
312+
if (_initDone)
313+
{
314+
// Reloading config
315+
initializeAht();
316+
317+
#ifndef WLED_DISABLE_MQTT
318+
mqttInitialize();
319+
#endif
320+
}
321+
322+
_initDone = true;
323+
return configComplete;
324+
}
325+
};
326+
327+
const char UsermodAHT10::_name[] PROGMEM = "AHTxx";

0 commit comments

Comments
 (0)