From 57421d2392e1188a0f4a27a1df5af3cf8eeeb592 Mon Sep 17 00:00:00 2001 From: Phil Bolduc Date: Sun, 13 Sep 2020 10:26:27 -0700 Subject: [PATCH] Async temperature usermod (#1178) * Change Temperature usermod to use async reading to avoid blocking * Fix comparison on wait time * Add * Simplify logic in the loop method * Optimize calls to minimize latency, inform user how long till first measurement * disable usermod if sensor not found * Added debug statements on init, update readme * fix spelling error of celsius --- usermods/Temperature/platformio_override.ini | 13 ++ usermods/Temperature/readme.md | 26 +++- usermods/Temperature/usermod_temperature.h | 141 ++++++++++++++++--- usermods/Temperature/usermods_list.cpp | 8 +- wled00/usermods_list.cpp | 8 +- 5 files changed, 166 insertions(+), 30 deletions(-) create mode 100644 usermods/Temperature/platformio_override.ini diff --git a/usermods/Temperature/platformio_override.ini b/usermods/Temperature/platformio_override.ini new file mode 100644 index 00000000..d9e3fbac --- /dev/null +++ b/usermods/Temperature/platformio_override.ini @@ -0,0 +1,13 @@ +; Options +; ------- +; USERMOD_DALLASTEMPERATURE - define this to have this user mod included wled00\usermods_list.cpp +; USERMOD_DALLASTEMPERATURE_CELSIUS - define this to report temperatures in degrees celsius, otherwise fahrenheit will be reported +; USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds +; USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 20 seconds +; +[env:d1_mini_usermod_dallas_temperature_C] +extends = env:d1_mini +build_flags = ${common.build_flags_esp8266} -D USERMOD_DALLASTEMPERATURE -D USERMOD_DALLASTEMPERATURE_CELSIUS +lib_deps = ${env.lib_deps} + milesburton/DallasTemperature@^3.9.0 + OneWire@~2.3.5 diff --git a/usermods/Temperature/readme.md b/usermods/Temperature/readme.md index f6ce8532..5e26ba69 100644 --- a/usermods/Temperature/readme.md +++ b/usermods/Temperature/readme.md @@ -5,11 +5,18 @@ This usermod will read from an attached DS18B20 temperature sensor (as available The temperature is displayed both in the Info section of the web UI as well as published to the `/temperature` MQTT topic if enabled. This usermod will be expanded with support for different sensor types in the future. +If temperature sensor is not detected during boot, this usermod will be disabled. + ## Installation -Copy `usermod_temperature.h` to the wled00 directory. -Uncomment the corresponding lines in `usermods_list.cpp` and compile! -If this is the only v2 usermod you plan to use, you can alternatively replace `usermods_list.h` in wled00 with the one in this folder. +Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_DALLASTEMPERATURE` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_DALLASTEMPERATURE_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +* `USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds +* `USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 20 seconds ## Project link @@ -17,7 +24,10 @@ If this is the only v2 usermod you plan to use, you can alternatively replace `u ### PlatformIO requirements -You might have to uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: +If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dallas_temperature_C`. + + +If you are not using `platformio_override.ini`, you might have to uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: ```ini # platformio.ini @@ -38,3 +48,11 @@ lib_deps_external = OneWire@~2.3.5 ... ``` + +## Change Log + +2020-09-12 +* Changed to use async, non-blocking implementation +* Do not report low temperatures that indicate an error to mqtt +* Disable plugin if temperature sensor not detected +* Report the number of seconds until the first read in the info screen instead of sensor error \ No newline at end of file diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index eb123df0..d72be934 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -11,61 +11,156 @@ #define TEMPERATURE_PIN 14 #endif -#define TEMP_CELSIUS // Comment out for Fahrenheit +// the frequency to check temperature, 1 minute +#ifndef USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL +#define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000 +#endif -#define MEASUREMENT_INTERVAL 60000 //1 Minute +// how many seconds after boot to take first measurement, 20 seconds +#ifndef USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT +#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000 +#endif OneWire oneWire(TEMPERATURE_PIN); DallasTemperature sensor(&oneWire); class UsermodTemperature : public Usermod { private: - //set last reading as "40 sec before boot", so first reading is taken after 20 sec - unsigned long lastMeasurement = UINT32_MAX - 40000; - float temperature = 0.0f; - public: - void getReading() { - sensor.requestTemperatures(); - #ifdef TEMP_CELSIUS - temperature = sensor.getTempCByIndex(0); - #else - temperature = sensor.getTempFByIndex(0); - #endif + // The device's unique 64-bit serial code stored in on-board ROM. + // Reading directly from the sensor device address is faster than + // reading from index. When reading by index, DallasTemperature + // must first look up the device address at the specified index. + DeviceAddress sensorDeviceAddress; + // set last reading as "40 sec before boot", so first reading is taken after 20 sec + unsigned long lastMeasurement = UINT32_MAX - (USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL - USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT); + // last time requestTemperatures was called + // used to determine when we can read the sensors temperature + // we have to wait at least 93.75 ms after requestTemperatures() is called + unsigned long lastTemperaturesRequest; + float temperature = -100; // default to -100, DS18B20 only goes down to -50C + // indicates requestTemperatures has been called but the sensor measurement is not complete + bool waitingForConversion = false; + // flag to indicate we have finished the first getTemperature call + // allows this library to report to the user how long until the first + // measurement + bool getTemperatureComplete = false; + // flag set at startup if DS18B20 sensor not found, avoids trying to keep getting + // temperature if flashed to a board without a sensor attached + bool disabled = false; + + void requestTemperatures() { + // there is requestTemperaturesByAddress however it + // appears to do more work, + // TODO: measure exection time difference + sensor.requestTemperatures(); + lastTemperaturesRequest = millis(); + waitingForConversion = true; } + void getTemperature() { + #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS + temperature = sensor.getTempC(sensorDeviceAddress); + #else + temperature = sensor.getTempF(sensorDeviceAddress); + #endif + + lastMeasurement = millis(); + waitingForConversion = false; + getTemperatureComplete = true; + } + + public: + + void setup() { sensor.begin(); - sensor.setResolution(9); + + // get the unique 64-bit serial code stored in on-board ROM + // if getAddress returns false, the sensor was not found + disabled = !sensor.getAddress(sensorDeviceAddress, 0); + + if (!disabled) { + DEBUG_PRINTLN("Dallas Temperature found"); + // set the resolution for this specific device + sensor.setResolution(sensorDeviceAddress, 9, true); + // do not block waiting for reading + sensor.setWaitForConversion(false); + } else { + DEBUG_PRINTLN("Dallas Temperature not found"); + } } void loop() { - if (millis() - lastMeasurement > MEASUREMENT_INTERVAL) - { - getReading(); + if (disabled) { + return; + } + + unsigned long now = millis(); + // check to see if we are due for taking a measurement + // lastMeasurement will not be updated until the conversion + // is complete the the reading is finished + if (now - lastMeasurement < USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL) + { + return; + } + + // we are due for a measurement, if we are not already waiting + // for a conversion to complete, then make a new request for temps + if (!waitingForConversion) + { + requestTemperatures(); + return; + } + + // we were waiting for a conversion to complete, have we waited log enough? + if (now - lastTemperaturesRequest >= 94 /* 93.75ms per the datasheet */) + { + getTemperature(); + if (WLED_MQTT_CONNECTED) { char subuf[38]; strcpy(subuf, mqttDeviceTopic); - strcat(subuf, "/temperature"); - mqtt->publish(subuf, 0, true, String(temperature).c_str()); + if (-100 <= temperature) { + // dont publish super low temperature as the graph will get messed up + // the DallasTemperature library returns -127C or -196.6F when problem + // reading the sensor + strcat(subuf, "/temperature"); + mqtt->publish(subuf, 0, true, String(temperature).c_str()); + } else { + // publish something else to indicate status? + } } - lastMeasurement = millis(); } } void addToJsonInfo(JsonObject& root) { + // dont add temperature to info if we are disabled + if (disabled) { + return; + } + JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); JsonArray temp = user.createNestedArray("Temperature"); - if (temperature == DEVICE_DISCONNECTED_C) { + + if (!getTemperatureComplete) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + temp.add((USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT - millis()) / 1000); + temp.add(" sec until read"); + return; + } + + if (temperature <= -100) { temp.add(0); temp.add(" Sensor Error!"); return; } temp.add(temperature); - #ifdef TEMP_CELSIUS + #ifdef USERMOD_DALLASTEMPERATURE_CELSIUS temp.add("°C"); #else temp.add("°F"); @@ -76,4 +171,4 @@ class UsermodTemperature : public Usermod { { return USERMOD_ID_TEMPERATURE; } -}; \ No newline at end of file +}; diff --git a/usermods/Temperature/usermods_list.cpp b/usermods/Temperature/usermods_list.cpp index 1a1efdd7..50dd7816 100644 --- a/usermods/Temperature/usermods_list.cpp +++ b/usermods/Temperature/usermods_list.cpp @@ -9,7 +9,10 @@ * \/ \/ \/ */ //#include "usermod_v2_example.h" -#include "usermod_temperature.h" +#ifdef USERMOD_DALLASTEMPERATURE +#include "../usermods/Temperature/usermod_temperature.h" +#endif + //#include "usermod_v2_empty.h" void registerUsermods() @@ -20,6 +23,9 @@ void registerUsermods() * \/ \/ \/ */ //usermods.add(new MyExampleUsermod()); +#ifdef USERMOD_DALLASTEMPERATURE usermods.add(new UsermodTemperature()); +#endif + //usermods.add(new UsermodRenameMe()); } \ No newline at end of file diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index dcecd0ff..90b18074 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -10,7 +10,9 @@ * \/ \/ \/ */ //#include "usermod_v2_example.h" -//#include "usermod_temperature.h" +#ifdef USERMOD_DALLASTEMPERATURE +#include "../usermods/Temperature/usermod_temperature.h" +#endif //#include "usermod_v2_empty.h" void registerUsermods() @@ -21,6 +23,8 @@ void registerUsermods() * \/ \/ \/ */ //usermods.add(new MyExampleUsermod()); - //usermods.add(new UsermodTemperature()); + #ifdef USERMOD_DALLASTEMPERATURE + usermods.add(new UsermodTemperature()); + #endif //usermods.add(new UsermodRenameMe()); } \ No newline at end of file