#include "wled.h" #include #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; } #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(_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(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; } ~UsermodAHT10() { delete _aht; _aht = nullptr; } }; const char UsermodAHT10::_name[] PROGMEM = "AHTxx"; static UsermodAHT10 aht10_v2; REGISTER_USERMOD(aht10_v2);