2020-05-28 02:20:02 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "wled.h"
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
//#include <DallasTemperature.h> //DS18B20
|
|
|
|
#include "OneWire.h"
|
2020-05-28 02:20:02 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
//Pin defaults for QuinLed Dig-Uno if not overriden
|
2021-01-17 15:00:14 +01:00
|
|
|
#ifndef TEMPERATURE_PIN
|
2021-05-07 12:41:39 +02:00
|
|
|
#ifdef ARDUINO_ARCH_ESP32
|
|
|
|
#define TEMPERATURE_PIN 18
|
|
|
|
#else //ESP8266 boards
|
|
|
|
#define TEMPERATURE_PIN 14
|
|
|
|
#endif
|
2021-01-17 15:00:14 +01:00
|
|
|
#endif
|
2020-05-28 02:20:02 +02:00
|
|
|
|
2020-09-13 19:26:27 +02:00
|
|
|
// the frequency to check temperature, 1 minute
|
|
|
|
#ifndef USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL
|
|
|
|
#define USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL 60000
|
|
|
|
#endif
|
2020-05-28 02:20:02 +02:00
|
|
|
|
2020-09-13 19:26:27 +02:00
|
|
|
// how many seconds after boot to take first measurement, 20 seconds
|
|
|
|
#ifndef USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT
|
2021-05-07 12:41:39 +02:00
|
|
|
#define USERMOD_DALLASTEMPERATURE_FIRST_MEASUREMENT_AT 20000
|
2020-09-13 19:26:27 +02:00
|
|
|
#endif
|
2020-05-28 02:20:02 +02:00
|
|
|
|
|
|
|
class UsermodTemperature : public Usermod {
|
2021-05-07 12:41:39 +02:00
|
|
|
|
2020-05-28 02:20:02 +02:00
|
|
|
private:
|
2021-05-07 12:41:39 +02:00
|
|
|
|
|
|
|
bool initDone = false;
|
|
|
|
OneWire *oneWire;
|
|
|
|
// GPIO pin used for sensor (with a default compile-time fallback)
|
|
|
|
int8_t temperaturePin = TEMPERATURE_PIN;
|
|
|
|
// measurement unit (true==°C, false==°F)
|
|
|
|
bool degC = true;
|
|
|
|
unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL;
|
2020-09-13 19:26:27 +02:00
|
|
|
// 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;
|
2021-05-07 12:41:39 +02:00
|
|
|
// flag to indicate we have finished the first readTemperature call
|
2020-09-13 19:26:27 +02:00
|
|
|
// allows this library to report to the user how long until the first
|
|
|
|
// measurement
|
2021-05-07 12:41:39 +02:00
|
|
|
bool readTemperatureComplete = false;
|
2020-09-13 19:26:27 +02:00
|
|
|
// 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;
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
// strings to reduce flash memory usage (used more than twice)
|
|
|
|
static const char _name[];
|
|
|
|
static const char _enabled[];
|
|
|
|
static const char _readInterval[];
|
|
|
|
|
|
|
|
//Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013
|
|
|
|
int16_t readDallas() {
|
|
|
|
byte i;
|
|
|
|
byte data[2];
|
|
|
|
int16_t result; // raw data from sensor
|
|
|
|
oneWire->reset();
|
|
|
|
oneWire->write(0xCC); // skip ROM
|
|
|
|
oneWire->write(0xBE); // read (temperature) from EEPROM
|
|
|
|
for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature
|
|
|
|
for (i=2; i < 8; i++) oneWire->read(); // read unused bytes
|
|
|
|
result = (data[1]<<8) | data[0];
|
|
|
|
result >>= 4; // 9-bit precision accurate to 1°C (/16)
|
|
|
|
if (data[1]&0x80) result |= 0xF000; // fix negative value
|
|
|
|
//if (data[0]&0x08) ++result;
|
|
|
|
oneWire->reset();
|
|
|
|
oneWire->write(0xCC); // skip ROM
|
|
|
|
oneWire->write(0x44,0); // request new temperature reading (without parasite power)
|
|
|
|
return result;
|
2020-09-13 19:26:27 +02:00
|
|
|
}
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
void requestTemperatures() {
|
|
|
|
readDallas();
|
|
|
|
lastTemperaturesRequest = millis();
|
|
|
|
waitingForConversion = true;
|
|
|
|
DEBUG_PRINTLN(F("Requested temperature."));
|
|
|
|
}
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
void readTemperature() {
|
|
|
|
temperature = readDallas();
|
2020-09-13 19:26:27 +02:00
|
|
|
lastMeasurement = millis();
|
|
|
|
waitingForConversion = false;
|
2021-05-07 12:41:39 +02:00
|
|
|
readTemperatureComplete = true;
|
|
|
|
DEBUG_PRINTF("Read temperature %2.1f.\n", temperature);
|
2020-05-28 02:20:02 +02:00
|
|
|
}
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
bool findSensor() {
|
|
|
|
DEBUG_PRINTLN(F("Searching for sensor..."));
|
|
|
|
uint8_t deviceAddress[8] = {0,0,0,0,0,0,0,0};
|
|
|
|
// find out if we have DS18xxx sensor attached
|
|
|
|
oneWire->reset_search();
|
|
|
|
while (oneWire->search(deviceAddress)) {
|
|
|
|
if (oneWire->crc8(deviceAddress, 7) == deviceAddress[7]) {
|
|
|
|
switch (deviceAddress[0]) {
|
|
|
|
case 0x10: // DS18S20
|
|
|
|
case 0x22: // DS18B20
|
|
|
|
case 0x28: // DS1822
|
|
|
|
case 0x3B: // DS1825
|
|
|
|
case 0x42: // DS28EA00
|
|
|
|
DEBUG_PRINTLN(F("Sensor found."));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
public:
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2020-05-28 02:20:02 +02:00
|
|
|
void setup() {
|
2021-05-07 12:41:39 +02:00
|
|
|
int retries = 10;
|
|
|
|
// pin retrieved from cfg.json (readFromConfig()) prior to running setup()
|
|
|
|
if (!pinManager.allocatePin(temperaturePin,false)) {
|
|
|
|
temperaturePin = -1; // allocation failed
|
|
|
|
disabled = true;
|
|
|
|
DEBUG_PRINTLN(F("Temperature pin allocation failed."));
|
2020-09-13 19:26:27 +02:00
|
|
|
} else {
|
2021-05-07 12:41:39 +02:00
|
|
|
if (!disabled) {
|
|
|
|
// config says we are enabled
|
|
|
|
oneWire = new OneWire(temperaturePin);
|
|
|
|
if (!oneWire->reset())
|
|
|
|
disabled = true; // resetting 1-Wire bus yielded an error
|
|
|
|
else
|
|
|
|
while ((disabled=!findSensor()) && retries--) delay(25); // try to find sensor
|
|
|
|
}
|
2020-09-13 19:26:27 +02:00
|
|
|
}
|
2021-05-07 12:41:39 +02:00
|
|
|
initDone = true;
|
2020-05-28 02:20:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void loop() {
|
2021-01-21 01:21:16 +01:00
|
|
|
if (disabled || strip.isUpdating()) return;
|
2021-05-07 12:41:39 +02:00
|
|
|
|
2020-09-13 19:26:27 +02:00
|
|
|
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
|
2021-05-07 12:41:39 +02:00
|
|
|
if (now - lastMeasurement < readingInterval) return;
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
// we are due for a measurement, if we are not already waiting
|
2020-09-13 19:26:27 +02:00
|
|
|
// for a conversion to complete, then make a new request for temps
|
2021-05-07 12:41:39 +02:00
|
|
|
if (!waitingForConversion) {
|
2020-09-13 19:26:27 +02:00
|
|
|
requestTemperatures();
|
|
|
|
return;
|
|
|
|
}
|
2020-05-28 02:20:02 +02:00
|
|
|
|
2020-09-13 19:26:27 +02:00
|
|
|
// we were waiting for a conversion to complete, have we waited log enough?
|
2021-05-07 12:41:39 +02:00
|
|
|
if (now - lastTemperaturesRequest >= 100 /* 93.75ms per the datasheet but can be up to 750ms */) {
|
|
|
|
readTemperature();
|
|
|
|
|
2020-05-28 02:20:02 +02:00
|
|
|
if (WLED_MQTT_CONNECTED) {
|
2021-05-07 12:41:39 +02:00
|
|
|
char subuf[64];
|
2020-05-28 02:20:02 +02:00
|
|
|
strcpy(subuf, mqttDeviceTopic);
|
2020-09-13 19:26:27 +02:00
|
|
|
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
|
2021-01-17 15:00:14 +01:00
|
|
|
strcat_P(subuf, PSTR("/temperature"));
|
2020-09-13 19:26:27 +02:00
|
|
|
mqtt->publish(subuf, 0, true, String(temperature).c_str());
|
2021-05-07 12:41:39 +02:00
|
|
|
strcat_P(subuf, PSTR("_f"));
|
|
|
|
mqtt->publish(subuf, 0, true, String((float)temperature * 1.8f + 32).c_str());
|
2020-09-13 19:26:27 +02:00
|
|
|
} else {
|
|
|
|
// publish something else to indicate status?
|
|
|
|
}
|
2020-05-28 02:20:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
/*
|
|
|
|
* API calls te enable data exchange between WLED modules
|
|
|
|
*/
|
|
|
|
inline float getTemperatureC() {
|
|
|
|
return (float)temperature;
|
|
|
|
}
|
|
|
|
inline float getTemperatureF() {
|
|
|
|
return (float)temperature * 1.8f + 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
|
|
|
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
|
|
|
* Below it is shown how this could be used for e.g. a light sensor
|
|
|
|
*/
|
2020-05-28 02:20:02 +02:00
|
|
|
void addToJsonInfo(JsonObject& root) {
|
2020-09-13 19:26:27 +02:00
|
|
|
// dont add temperature to info if we are disabled
|
2021-01-21 01:21:16 +01:00
|
|
|
if (disabled) return;
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
JsonObject user = root["u"];
|
|
|
|
if (user.isNull()) user = root.createNestedObject("u");
|
2020-05-28 02:20:02 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
JsonArray temp = user.createNestedArray(FPSTR(_name));
|
|
|
|
//temp.add(F("Loaded."));
|
2020-09-13 19:26:27 +02:00
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
if (!readTemperatureComplete) {
|
2020-09-13 19:26:27 +02:00
|
|
|
// 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);
|
2021-01-17 15:00:14 +01:00
|
|
|
temp.add(F(" sec until read"));
|
2020-09-13 19:26:27 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (temperature <= -100) {
|
2020-05-28 02:20:02 +02:00
|
|
|
temp.add(0);
|
2021-01-17 15:00:14 +01:00
|
|
|
temp.add(F(" Sensor Error!"));
|
2020-05-28 02:20:02 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-07 12:41:39 +02:00
|
|
|
temp.add(degC ? temperature : (float)temperature * 1.8f + 32);
|
|
|
|
if (degC) temp.add(F("°C"));
|
|
|
|
else temp.add(F("°F"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
|
|
|
|
* Values in the state object may be modified by connected clients
|
|
|
|
*/
|
|
|
|
//void addToJsonState(JsonObject &root)
|
|
|
|
//{
|
|
|
|
//}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
|
|
|
|
* Values in the state object may be modified by connected clients
|
|
|
|
* Read "<usermodname>_<usermodparam>" from json state and and change settings (i.e. GPIO pin) used.
|
|
|
|
*/
|
|
|
|
//void readFromJsonState(JsonObject &root) {
|
|
|
|
// if (!initDone) return; // prevent crash on boot applyPreset()
|
|
|
|
//}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
|
|
|
|
*/
|
|
|
|
void addToConfig(JsonObject &root) {
|
|
|
|
// we add JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
|
|
|
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
|
|
|
|
top[FPSTR(_enabled)] = !disabled;
|
|
|
|
top["pin"] = temperaturePin; // usermodparam
|
|
|
|
top["degC"] = degC; // usermodparam
|
|
|
|
top[FPSTR(_readInterval)] = readingInterval / 1000;
|
|
|
|
DEBUG_PRINTLN(F("Temperature config saved."));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
|
|
|
|
*/
|
|
|
|
void readFromConfig(JsonObject &root) {
|
|
|
|
// we look for JSON object: {"Temperature": {"pin": 0, "degC": true}}
|
|
|
|
JsonObject top = root[FPSTR(_name)];
|
|
|
|
int8_t newTemperaturePin = temperaturePin;
|
|
|
|
|
|
|
|
if (!top.isNull() && top["pin"] != nullptr) {
|
|
|
|
if (top[FPSTR(_enabled)].is<bool>()) {
|
|
|
|
disabled = !top[FPSTR(_enabled)].as<bool>();
|
|
|
|
} else {
|
|
|
|
String str = top[FPSTR(_enabled)]; // checkbox -> off or on
|
|
|
|
disabled = (bool)(str=="off"); // off is guaranteed to be present
|
|
|
|
}
|
|
|
|
newTemperaturePin = min(39,max(-1,top["pin"].as<int>()));
|
|
|
|
if (top["degC"].is<bool>()) {
|
|
|
|
// reading from cfg.json
|
|
|
|
degC = top["degC"].as<bool>();
|
|
|
|
} else {
|
|
|
|
// new configuration from set.cpp
|
|
|
|
String str = top["degC"]; // checkbox -> off or on
|
|
|
|
degC = (bool)(str!="off"); // off is guaranteed to be present
|
|
|
|
}
|
|
|
|
readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as<int>())) * 1000; // convert to ms
|
|
|
|
DEBUG_PRINTLN(F("Temperature config (re)loaded."));
|
|
|
|
} else {
|
|
|
|
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!initDone) {
|
|
|
|
// first run: reading from cfg.json
|
|
|
|
temperaturePin = newTemperaturePin;
|
|
|
|
} else {
|
|
|
|
// changing paramters from settings page
|
|
|
|
if (newTemperaturePin != temperaturePin) {
|
|
|
|
// deallocate pin and release memory
|
|
|
|
delete oneWire;
|
|
|
|
pinManager.deallocatePin(temperaturePin);
|
|
|
|
temperaturePin = newTemperaturePin;
|
|
|
|
// initialise
|
|
|
|
setup();
|
|
|
|
}
|
|
|
|
}
|
2020-05-28 02:20:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t getId()
|
|
|
|
{
|
|
|
|
return USERMOD_ID_TEMPERATURE;
|
|
|
|
}
|
2020-09-13 19:26:27 +02:00
|
|
|
};
|
2021-05-07 12:41:39 +02:00
|
|
|
|
|
|
|
// strings to reduce flash memory usage (used more than twice)
|
|
|
|
const char UsermodTemperature::_name[] PROGMEM = "Temperature";
|
|
|
|
const char UsermodTemperature::_enabled[] PROGMEM = "enabled";
|
|
|
|
const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s";
|