5 Commits

Author SHA1 Message Date
Yurii
57095d6320 Merge branch 'async' into dht 2026-02-15 01:53:24 +03:00
Yurii
47bf122cff fix: HaHelper::publishDynamicSensor() for DHT sensors fixed 2026-02-13 13:02:17 +03:00
Yurii
5797a5c4c0 fix: typo 2026-02-13 11:24:28 +03:00
Yurii
c0ff34f2e0 chore: formatting 2026-02-12 13:07:51 +03:00
Yurii
5719d5badf feat: Added support DHT11/DHT22 sensors 2026-02-12 13:00:26 +03:00
14 changed files with 170 additions and 11 deletions

View File

@@ -4,7 +4,7 @@ extra_configs = secrets.default.ini
core_dir = .pio
[env]
version = 1.6.0-async
version = 1.6.0-async-dht
framework = arduino
lib_deps = ESP32Async/AsyncTCP@^3.4.10
ESP32Async/ESPAsyncWebServer@^3.9.4
@@ -18,6 +18,7 @@ lib_deps = ESP32Async/AsyncTCP@^3.4.10
gyverlibs/GyverBlinker@^1.1.1
pstolarz/OneWireNg@^0.14.1
milesburton/DallasTemperature@^4.0.6
https://github.com/Laxilef/esp32DHT#idf5
;laxilef/TinyLogger@^1.1.1
https://github.com/Laxilef/TinyLogger#custom_handlers
lib_ignore = OneWire
@@ -33,6 +34,7 @@ build_flags = ;-mtext-section-literals
-D ARDUINOJSON_USE_DOUBLE=0
-D ARDUINOJSON_USE_LONG_LONG=0
-D TINYLOGGER_GLOBAL
-D DHT_TASK_STACK_SIZE=4096
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEFAULT_WEBSERIAL_ENABLED=${secrets.webserial_enabled}

View File

@@ -176,9 +176,10 @@ public:
objId.c_str()
);
// set device class, name, value template for bluetooth sensors
// set device class, name, value template for Bluetooth/DHT sensors
// or name & value template for another sensors
if (sSensor.type == Sensors::Type::BLUETOOTH) {
if (sSensor.type == Sensors::Type::BLUETOOTH ||
sSensor.type == Sensors::Type::DHT11 || sSensor.type == Sensors::Type::DHT22) {
// available state topic
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = doc[FPSTR(HA_STATE_TOPIC)];
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = JsonString(AVAILABILITY_SENSOR_CONN, true);

View File

@@ -292,6 +292,14 @@ protected:
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::RSSI);
break;
case Sensors::Type::DHT11:
case Sensors::Type::DHT22:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::HUMIDITY);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
@@ -319,6 +327,14 @@ protected:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DHT11:
case Sensors::Type::DHT22:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);

View File

@@ -19,7 +19,7 @@ protected:
const char* getTaskName() override {
return "Regulator";
}
/*BaseType_t getTaskCore() override {
return 1;
}*/
@@ -28,7 +28,7 @@ protected:
return 4;
}
#endif
void loop() {
if (vars.states.restarting || vars.states.upgrading) {
return;
@@ -155,7 +155,7 @@ protected:
);
float etResult = settings.heating.target + settings.equitherm.shift + sf * (
tempDelta >= 0
? pow(tempDelta, 1.0f / settings.equitherm.exponent)
? pow(tempDelta, 1.0f / settings.equitherm.exponent)
: -(pow(-(tempDelta), 1.0f / settings.equitherm.exponent))
);
@@ -204,7 +204,7 @@ protected:
}*/
float error = pidRegulator.setpoint - pidRegulator.input;
bool hasDeadband = settings.pid.deadband.enabled
bool hasDeadband = settings.pid.deadband.enabled
&& (error > -(settings.pid.deadband.thresholdHigh))
&& (error < settings.pid.deadband.thresholdLow);

View File

@@ -39,6 +39,8 @@ public:
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
BLUETOOTH = 52,
DHT11 = 53,
DHT22 = 54,
HEATING_SETPOINT_TEMP = 253,
MANUAL = 254,

View File

