muwerk mupplet Sensor Library
muwerk applets; mupplets: functional units that support specific hardware or reusable applications for sensors
All Classes Functions Enumerations Enumerator Pages
mup_temphum_dht.h
1// mup_temphum_dht.h
2#pragma once
3
4#include "scheduler.h"
5#include "sensors.h"
6
7namespace ustd {
8
9#if defined(__ESP32__) || defined(__ESP__)
10#define G_INT_ATTR IRAM_ATTR
11#else
12#define G_INT_ATTR
13#endif
14
15#define USTD_DHT_MAX_PIRQS (10)
16
18enum DhtProtState {
19 NONE,
20 START_PULSE_START,
21 START_PULSE_END,
22 REPL_PULSE_START,
23 REPL_PULSE_START_H,
24 DATA_ACQUISITION_INTRO_START,
25 DATA_ACQUISITION_INTRO_END,
26 DATA_ACQUISITION,
27 DATA_ABORT,
28 DATA_OK
29};
30
32enum DhtFailureCode {
33 OK = 0,
34 BAD_START_PULSE_LEVEL = 1,
35 BAD_REPLY_PULSE_LENGTH = 2,
36 BAD_START_PULSE_END_LEVEL = 3,
37 BAD_REPLY_PULSE_LENGTH2 = 4,
38 BAD_START_PULSE_END_LEVEL2 = 5,
39 BAD_DATA_INTRO_PULSE_LENGTH = 6,
40 BAD_DATA_BIT_LENGTH = 7
41};
42
43volatile DhtProtState pDhtState[USTD_DHT_MAX_PIRQS] = {DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE,
44 DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE, DhtProtState::NONE};
45volatile unsigned long pDhtBeginIrqTimer[USTD_DHT_MAX_PIRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
46volatile uint8_t pDhtPortIrq[USTD_DHT_MAX_PIRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
47volatile uint8_t pDhtBitCounter[USTD_DHT_MAX_PIRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
48volatile DhtFailureCode pDhtFailureCode[USTD_DHT_MAX_PIRQS] = {DhtFailureCode::OK, DhtFailureCode::OK, DhtFailureCode::OK,
49 DhtFailureCode::OK, DhtFailureCode::OK, DhtFailureCode::OK, DhtFailureCode::OK, DhtFailureCode::OK, DhtFailureCode::OK,
50 DhtFailureCode::OK};
51volatile int pDhtFailureData[USTD_DHT_MAX_PIRQS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
52volatile uint8_t sensorDataBytes[USTD_DHT_MAX_PIRQS * 5];
53
54unsigned long dhtWakeUpPulse = 22000; // (1) The intial low-pulse of 22ms that awakens the DHT. Note: manufacturer doc is wrong! Says 2ms.
55int dhtInitialDelay = 20; // (2) after about 20ms(!) low by mcpu, at least dhtInitialDelay uS high is set by mcu before switching to input.
56int dhtSignalInitDelta = 25; // (3.1, 3.2) deviation for initial 80us low + high by dht, initialDeals uS get substracted from this.
57int dhtSignalIntroDelta = 25; // (4) deviation for initial 50us low by dht
58int dhtSignalDelta = 15; // (5) uS count for max signal length deviation, 27+-dhtSignalDelta is low, 70+-dhtSignalDelta is high-bit
59
60void G_INT_ATTR ustd_dht_pirq_master(uint8_t irqno) {
61 long dt;
62 switch (pDhtState[irqno]) {
63 case DhtProtState::NONE:
64 return;
65 case DhtProtState::START_PULSE_START:
66 return;
67 case DhtProtState::START_PULSE_END: // start of (3.1)
68 if (digitalRead(pDhtPortIrq[irqno]) == LOW) {
69 pDhtBeginIrqTimer[irqno] = micros();
70 pDhtState[irqno] = DhtProtState::REPL_PULSE_START;
71 pDhtBitCounter[irqno] = 0;
72 for (int i = 0; i < 5; i++)
73 sensorDataBytes[irqno * 5 + i] = 0;
74 return;
75 } else {
76 pDhtFailureCode[irqno] = DhtFailureCode::BAD_START_PULSE_LEVEL;
77 pDhtState[irqno] = DhtProtState::DATA_ABORT;
78 }
79 break;
80 case DhtProtState::REPL_PULSE_START: // start of (3.2)
81 if (digitalRead(pDhtPortIrq[irqno]) == HIGH) {
82 dt = timeDiff(pDhtBeginIrqTimer[irqno], micros());
83 if (dt > 80 - dhtInitialDelay - dhtSignalInitDelta && dt < 80 + dhtSignalInitDelta) { // First alive signal of 80us LOW +- dhtSignalInitDelta uS.
84 pDhtBeginIrqTimer[irqno] = micros();
85 pDhtState[irqno] = DhtProtState::REPL_PULSE_START_H;
86 return;
87 } else {
88 pDhtFailureCode[irqno] = DhtFailureCode::BAD_REPLY_PULSE_LENGTH;
89 pDhtFailureData[irqno] = dt;
90 pDhtState[irqno] = DhtProtState::DATA_ABORT;
91 }
92 } else {
93 pDhtFailureCode[irqno] = DhtFailureCode::BAD_START_PULSE_END_LEVEL;
94 pDhtState[irqno] = DhtProtState::DATA_ABORT;
95 }
96 break;
97 case DhtProtState::REPL_PULSE_START_H: // end of (3.2), start of data transmission (4)
98 if (digitalRead(pDhtPortIrq[irqno]) == LOW) {
99 dt = timeDiff(pDhtBeginIrqTimer[irqno], micros());
100 if (dt > 80 - dhtSignalInitDelta && dt < 80 + dhtSignalInitDelta) { // Sec alive signal of 80us HIGH +- dhtSignalInitDelta uS.
101 pDhtBeginIrqTimer[irqno] = micros();
102 pDhtState[irqno] = DhtProtState::DATA_ACQUISITION_INTRO_END;
103 return;
104 } else {
105 pDhtFailureCode[irqno] = DhtFailureCode::BAD_REPLY_PULSE_LENGTH2;
106 pDhtFailureData[irqno] = dt;
107 pDhtState[irqno] = DhtProtState::DATA_ABORT;
108 }
109 } else {
110 pDhtFailureCode[irqno] = DhtFailureCode::BAD_START_PULSE_END_LEVEL2;
111 pDhtState[irqno] = DhtProtState::DATA_ABORT;
112 }
113 break;
114
115 case DhtProtState::DATA_ACQUISITION_INTRO_START: // start of (4)
116 if (digitalRead(pDhtPortIrq[irqno]) == LOW) {
117 pDhtBeginIrqTimer[irqno] = micros();
118 pDhtState[irqno] = DhtProtState::DATA_ACQUISITION_INTRO_END;
119 }
120 case DhtProtState::DATA_ACQUISITION_INTRO_END: // end of (4)
121 if (digitalRead(pDhtPortIrq[irqno]) == HIGH) {
122 dt = timeDiff(pDhtBeginIrqTimer[irqno], micros());
123 if (dt > 50 - dhtSignalIntroDelta && dt < 50 + dhtSignalIntroDelta) { // Intro-marker 50us LOW +- dhtSignalIntroDelta uS.
124 pDhtBeginIrqTimer[irqno] = micros();
125 pDhtState[irqno] = DhtProtState::DATA_ACQUISITION;
126 return;
127 } else {
128 pDhtFailureCode[irqno] = DhtFailureCode::BAD_DATA_INTRO_PULSE_LENGTH;
129 pDhtFailureData[irqno] = dt;
130 pDhtState[irqno] = DhtProtState::DATA_ABORT;
131 }
132 }
133 break;
134 case DhtProtState::DATA_ACQUISITION: // end of (5), data bit of either 27us (0bit) or 70us (1bit) received.
135 if (digitalRead(pDhtPortIrq[irqno]) == LOW) {
136 dt = timeDiff(pDhtBeginIrqTimer[irqno], micros());
137 if (dt > 27 - dhtSignalDelta && dt < 27 + dhtSignalDelta) { // Zero-bit 26-28uS
138 // nothing
139 } else {
140 if (dt > 70 - dhtSignalDelta && dt < 70 + dhtSignalDelta) { // One-bit 70uS
141 uint8_t byte = pDhtBitCounter[irqno] / 8;
142 uint8_t bit = pDhtBitCounter[irqno] % 8;
143 sensorDataBytes[irqno * 5 + byte] |= 1 << (7 - bit);
144 } else {
145 pDhtFailureCode[irqno] = DhtFailureCode::BAD_DATA_BIT_LENGTH;
146 pDhtFailureData[irqno] = dt;
147 pDhtState[irqno] = DhtProtState::DATA_ABORT;
148 return;
149 }
150 }
151 ++pDhtBitCounter[irqno];
152 if (pDhtBitCounter[irqno] == 40) { // after 40 bit received, data (2byte humidity, 2byte temperature, 1byte crc) complete.
153 pDhtState[irqno] = DhtProtState::DATA_OK;
154 } else {
155 pDhtBeginIrqTimer[irqno] = micros();
156 pDhtState[irqno] = DhtProtState::DATA_ACQUISITION_INTRO_END;
157 }
158 }
159 break;
160 default:
161 return;
162 }
163}
164
165void G_INT_ATTR ustd_dht_pirq0() {
166 ustd_dht_pirq_master(0);
167}
168void G_INT_ATTR ustd_dht_pirq1() {
169 ustd_dht_pirq_master(1);
170}
171void G_INT_ATTR ustd_dht_pirq2() {
172 ustd_dht_pirq_master(2);
173}
174void G_INT_ATTR ustd_dht_pirq3() {
175 ustd_dht_pirq_master(3);
176}
177void G_INT_ATTR ustd_dht_pirq4() {
178 ustd_dht_pirq_master(4);
179}
180void G_INT_ATTR ustd_dht_pirq5() {
181 ustd_dht_pirq_master(5);
182}
183void G_INT_ATTR ustd_dht_pirq6() {
184 ustd_dht_pirq_master(6);
185}
186void G_INT_ATTR ustd_dht_pirq7() {
187 ustd_dht_pirq_master(7);
188}
189void G_INT_ATTR ustd_dht_pirq8() {
190 ustd_dht_pirq_master(8);
191}
192void G_INT_ATTR ustd_dht_pirq9() {
193 ustd_dht_pirq_master(9);
194}
195
196void (*ustd_dht_irq_table[USTD_DHT_MAX_PIRQS])() = {ustd_dht_pirq0, ustd_dht_pirq1, ustd_dht_pirq2, ustd_dht_pirq3,
197 ustd_dht_pirq4, ustd_dht_pirq5, ustd_dht_pirq6, ustd_dht_pirq7,
198 ustd_dht_pirq8, ustd_dht_pirq9};
199
200// clang-format off
267// clang-format on
268
270 private:
271 String DHT_VERSION = "0.1.0";
272 Scheduler *pSched;
273 int tID;
274 String name;
275 uint8_t port;
276 uint8_t interruptIndex;
277 double temperatureValue, humidityValue;
278 bool bActive = false;
279 unsigned long stateMachineTimeout = 0;
280 unsigned long lastPoll = 0;
281 unsigned long startPulseStartUs = 0;
282 unsigned long errs = 0;
283 unsigned long oks = 0;
284 unsigned long sensorPollRate = 3;
285 uint8_t iPin = 255;
286
287 public:
288 enum DHTType { DHT11,
289 DHT22 };
290 DHTType dhtType;
291 enum FilterMode { FAST,
292 MEDIUM,
293 LONGTERM };
294 FilterMode filterMode;
295 ustd::sensorprocessor temperatureSensor = ustd::sensorprocessor(4, 600, 0.005);
296 ustd::sensorprocessor humiditySensor = ustd::sensorprocessor(4, 600, 0.005);
297
298 TempHumDHT(String name, uint8_t port, uint8_t interruptIndex, DHTType dhtType = DHTType::DHT22, FilterMode filterMode = FilterMode::MEDIUM)
299 : name(name), port(port), interruptIndex(interruptIndex), dhtType(dhtType), filterMode(filterMode) {
308 if (interruptIndex >= 0 && interruptIndex < USTD_DHT_MAX_PIRQS) {
309 iPin = digitalPinToInterrupt(port);
310 attachInterrupt(iPin, ustd_dht_irq_table[interruptIndex], CHANGE);
311 pDhtPortIrq[interruptIndex] = port;
312 pDhtState[interruptIndex] = DhtProtState::NONE;
313 setFilterMode(filterMode, true);
314 }
315 }
316
317 ~TempHumDHT() {
318 if (iPin != 255)
319 detachInterrupt(iPin);
320 }
321
322 double getTemperature() {
326 return temperatureValue;
327 }
328
329 double getHumidity() {
333 return humidityValue;
334 }
335
336 void begin(Scheduler *_pSched) {
337 pSched = _pSched;
338
339 lastPoll = 0;
340 auto ft = [=]() { this->loop(); };
341 tID = pSched->add(ft, name, 500); // 500us
342
343 auto fnall = [=](String topic, String msg, String originator) {
344 this->subsMsg(topic, msg, originator);
345 };
346 pSched->subscribe(tID, name + "/sensor/#", fnall);
347 if (iPin != 255) {
348 pDhtState[interruptIndex] = DhtProtState::NONE;
349 bActive = true;
350 }
351 }
352
353 void setFilterMode(FilterMode mode, bool silent = false) {
354 switch (mode) {
355 case FAST:
356 filterMode = FAST;
357 temperatureSensor.update(1, 2, 0.05);
358 humiditySensor.update(1, 2, 0.1);
359 break;
360 case MEDIUM:
361 filterMode = MEDIUM;
362 temperatureSensor.update(4, 30, 0.1);
363 humiditySensor.update(4, 30, 0.5);
364 break;
365 case LONGTERM:
366 default:
367 filterMode = LONGTERM;
368 temperatureSensor.update(10, 600, 0.1);
369 humiditySensor.update(50, 600, 0.5);
370 break;
371 }
372 if (!silent)
373 publishFilterMode();
374 }
375
376 private:
377 void publishTemperature() {
378 char buf[32];
379 sprintf(buf, "%6.2f", temperatureValue);
380 pSched->publish(name + "/sensor/temperature", buf);
381 }
382
383 void publishHumidity() {
384 char buf[32];
385 sprintf(buf, "%6.2f", humidityValue);
386 pSched->publish(name + "/sensor/humidity", buf);
387 }
388
389 void publishError(String errMsg) {
390 pSched->publish(name + "/sensor/error", errMsg);
391 }
392
393 void publishFilterMode() {
394 switch (filterMode) {
395 case FilterMode::FAST:
396 pSched->publish(name + "/sensor/mode", "FAST");
397 break;
398 case FilterMode::MEDIUM:
399 pSched->publish(name + "/sensor/mode", "MEDIUM");
400 break;
401 case FilterMode::LONGTERM:
402 pSched->publish(name + "/sensor/mode", "LONGTERM");
403 break;
404 }
405 }
406
407 void generateStartMeasurementPulse() {
408 // generate a 80us low-pulse to initiate DHT sensor measurement
409 noInterrupts();
410 switch (pDhtState[interruptIndex]) {
411 case DhtProtState::NONE:
412 pinMode(port, OUTPUT);
413 digitalWrite(port, LOW);
414 startPulseStartUs = micros();
415 pDhtState[interruptIndex] = DhtProtState::START_PULSE_START;
416 pDhtFailureCode[interruptIndex] = DhtFailureCode::OK;
417 pDhtFailureData[interruptIndex] = 0;
418 break;
419 case DhtProtState::START_PULSE_START:
420 if (timeDiff(startPulseStartUs, micros()) > dhtWakeUpPulse) { // should be >18ms (Note: original manufacturer doc is WRONG, says 2ms)
421 digitalWrite(port, HIGH);
422 delayMicroseconds(dhtInitialDelay);
423 startPulseStartUs = 0;
424 pDhtState[interruptIndex] = DhtProtState::START_PULSE_END;
425 lastPoll = time(nullptr);
426 pinMode(port, INPUT_PULLUP);
427 }
428 break;
429 default:
430 break;
431 }
432 interrupts();
433 }
434
435 bool measurementChanged(double *tempVal, double *humiVal) {
436 int crc = 0;
437 char msg[128];
438 for (unsigned int i = 0; i < 4; i++)
439 crc += sensorDataBytes[interruptIndex * 5 + i];
440 if (crc % 256 != sensorDataBytes[interruptIndex * 5 + 4]) {
441 ++errs;
442 sprintf(msg, "CRC_ERROR! (%d) Errs: %ld, Code: %d, ErrData %d, bytes:[%d,%d,%d,%d,%d]", (crc % 256), errs, int(pDhtFailureCode[interruptIndex]), pDhtFailureData[interruptIndex],
443 sensorDataBytes[interruptIndex * 5 + 0], sensorDataBytes[interruptIndex * 5 + 1], sensorDataBytes[interruptIndex * 5 + 2], sensorDataBytes[interruptIndex * 5 + 3], sensorDataBytes[interruptIndex * 5 + 4]);
444 publishError(msg);
445 noInterrupts();
446 pDhtState[interruptIndex] = DhtProtState::NONE;
447 interrupts();
448 return false;
449
450 } else {
451 ++oks;
452 sprintf(msg, "OK! Oks: %ld Errs: %ld, Code: %d, ErrData %d, bytes:[%d,%d,%d,%d,%d]", oks, errs, int(pDhtFailureCode[interruptIndex]), pDhtFailureData[interruptIndex],
453 sensorDataBytes[interruptIndex * 5 + 0], sensorDataBytes[interruptIndex * 5 + 1], sensorDataBytes[interruptIndex * 5 + 2], sensorDataBytes[interruptIndex * 5 + 3], sensorDataBytes[interruptIndex * 5 + 4]);
454 // publishError(msg);
455 int t = ((sensorDataBytes[interruptIndex * 5 + 2] & 0x7f) << 8) | sensorDataBytes[interruptIndex * 5 + 3];
456 if (sensorDataBytes[interruptIndex * 5 + 2] & 0x80) t = t * (-1);
457 *tempVal = (double)t / 10.0;
458 int h = ((sensorDataBytes[interruptIndex * 5 + 0]) << 8) | sensorDataBytes[interruptIndex * 5 + 1];
459 *humiVal = (double)h / 10.0;
460 return true;
461 }
462 }
463
464 void loop() {
465 double tempVal, humiVal;
466 double errratio;
467 DhtProtState curState;
468 if (bActive) {
469 noInterrupts();
470 curState = pDhtState[interruptIndex];
471 interrupts();
472 switch (curState) {
473 case DhtProtState::NONE:
474 if (time(nullptr) - lastPoll > sensorPollRate) {
475 generateStartMeasurementPulse();
476 lastPoll = time(nullptr);
477 }
478 break;
479 case DhtProtState::START_PULSE_START:
480 generateStartMeasurementPulse();
481 break;
482 case DhtProtState::DATA_OK:
483 if (measurementChanged(&tempVal, &humiVal)) {
484 if (temperatureSensor.filter(&tempVal)) {
485 temperatureValue = tempVal;
486 publishTemperature();
487 }
488 if (humiditySensor.filter(&humiVal)) {
489 humidityValue = humiVal;
490 publishHumidity();
491 }
492 }
493 ++oks;
494 noInterrupts();
495 pDhtState[interruptIndex] = DhtProtState::NONE;
496 interrupts();
497 break;
498 case DhtProtState::DATA_ABORT:
499 // State machine error!
500 char msg[128];
501 errratio = (double)errs / (errs + oks) * 100.0;
502 ++errs;
503 sprintf(msg, "Errs: %ld, err-rate: %6.2f Code: %d, Data %d", errs, errratio, pDhtFailureCode[interruptIndex], pDhtFailureData[interruptIndex]);
504 publishError(msg);
505 noInterrupts();
506 pDhtState[interruptIndex] = DhtProtState::NONE;
507 interrupts();
508 break;
509 default:
510 return;
511 }
512 }
513 }
514
515 void subsMsg(String topic, String msg, String originator) {
516 if (topic == name + "/sensor/temperature/get") {
517 publishTemperature();
518 } else if (topic == name + "/sensor/humidity/get") {
519 publishHumidity();
520 } else if (topic == name + "/sensor/mode/get") {
521 publishFilterMode();
522 } else if (topic == name + "/sensor/mode/set") {
523 if (msg == "fast" || msg == "FAST") {
524 setFilterMode(FilterMode::FAST);
525 } else {
526 if (msg == "medium" || msg == "MEDIUM") {
527 setFilterMode(FilterMode::MEDIUM);
528 } else {
529 setFilterMode(FilterMode::LONGTERM);
530 }
531 }
532 }
533 };
534}; // TempHumDHT
535
536} // namespace ustd
mupplet-sensor temperature and humidity with DHT11/22
Definition: mup_temphum_dht.h:269
TempHumDHT(String name, uint8_t port, uint8_t interruptIndex, DHTType dhtType=DHTType::DHT22, FilterMode filterMode=FilterMode::MEDIUM)
Definition: mup_temphum_dht.h:298
double getTemperature()
Definition: mup_temphum_dht.h:322
double getHumidity()
Definition: mup_temphum_dht.h:329