mirror of
https://github.com/esphome/esphome.git
synced 2024-11-29 10:14:13 +01:00
Add MAX31865 sensor support, fix MAX31855 sensor (#832)
* Add MAX31865 sensor support, fix MAX31855 sensor. # MAX31865 Added support for the MAX31865 RTD-to-Digital Converter to measure PT100 and similar RTDs. Verified with an Adafruit unit (product ID: 3328) and a PT100 probe. # MAX31855 This was setup for incorrect SPI clock polarity and phase, and would return bad data due to a race condition measuring on the wrong edge (verified with Saleae Logic scope). Selecting the correct configuration fixes that problem. Re-wrote the decode off the datasheet to handle error states better (sends NaN as an update on failure to read temperature, which shows the value as Unknown in Home Assistant). Added the *optional* ability to monitor the internal high-precision temperature sensor, which can be nice in some applications. * Tests for MAX31855/MAX38165. * Update style to match project rules. Also fix CONF_REFERENCE_RESISTANCE and CONF_REFERENCE_TEMPERATURE being defined multiple places. Missed this when I added them to const.py. * Update style to match project rules. Pylint line limit 101/100 ("missed it by that much"). Also apparently I can't read and patched the wrong line in max31855.cpp. * Minor string/style cleanup. There was a copy-paste leftover in max31855.cpp and max31865/sensor.py had unnecessary whitespace. * Improve MAX31865 fault detection and logging. Log levels are more in-line with the documented descriptions. Fault detection code is improved. A transient fault between reads is still reported, but now only faults *during* a read cause the sensor to fail and return NAN ("unknown" in Home Assistant). * Update style to match project rules. I just now realized the .clang-format and pylintrc files are included. D'oh! * MAX31855 & MAX31865 code style alignment. @OttoWinter caught some style mismatches, updated to match project better. * Fix a lost '\' in max31865/sensor.py.
This commit is contained in:
parent
1ed8e63d59
commit
f94e9b6b1e
10 changed files with 397 additions and 51 deletions
|
@ -1,4 +1,5 @@
|
||||||
#include "max31855.h"
|
#include "max31855.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -22,9 +23,15 @@ void MAX31855Sensor::setup() {
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
}
|
}
|
||||||
void MAX31855Sensor::dump_config() {
|
void MAX31855Sensor::dump_config() {
|
||||||
LOG_SENSOR("", "MAX31855", this);
|
ESP_LOGCONFIG(TAG, "MAX31855:");
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
LOG_SENSOR(" ", "Thermocouple", this);
|
||||||
|
if (this->temperature_reference_) {
|
||||||
|
LOG_SENSOR(" ", "Reference", this->temperature_reference_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " Reference temperature disabled.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
float MAX31855Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
float MAX31855Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
void MAX31855Sensor::read_data_() {
|
void MAX31855Sensor::read_data_() {
|
||||||
|
@ -32,53 +39,68 @@ void MAX31855Sensor::read_data_() {
|
||||||
delay(1);
|
delay(1);
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
this->read_array(data, 4);
|
this->read_array(data, 4);
|
||||||
|
|
||||||
// val is 14 bits of signed temperature data followed by 2 bits of status flags
|
|
||||||
int16_t val = data[1] | data[0] << 8;
|
|
||||||
|
|
||||||
// test data from MAX31855 datasheet
|
|
||||||
// val = 0x6400 // 1600.00°C
|
|
||||||
// val = 0x3E80 // 1000.00°C
|
|
||||||
// val = 0x064C // 100.75°C
|
|
||||||
// val = 0x0190 // 25.00°C
|
|
||||||
// val = 0x0000 // 0.00°C
|
|
||||||
// val = 0xFFFC // -0.25°C
|
|
||||||
// val = 0xFFF0 // -1.00°C
|
|
||||||
// val = 0xF060 // -250.00°C
|
|
||||||
|
|
||||||
this->disable();
|
this->disable();
|
||||||
if ((data[3] & 0x01) != 0) {
|
|
||||||
ESP_LOGW(TAG, "Got thermocouple not connected from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((data[3] & 0x02) != 0) {
|
|
||||||
ESP_LOGW(TAG, "Got short circuit to ground from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((data[3] & 0x04) != 0) {
|
|
||||||
ESP_LOGW(TAG, "Got short circuit to power from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ((data[1] & 0x01) != 0) {
|
|
||||||
ESP_LOGW(TAG, "Got faulty reading from MAX31855Sensor (0x%04X) (0x%04X)", val, data[3] | data[2] << 8);
|
|
||||||
this->status_set_warning();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((val & 0x8000) != 0) {
|
const uint32_t mem = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0;
|
||||||
// Negative value, drop the lower 2 bits and explicitly extend sign bits.
|
|
||||||
val = 0xE000 | ((val >> 2) & 0x1FFF);
|
// Verify we got data
|
||||||
|
if (mem != 0 && mem != 0xFFFFFFFF) {
|
||||||
|
this->status_clear_error();
|
||||||
} else {
|
} else {
|
||||||
// Positive value, just drop the lower 2 bits.
|
ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem);
|
||||||
val >>= 2;
|
this->publish_state(NAN);
|
||||||
|
if (this->temperature_reference_) {
|
||||||
|
this->temperature_reference_->publish_state(NAN);
|
||||||
|
}
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float temperature = float(val) / 4.0f;
|
// Internal reference temperature always works
|
||||||
ESP_LOGD(TAG, "'%s': Got temperature=%.1f°C", this->name_.c_str(), temperature);
|
if (this->temperature_reference_) {
|
||||||
this->publish_state(temperature);
|
int16_t val = (mem & 0x0000FFF0) >> 4;
|
||||||
|
if (val & 0x0800) {
|
||||||
|
val |= 0xF000; // Pad out 2's complement
|
||||||
|
}
|
||||||
|
const float t_ref = float(val) * 0.0625f;
|
||||||
|
ESP_LOGD(TAG, "Got reference temperature: %.4f°C", t_ref);
|
||||||
|
this->temperature_reference_->publish_state(t_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check thermocouple faults
|
||||||
|
if (mem & 0x00000001) {
|
||||||
|
ESP_LOGW(TAG, "Thermocouple open circuit (not connected) fault from MAX31855 (0x%08X)", mem);
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mem & 0x00000002) {
|
||||||
|
ESP_LOGW(TAG, "Thermocouple short circuit to ground fault from MAX31855 (0x%08X)", mem);
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mem & 0x00000004) {
|
||||||
|
ESP_LOGW(TAG, "Thermocouple short circuit to VCC fault from MAX31855 (0x%08X)", mem);
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mem & 0x00010000) {
|
||||||
|
ESP_LOGW(TAG, "Got faulty reading from MAX31855 (0x%08X)", mem);
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_warning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode thermocouple temperature
|
||||||
|
int16_t val = (mem & 0xFFFC0000) >> 18;
|
||||||
|
if (val & 0x2000) {
|
||||||
|
val |= 0xC000; // Pad out 2's complement
|
||||||
|
}
|
||||||
|
const float t_sense = float(val) * 0.25f;
|
||||||
|
ESP_LOGD(TAG, "Got thermocouple temperature: %.2f°C", t_sense);
|
||||||
|
this->publish_state(t_sense);
|
||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,11 @@ namespace max31855 {
|
||||||
|
|
||||||
class MAX31855Sensor : public sensor::Sensor,
|
class MAX31855Sensor : public sensor::Sensor,
|
||||||
public PollingComponent,
|
public PollingComponent,
|
||||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
|
||||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_4MHZ> {
|
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_4MHZ> {
|
||||||
public:
|
public:
|
||||||
|
void set_reference_sensor(sensor::Sensor *temperature_sensor) { temperature_reference_ = temperature_sensor; }
|
||||||
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
@ -20,6 +22,7 @@ class MAX31855Sensor : public sensor::Sensor,
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void read_data_();
|
void read_data_();
|
||||||
|
sensor::Sensor *temperature_reference_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace max31855
|
} // namespace max31855
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import sensor, spi
|
from esphome.components import sensor, spi
|
||||||
from esphome.const import CONF_ID, ICON_THERMOMETER, UNIT_CELSIUS
|
from esphome.const import CONF_ID, CONF_REFERENCE_TEMPERATURE, ICON_THERMOMETER, UNIT_CELSIUS
|
||||||
|
|
||||||
max31855_ns = cg.esphome_ns.namespace('max31855')
|
max31855_ns = cg.esphome_ns.namespace('max31855')
|
||||||
MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingComponent,
|
MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingComponent,
|
||||||
|
@ -9,6 +9,8 @@ MAX31855Sensor = max31855_ns.class_('MAX31855Sensor', sensor.Sensor, cg.PollingC
|
||||||
|
|
||||||
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||||
cv.GenerateID(): cv.declare_id(MAX31855Sensor),
|
cv.GenerateID(): cv.declare_id(MAX31855Sensor),
|
||||||
|
cv.Optional(CONF_REFERENCE_TEMPERATURE):
|
||||||
|
sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2),
|
||||||
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,3 +19,6 @@ def to_code(config):
|
||||||
yield cg.register_component(var, config)
|
yield cg.register_component(var, config)
|
||||||
yield spi.register_spi_device(var, config)
|
yield spi.register_spi_device(var, config)
|
||||||
yield sensor.register_sensor(var, config)
|
yield sensor.register_sensor(var, config)
|
||||||
|
if CONF_REFERENCE_TEMPERATURE in config:
|
||||||
|
tc_ref = yield sensor.new_sensor(config[CONF_REFERENCE_TEMPERATURE])
|
||||||
|
cg.add(var.set_reference_sensor(tc_ref))
|
||||||
|
|
0
esphome/components/max31865/__init__.py
Normal file
0
esphome/components/max31865/__init__.py
Normal file
214
esphome/components/max31865/max31865.cpp
Normal file
214
esphome/components/max31865/max31865.cpp
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
#include "max31865.h"
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max31865 {
|
||||||
|
|
||||||
|
static const char* TAG = "max31865";
|
||||||
|
|
||||||
|
void MAX31865Sensor::update() {
|
||||||
|
// Check new faults since last measurement
|
||||||
|
if (!has_fault_) {
|
||||||
|
const uint8_t faults = this->read_register_(FAULT_STATUS_REG);
|
||||||
|
if (faults & 0b11111100) {
|
||||||
|
if (faults & (1 << 2)) {
|
||||||
|
ESP_LOGW(TAG, "Overvoltage/undervoltage fault between measurements");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 3)) {
|
||||||
|
ESP_LOGW(TAG, "RTDIN- < 0.85 x V_BIAS (FORCE- open) between measurements");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 4)) {
|
||||||
|
ESP_LOGW(TAG, "REFIN- < 0.85 x V_BIAS (FORCE- open) between measurements");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 5)) {
|
||||||
|
ESP_LOGW(TAG, "REFIN- > 0.85 x V_BIAS between measurements");
|
||||||
|
}
|
||||||
|
if (!has_warn_) {
|
||||||
|
if (faults & (1 << 6)) {
|
||||||
|
ESP_LOGW(TAG, "RTD Low Threshold between measurements");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 7)) {
|
||||||
|
ESP_LOGW(TAG, "RTD High Threshold between measurements");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run fault detection
|
||||||
|
write_register_(CONFIGURATION_REG, 0b11101110, 0b10000110);
|
||||||
|
const uint32_t start_time = micros();
|
||||||
|
uint8_t config;
|
||||||
|
uint32_t fault_detect_time;
|
||||||
|
do {
|
||||||
|
config = this->read_register_(CONFIGURATION_REG);
|
||||||
|
fault_detect_time = micros() - start_time;
|
||||||
|
if ((fault_detect_time >= 6000) && (config & 0b00001100)) {
|
||||||
|
ESP_LOGE(TAG, "Fault detection incomplete (0x%02X) after %uμs (datasheet spec is 600μs max)! Aborting read.",
|
||||||
|
config, fault_detect_time);
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while (config & 0b00001100);
|
||||||
|
ESP_LOGV(TAG, "Fault detection completed in %uμs.", fault_detect_time);
|
||||||
|
|
||||||
|
// Start 1-shot conversion
|
||||||
|
this->write_register_(CONFIGURATION_REG, 0b11100000, 0b10100000);
|
||||||
|
|
||||||
|
// Datasheet max conversion time is 55ms for 60Hz / 66ms for 50Hz
|
||||||
|
auto f = std::bind(&MAX31865Sensor::read_data_, this);
|
||||||
|
this->set_timeout("value", filter_ == FILTER_60HZ ? 55 : 66, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX31865Sensor::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up MAX31865Sensor '%s'...", this->name_.c_str());
|
||||||
|
this->spi_setup();
|
||||||
|
|
||||||
|
// Build configuration
|
||||||
|
uint8_t config = 0b00000010;
|
||||||
|
config |= (filter_ & 1) << 0;
|
||||||
|
if (rtd_wires_ == 3) {
|
||||||
|
config |= 1 << 4;
|
||||||
|
}
|
||||||
|
this->write_register_(CONFIGURATION_REG, 0b11111111, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX31865Sensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "MAX31865", this);
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Reference Resistance: %.2fΩ", reference_resistance_);
|
||||||
|
ESP_LOGCONFIG(TAG, " RTD: %u-wire %.2fΩ", rtd_wires_, rtd_nominal_resistance_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Filter: %s",
|
||||||
|
(filter_ == FILTER_60HZ ? "60 Hz" : (filter_ == FILTER_50HZ ? "50 Hz" : "Unknown!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
float MAX31865Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void MAX31865Sensor::read_data_() {
|
||||||
|
// Read temperature, disable V_BIAS (save power)
|
||||||
|
const uint16_t rtd_resistance_register = this->read_register_16_(RTD_RESISTANCE_MSB_REG);
|
||||||
|
this->write_register_(CONFIGURATION_REG, 0b11000000, 0b00000000);
|
||||||
|
|
||||||
|
// Check faults
|
||||||
|
const uint8_t faults = this->read_register_(FAULT_STATUS_REG);
|
||||||
|
if ((has_fault_ = faults & 0b00111100)) {
|
||||||
|
if (faults & (1 << 2)) {
|
||||||
|
ESP_LOGE(TAG, "Overvoltage/undervoltage fault");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 3)) {
|
||||||
|
ESP_LOGE(TAG, "RTDIN- < 0.85 x V_BIAS (FORCE- open)");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 4)) {
|
||||||
|
ESP_LOGE(TAG, "REFIN- < 0.85 x V_BIAS (FORCE- open)");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 5)) {
|
||||||
|
ESP_LOGE(TAG, "REFIN- > 0.85 x V_BIAS");
|
||||||
|
}
|
||||||
|
this->publish_state(NAN);
|
||||||
|
this->status_set_error();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this->status_clear_error();
|
||||||
|
}
|
||||||
|
if ((has_warn_ = faults & 0b11000000)) {
|
||||||
|
if (faults & (1 << 6)) {
|
||||||
|
ESP_LOGW(TAG, "RTD Low Threshold");
|
||||||
|
}
|
||||||
|
if (faults & (1 << 7)) {
|
||||||
|
ESP_LOGW(TAG, "RTD High Threshold");
|
||||||
|
}
|
||||||
|
this->status_set_warning();
|
||||||
|
} else {
|
||||||
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process temperature
|
||||||
|
if (rtd_resistance_register & 0x0001) {
|
||||||
|
ESP_LOGW(TAG, "RTD Resistance Registers fault bit set! (0x%04X)", rtd_resistance_register);
|
||||||
|
this->status_set_warning();
|
||||||
|
}
|
||||||
|
const float rtd_ratio = static_cast<float>(rtd_resistance_register >> 1) / static_cast<float>((1 << 15) - 1);
|
||||||
|
const float temperature = this->calc_temperature_(rtd_ratio);
|
||||||
|
ESP_LOGV(TAG, "RTD read complete. %.5f (ratio) * %.1fΩ (reference) = %.2fΩ --> %.2f°C", rtd_ratio,
|
||||||
|
reference_resistance_, reference_resistance_ * rtd_ratio, temperature);
|
||||||
|
this->publish_state(temperature);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MAX31865Sensor::write_register_(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position) {
|
||||||
|
uint8_t value = this->read_register_(reg);
|
||||||
|
|
||||||
|
value &= (~mask);
|
||||||
|
value |= (bits << start_position);
|
||||||
|
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg |= SPI_WRITE_M);
|
||||||
|
this->write_byte(value);
|
||||||
|
this->disable();
|
||||||
|
ESP_LOGVV(TAG, "write_register_ 0x%02X: 0x%02X", reg, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t MAX31865Sensor::read_register_(uint8_t reg) {
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg);
|
||||||
|
const uint8_t value(this->read_byte());
|
||||||
|
this->disable();
|
||||||
|
ESP_LOGVV(TAG, "read_register_ 0x%02X: 0x%02X", reg, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t MAX31865Sensor::read_register_16_(uint8_t reg) {
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg);
|
||||||
|
const uint8_t msb(this->read_byte());
|
||||||
|
const uint8_t lsb(this->read_byte());
|
||||||
|
this->disable();
|
||||||
|
const uint16_t value((msb << 8) | lsb);
|
||||||
|
ESP_LOGVV(TAG, "read_register_16_ 0x%02X: 0x%04X", reg, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float MAX31865Sensor::calc_temperature_(const float& rtd_ratio) {
|
||||||
|
// Based loosely on Adafruit's library: https://github.com/adafruit/Adafruit_MAX31865
|
||||||
|
// Mainly based on formulas provided by Analog:
|
||||||
|
// http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf
|
||||||
|
|
||||||
|
const float a = 3.9083e-3;
|
||||||
|
const float b = -5.775e-7;
|
||||||
|
const float z1 = -a;
|
||||||
|
const float z2 = a * a - 4 * b;
|
||||||
|
const float z3 = 4 * b / rtd_nominal_resistance_;
|
||||||
|
const float z4 = 2 * b;
|
||||||
|
|
||||||
|
float rtd_resistance = rtd_ratio * reference_resistance_;
|
||||||
|
|
||||||
|
// ≥ 0°C Formula
|
||||||
|
const float pos_temp = (z1 + std::sqrt(z2 + (z3 * rtd_resistance))) / z4;
|
||||||
|
if (pos_temp >= 0) {
|
||||||
|
return pos_temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 0°C Formula
|
||||||
|
if (rtd_nominal_resistance_ != 100) {
|
||||||
|
// Normalize RTD to 100Ω
|
||||||
|
rtd_resistance /= rtd_nominal_resistance_;
|
||||||
|
rtd_resistance *= 100;
|
||||||
|
}
|
||||||
|
float rpoly = rtd_resistance;
|
||||||
|
float neg_temp = -242.02;
|
||||||
|
neg_temp += 2.2228 * rpoly;
|
||||||
|
rpoly *= rtd_resistance; // square
|
||||||
|
neg_temp += 2.5859e-3 * rpoly;
|
||||||
|
rpoly *= rtd_resistance; // ^3
|
||||||
|
neg_temp -= 4.8260e-6 * rpoly;
|
||||||
|
rpoly *= rtd_resistance; // ^4
|
||||||
|
neg_temp -= 2.8183e-8 * rpoly;
|
||||||
|
rpoly *= rtd_resistance; // ^5
|
||||||
|
neg_temp += 1.5243e-10 * rpoly;
|
||||||
|
return neg_temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace max31865
|
||||||
|
} // namespace esphome
|
56
esphome/components/max31865/max31865.h
Normal file
56
esphome/components/max31865/max31865.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace max31865 {
|
||||||
|
|
||||||
|
enum MAX31865RegisterMasks { SPI_WRITE_M = 0x80 };
|
||||||
|
enum MAX31865Registers {
|
||||||
|
CONFIGURATION_REG = 0x00,
|
||||||
|
RTD_RESISTANCE_MSB_REG = 0x01,
|
||||||
|
RTD_RESISTANCE_LSB_REG = 0x02,
|
||||||
|
FAULT_THRESHOLD_H_MSB_REG = 0x03,
|
||||||
|
FAULT_THRESHOLD_H_LSB_REG = 0x04,
|
||||||
|
FAULT_THRESHOLD_L_MSB_REG = 0x05,
|
||||||
|
FAULT_THRESHOLD_L_LSB_REG = 0x06,
|
||||||
|
FAULT_STATUS_REG = 0x07,
|
||||||
|
};
|
||||||
|
enum MAX31865ConfigFilter {
|
||||||
|
FILTER_60HZ = 0,
|
||||||
|
FILTER_50HZ = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MAX31865Sensor : public sensor::Sensor,
|
||||||
|
public PollingComponent,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||||
|
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_4MHZ> {
|
||||||
|
public:
|
||||||
|
void set_reference_resistance(float reference_resistance) { reference_resistance_ = reference_resistance; }
|
||||||
|
void set_nominal_resistance(float nominal_resistance) { rtd_nominal_resistance_ = nominal_resistance; }
|
||||||
|
void set_filter(MAX31865ConfigFilter filter) { filter_ = filter; }
|
||||||
|
void set_num_rtd_wires(uint8_t rtd_wires) { rtd_wires_ = rtd_wires; }
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
float reference_resistance_;
|
||||||
|
float rtd_nominal_resistance_;
|
||||||
|
MAX31865ConfigFilter filter_;
|
||||||
|
uint8_t rtd_wires_;
|
||||||
|
bool has_fault_ = false;
|
||||||
|
bool has_warn_ = false;
|
||||||
|
void read_data_();
|
||||||
|
void write_register_(uint8_t reg, uint8_t mask, uint8_t bits, uint8_t start_position = 0);
|
||||||
|
const uint8_t read_register_(uint8_t reg);
|
||||||
|
const uint16_t read_register_16_(uint8_t reg);
|
||||||
|
float calc_temperature_(const float& rtd_ratio);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace max31865
|
||||||
|
} // namespace esphome
|
34
esphome/components/max31865/sensor.py
Normal file
34
esphome/components/max31865/sensor.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor, spi
|
||||||
|
from esphome.const import CONF_ID, CONF_MAINS_FILTER, CONF_REFERENCE_RESISTANCE, \
|
||||||
|
CONF_RTD_NOMINAL_RESISTANCE, CONF_RTD_WIRES, ICON_THERMOMETER, UNIT_CELSIUS
|
||||||
|
|
||||||
|
max31865_ns = cg.esphome_ns.namespace('max31865')
|
||||||
|
MAX31865Sensor = max31865_ns.class_('MAX31865Sensor', sensor.Sensor, cg.PollingComponent,
|
||||||
|
spi.SPIDevice)
|
||||||
|
|
||||||
|
MAX31865ConfigFilter = max31865_ns.enum('MAX31865ConfigFilter')
|
||||||
|
FILTER = {
|
||||||
|
'50HZ': MAX31865ConfigFilter.FILTER_50HZ,
|
||||||
|
'60HZ': MAX31865ConfigFilter.FILTER_60HZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 2).extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(MAX31865Sensor),
|
||||||
|
cv.Required(CONF_REFERENCE_RESISTANCE): cv.All(cv.resistance, cv.Range(min=100, max=10000)),
|
||||||
|
cv.Required(CONF_RTD_NOMINAL_RESISTANCE): cv.All(cv.resistance, cv.Range(min=100, max=1000)),
|
||||||
|
cv.Optional(CONF_MAINS_FILTER, default='60HZ'): cv.enum(FILTER, upper=True, space=''),
|
||||||
|
cv.Optional(CONF_RTD_WIRES, default=4): cv.int_range(min=2, max=4),
|
||||||
|
}).extend(cv.polling_component_schema('60s')).extend(spi.SPI_DEVICE_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield spi.register_spi_device(var, config)
|
||||||
|
yield sensor.register_sensor(var, config)
|
||||||
|
cg.add(var.set_reference_resistance(config[CONF_REFERENCE_RESISTANCE]))
|
||||||
|
cg.add(var.set_nominal_resistance(config[CONF_RTD_NOMINAL_RESISTANCE]))
|
||||||
|
cg.add(var.set_filter(config[CONF_MAINS_FILTER]))
|
||||||
|
cg.add(var.set_num_rtd_wires(config[CONF_RTD_WIRES]))
|
|
@ -4,15 +4,14 @@ from math import log
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import UNIT_CELSIUS, ICON_THERMOMETER, CONF_SENSOR, CONF_TEMPERATURE, \
|
from esphome.const import CONF_CALIBRATION, CONF_ID, CONF_REFERENCE_RESISTANCE, \
|
||||||
CONF_VALUE, CONF_CALIBRATION, CONF_ID
|
CONF_REFERENCE_TEMPERATURE, CONF_SENSOR, CONF_TEMPERATURE, CONF_VALUE, ICON_THERMOMETER, \
|
||||||
|
UNIT_CELSIUS
|
||||||
|
|
||||||
ntc_ns = cg.esphome_ns.namespace('ntc')
|
ntc_ns = cg.esphome_ns.namespace('ntc')
|
||||||
NTC = ntc_ns.class_('NTC', cg.Component, sensor.Sensor)
|
NTC = ntc_ns.class_('NTC', cg.Component, sensor.Sensor)
|
||||||
|
|
||||||
CONF_B_CONSTANT = 'b_constant'
|
CONF_B_CONSTANT = 'b_constant'
|
||||||
CONF_REFERENCE_TEMPERATURE = 'reference_temperature'
|
|
||||||
CONF_REFERENCE_RESISTANCE = 'reference_resistance'
|
|
||||||
CONF_A = 'a'
|
CONF_A = 'a'
|
||||||
CONF_B = 'b'
|
CONF_B = 'b'
|
||||||
CONF_C = 'c'
|
CONF_C = 'c'
|
||||||
|
|
|
@ -224,6 +224,7 @@ CONF_LOGS = 'logs'
|
||||||
CONF_LOW = 'low'
|
CONF_LOW = 'low'
|
||||||
CONF_LOW_VOLTAGE_REFERENCE = 'low_voltage_reference'
|
CONF_LOW_VOLTAGE_REFERENCE = 'low_voltage_reference'
|
||||||
CONF_MAC_ADDRESS = 'mac_address'
|
CONF_MAC_ADDRESS = 'mac_address'
|
||||||
|
CONF_MAINS_FILTER = 'mains_filter'
|
||||||
CONF_MAKE_ID = 'make_id'
|
CONF_MAKE_ID = 'make_id'
|
||||||
CONF_MANUAL_IP = 'manual_ip'
|
CONF_MANUAL_IP = 'manual_ip'
|
||||||
CONF_MASK_DISTURBER = 'mask_disturber'
|
CONF_MASK_DISTURBER = 'mask_disturber'
|
||||||
|
@ -343,6 +344,8 @@ CONF_RAW = 'raw'
|
||||||
CONF_REBOOT_TIMEOUT = 'reboot_timeout'
|
CONF_REBOOT_TIMEOUT = 'reboot_timeout'
|
||||||
CONF_RECEIVE_TIMEOUT = 'receive_timeout'
|
CONF_RECEIVE_TIMEOUT = 'receive_timeout'
|
||||||
CONF_RED = 'red'
|
CONF_RED = 'red'
|
||||||
|
CONF_REFERENCE_RESISTANCE = 'reference_resistance'
|
||||||
|
CONF_REFERENCE_TEMPERATURE = 'reference_temperature'
|
||||||
CONF_REPEAT = 'repeat'
|
CONF_REPEAT = 'repeat'
|
||||||
CONF_REPOSITORY = 'repository'
|
CONF_REPOSITORY = 'repository'
|
||||||
CONF_RESET_PIN = 'reset_pin'
|
CONF_RESET_PIN = 'reset_pin'
|
||||||
|
@ -358,6 +361,8 @@ CONF_RGBW = 'rgbw'
|
||||||
CONF_RISING_EDGE = 'rising_edge'
|
CONF_RISING_EDGE = 'rising_edge'
|
||||||
CONF_ROTATION = 'rotation'
|
CONF_ROTATION = 'rotation'
|
||||||
CONF_RS_PIN = 'rs_pin'
|
CONF_RS_PIN = 'rs_pin'
|
||||||
|
CONF_RTD_NOMINAL_RESISTANCE = 'rtd_nominal_resistance'
|
||||||
|
CONF_RTD_WIRES = 'rtd_wires'
|
||||||
CONF_RUN_CYCLES = 'run_cycles'
|
CONF_RUN_CYCLES = 'run_cycles'
|
||||||
CONF_RUN_DURATION = 'run_duration'
|
CONF_RUN_DURATION = 'run_duration'
|
||||||
CONF_RW_PIN = 'rw_pin'
|
CONF_RW_PIN = 'rw_pin'
|
||||||
|
|
|
@ -451,6 +451,14 @@ sensor:
|
||||||
name: "Den Temperature"
|
name: "Den Temperature"
|
||||||
cs_pin: GPIO23
|
cs_pin: GPIO23
|
||||||
update_interval: 15s
|
update_interval: 15s
|
||||||
|
reference_temperature:
|
||||||
|
name: "MAX31855 Internal Temperature"
|
||||||
|
- platform: max31865
|
||||||
|
name: "Water Tank Temperature"
|
||||||
|
cs_pin: GPIO23
|
||||||
|
update_interval: 15s
|
||||||
|
reference_resistance: "430 Ω"
|
||||||
|
rtd_nominal_resistance: "100 Ω"
|
||||||
- platform: mhz19
|
- platform: mhz19
|
||||||
co2:
|
co2:
|
||||||
name: "MH-Z19 CO2 Value"
|
name: "MH-Z19 CO2 Value"
|
||||||
|
|
Loading…
Reference in a new issue