@@ -1,6 +1,7 @@
#include <unordered_map>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <esp32DHT.h>
#if USE_BLE
#include <NimBLEDevice.h>
@@ -50,19 +51,27 @@ protected:
class SensorsTask : public LeanTask {
public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
// OneWire
this->owInstances.reserve(2);
this->dallasInstances.reserve(2);
this->dallasSearchTime.reserve(2);
this->dallasPolling.reserve(2);
this->dallasLastPollingTime.reserve(2);
// DHT
this->dhtLastPollingTime.reserve(2);
}
~SensorsTask() {
// OneWire
this->dallasInstances.clear();
this->owInstances.clear();
this->dallasSearchTime.clear();
this->dallasPolling.clear();
this->dallasLastPollingTime.clear();
// DHT
this->dhtLastPollingTime.clear();
}
protected:
@@ -70,17 +79,26 @@ protected:
const unsigned int wirelessDisconnectTimeout = 600000u;
const unsigned short dallasSearchInterval = 60000;
const unsigned short dallasPollingInterval = 10000;
const unsigned short dhtPollingInterval = 15000;
const unsigned short globalPollingInterval = 15000;
#if USE_BLE
const unsigned int bleSetDtInterval = 7200000;
#endif
// OneWire
std::unordered_map<uint8_t, OneWire> owInstances;
std::unordered_map<uint8_t, DallasTemperature> dallasInstances;
std::unordered_map<uint8_t, unsigned long> dallasSearchTime;
std::unordered_map<uint8_t, bool> dallasPolling;
std::unordered_map<uint8_t, unsigned long> dallasLastPollingTime;
// DHT
DHT dhtInstance;
bool dhtIsPolling = false;
std::unordered_map<uint8_t, unsigned long> dhtLastPollingTime;
#if USE_BLE
// Bluetooth
std::unordered_map<uint8_t, NimBLEClient*> bleClients;
std::unordered_map<uint8_t, bool> bleSubscribed;
std::unordered_map<uint8_t, unsigned long> bleLastSetDtTime;
@@ -116,6 +134,9 @@ protected:
this->yield();
}
pollingDhtSensors();
this->yield();
if (millis() - this->globalLastPollingTime > this->globalPollingInterval) {
cleanDallasInstances();
makeDallasInstances();
@@ -399,6 +420,95 @@ protected:
}
}
void pollingDhtSensors() {
if (this->dhtIsPolling) {
// busy
return;
}
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];
if (!sSensor.enabled || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) {
continue;
}
if (sSensor.type != Sensors::Type::DHT11 && sSensor.type != Sensors::Type::DHT22) {
continue;
}
if (this->dhtLastPollingTime.count(sSensor.gpio) && millis() - this->dhtLastPollingTime[sSensor.gpio] < this->dhtPollingInterval) {
continue;
}
const auto sensorGpio = static_cast<gpio_num_t>(sSensor.gpio);
if (this->dhtInstance.getGpio() != sensorGpio) {
this->dhtInstance.reset();
this->dhtInstance.onData([this, sensorId](float humidity, float temperature) {
auto& sSensor = Sensors::settings[sensorId];
Log.straceln(
FPSTR(L_SENSORS_DHT), F("GPIO %hhu, sensor #%hhu '%s', temp: %.2f, humidity: %.2f%%"),
sSensor.gpio, sensorId, sSensor.name, temperature, humidity
);
// set temperature
Sensors::setValueById(sensorId, temperature, Sensors::ValueType::TEMPERATURE, true, true);
// set humidity
Sensors::setValueById(sensorId, humidity, Sensors::ValueType::HUMIDITY, true, true);
auto& rSensor = Sensors::results[sensorId];
if (rSensor.signalQuality < 100) {
rSensor.signalQuality++;
}
this->dhtLastPollingTime[sSensor.gpio] = millis();
this->dhtIsPolling = false;
});
this->dhtInstance.onError([this, sensorId](DHT::Status status) {
auto& sSensor = Sensors::settings[sensorId];
Log.swarningln(
FPSTR(L_SENSORS_DHT), F("GPIO %hhu, sensor #%hhu '%s': failed receiving data (err: %s)"),
sSensor.gpio, sensorId, sSensor.name, DHT::statusToString(this->dhtInstance.getStatus())
);
auto& rSensor = Sensors::results[sensorId];
if (rSensor.signalQuality > 0) {
rSensor.signalQuality--;
}
this->dhtLastPollingTime[sSensor.gpio] = millis();
this->dhtIsPolling = false;
});
DHT::Type sType = DHT::Type::DHT22;
if (sSensor.type == Sensors::Type::DHT11) {
sType = DHT::Type::DHT11;
} else if (sSensor.type == Sensors::Type::DHT22) {
sType = DHT::Type::DHT22;
}
if (this->dhtInstance.setup(sensorGpio, sType)) {
Log.sinfoln(FPSTR(L_SENSORS_DHT), F("Started on GPIO %hhu"), sSensor.gpio);
} else {
Log.swarningln(
FPSTR(L_SENSORS_DHT), F("Failed to start on GPIO %hhu (err: %s)"),
sSensor.gpio, DHT::statusToString(this->dhtInstance.getStatus())
);
}
}
this->dhtIsPolling = this->dhtInstance.poll();
break;
}
}
void pollingNtcSensors() {
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto& sSensor = Sensors::settings[sensorId];

View File

@@ -24,6 +24,7 @@ const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS[] PROGMEM = "SENSORS";
const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS";
const char L_SENSORS_DHT[] PROGMEM = "SENSORS.DHT";
const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";

View File

@@ -1927,6 +1927,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP):
case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP):
case static_cast<uint8_t>(Sensors::Type::BLUETOOTH):
case static_cast<uint8_t>(Sensors::Type::DHT11):
case static_cast<uint8_t>(Sensors::Type::DHT22):
case static_cast<uint8_t>(Sensors::Type::HEATING_SETPOINT_TEMP):
case static_cast<uint8_t>(Sensors::Type::MANUAL):
case static_cast<uint8_t>(Sensors::Type::NOT_CONFIGURED):
@@ -1943,7 +1945,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
// gpio
if (!src[FPSTR(S_GPIO)].isNull()) {
if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type != Sensors::Type::NTC_10K_TEMP) {
if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type != Sensors::Type::NTC_10K_TEMP &&
dst.type != Sensors::Type::DHT11 && dst.type != Sensors::Type::DHT22) {
if (dst.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
@@ -2084,6 +2087,10 @@ void sensorResultToJson(const uint8_t sensorId, JsonVariant dst) {
dst[FPSTR(S_BATTERY)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::BATTERY)], 1);
dst[FPSTR(S_RSSI)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::RSSI)], 0);
} else if (sSensor.type == Sensors::Type::DHT11 || sSensor.type == Sensors::Type::DHT22) {
dst[FPSTR(S_TEMPERATURE)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::TEMPERATURE)], 3);
dst[FPSTR(S_HUMIDITY)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::HUMIDITY)], 3);
} else {
dst[FPSTR(S_VALUE)] = roundf(rSensor.values[static_cast<uint8_t>(Sensors::ValueType::PRIMARY)], 3);
}

View File

@@ -248,6 +248,8 @@
"ntcTemp": "NTC 传感器",
"dallasTemp": "DALLAS 传感器",
"bluetooth": "BLE 传感器",
"dht11": "DHT11 传感器",
"dht22": "DHT22 传感器",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "通过 MQTT/API 手动配置",
"notConfigured": "未配置"

View File

@@ -248,6 +248,8 @@
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
"bluetooth": "BLE sensor",
"dht11": "DHT11 sensor",
"dht22": "DHT22 sensor",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "Manual via MQTT/API",
"notConfigured": "Not configured"

View File

@@ -248,6 +248,8 @@
"ntcTemp": "Sensore NTC",
"dallasTemp": "Sensore DALLAS",
"bluetooth": "Sensore BLE",
"dht11": "Sensore DHT11",
"dht22": "Sensore DHT22",
"heatSetpointTemp": "Riscaldamento, temp impostata",
"manual": "Manuale via MQTT/API",
"notConfigured": "Non configurato"

View File

@@ -227,6 +227,8 @@
"ntcTemp": "NTC-sensor",
"dallasTemp": "DALLAS-sensor",
"bluetooth": "BLE-sensor",
"dht11": "DHT11-sensor",
"dht22": "DHT22-sensor",
"heatSetpointTemp": "Verwarming, insteltemperatuur",
"manual": "Handmatig via MQTT/API",
"notConfigured": "Niet geconfigureerd"

View File

@@ -248,6 +248,8 @@
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
"bluetooth": "BLE датчик",
"dht11": "DHT11 датчик",
"dht22": "DHT22 датчик",
"heatSetpointTemp": "Отопление, температура уставки",
"manual": "Вручную через MQTT/API",
"notConfigured": "Не сконфигурировано"

View File

@@ -118,6 +118,8 @@
<option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option>
<option value="52" data-i18n>sensors.types.bluetooth</option>
<option value="53" data-i18n>sensors.types.dht11</option>
<option value="54" data-i18n>sensors.types.dht22</option>
<option value="253" data-i18n>sensors.types.heatSetpointTemp</option>
<option value="254" data-i18n>sensors.types.manual</option>
<option value="255" data-i18n>sensors.types.notConfigured</option>
@@ -271,27 +273,35 @@
}
switch(parseInt(event.target.value)) {
// ntc
// NTC10K
case 50:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// dallas
// OneWire
case 51:
parentGpio.classList.remove("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){7}[A-Fa-f0-9]{2}");
break;
// ble
// Bluetooth
case 52:
parentGpio.classList.add("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}");
break;
// DHT
case 53:
case 54:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// other
default:
parentGpio.classList.add("hidden");