mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 08:55:22 +01:00
Add NTC and resistance sensor (#560)
* Add NTC and resistance sensor Fixes https://github.com/esphome/feature-requests/issues/248 * Fix * Fix platformio4 moved get_project_dir
This commit is contained in:
parent
9fa1a334e6
commit
61ba2e0f35
12 changed files with 321 additions and 2 deletions
|
@ -127,7 +127,10 @@ def wrap_to_code(name, comp):
|
||||||
def wrapped(conf):
|
def wrapped(conf):
|
||||||
cg.add(cg.LineComment(u"{}:".format(name)))
|
cg.add(cg.LineComment(u"{}:".format(name)))
|
||||||
if comp.config_schema is not None:
|
if comp.config_schema is not None:
|
||||||
cg.add(cg.LineComment(indent(yaml_util.dump(conf).decode('utf-8'))))
|
conf_str = yaml_util.dump(conf)
|
||||||
|
if IS_PY2:
|
||||||
|
conf_str = conf_str.decode('utf-8')
|
||||||
|
cg.add(cg.LineComment(indent(conf_str)))
|
||||||
yield coro(conf)
|
yield coro(conf)
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
0
esphome/components/ntc/__init__.py
Normal file
0
esphome/components/ntc/__init__.py
Normal file
31
esphome/components/ntc/ntc.cpp
Normal file
31
esphome/components/ntc/ntc.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#include "ntc.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ntc {
|
||||||
|
|
||||||
|
static const char *TAG = "ntc";
|
||||||
|
|
||||||
|
void NTC::setup() {
|
||||||
|
this->sensor_->add_on_state_callback([this](float value) { this->process_(value); });
|
||||||
|
if (this->sensor_->has_state())
|
||||||
|
this->process_(this->sensor_->state);
|
||||||
|
}
|
||||||
|
void NTC::dump_config() { LOG_SENSOR("", "NTC Sensor", this) }
|
||||||
|
float NTC::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
void NTC::process_(float value) {
|
||||||
|
if (isnan(value)) {
|
||||||
|
this->publish_state(NAN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float lr = logf(value);
|
||||||
|
float v = this->a_ + this->b_ * lr + this->c_ * lr * lr * lr;
|
||||||
|
float temp = 1 / v - 273.15f;
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "'%s' - Temperature: %.1f°C", this->name_.c_str(), temp);
|
||||||
|
this->publish_state(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ntc
|
||||||
|
} // namespace esphome
|
29
esphome/components/ntc/ntc.h
Normal file
29
esphome/components/ntc/ntc.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ntc {
|
||||||
|
|
||||||
|
class NTC : public Component, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
void set_sensor(Sensor *sensor) { sensor_ = sensor; }
|
||||||
|
void set_a(float a) { a_ = a; }
|
||||||
|
void set_b(float b) { b_ = b; }
|
||||||
|
void set_c(float c) { c_ = c; }
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void process_(float value);
|
||||||
|
|
||||||
|
sensor::Sensor *sensor_;
|
||||||
|
float a_;
|
||||||
|
float b_;
|
||||||
|
float c_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ntc
|
||||||
|
} // namespace esphome
|
120
esphome/components/ntc/sensor.py
Normal file
120
esphome/components/ntc/sensor.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
# coding=utf-8
|
||||||
|
from math import log
|
||||||
|
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import UNIT_CELSIUS, ICON_THERMOMETER, CONF_SENSOR, CONF_TEMPERATURE, \
|
||||||
|
CONF_VALUE, CONF_CALIBRATION, CONF_ID
|
||||||
|
|
||||||
|
ntc_ns = cg.esphome_ns.namespace('ntc')
|
||||||
|
NTC = ntc_ns.class_('NTC', cg.Component, sensor.Sensor)
|
||||||
|
|
||||||
|
CONF_B_CONSTANT = 'b_constant'
|
||||||
|
CONF_REFERENCE_TEMPERATURE = 'reference_temperature'
|
||||||
|
CONF_REFERENCE_RESISTANCE = 'reference_resistance'
|
||||||
|
CONF_A = 'a'
|
||||||
|
CONF_B = 'b'
|
||||||
|
CONF_C = 'c'
|
||||||
|
ZERO_POINT = 273.15
|
||||||
|
|
||||||
|
|
||||||
|
def validate_calibration_parameter(value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return cv.Schema({
|
||||||
|
cv.Required(CONF_TEMPERATURE): cv.float_,
|
||||||
|
cv.Required(CONF_VALUE): cv.float_,
|
||||||
|
})
|
||||||
|
|
||||||
|
value = cv.string(value)
|
||||||
|
parts = value.split('->')
|
||||||
|
if len(parts) != 2:
|
||||||
|
raise cv.Invalid(u"Calibration parameter must be of form 3000 -> 23°C")
|
||||||
|
voltage = cv.resistance(parts[0].strip())
|
||||||
|
temperature = cv.temperature(parts[1].strip())
|
||||||
|
return validate_calibration_parameter({
|
||||||
|
CONF_TEMPERATURE: temperature,
|
||||||
|
CONF_VALUE: voltage,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def calc_steinhart_hart(value):
|
||||||
|
r1 = value[0][CONF_VALUE]
|
||||||
|
r2 = value[1][CONF_VALUE]
|
||||||
|
r3 = value[2][CONF_VALUE]
|
||||||
|
t1 = value[0][CONF_TEMPERATURE] + ZERO_POINT
|
||||||
|
t2 = value[1][CONF_TEMPERATURE] + ZERO_POINT
|
||||||
|
t3 = value[2][CONF_TEMPERATURE] + ZERO_POINT
|
||||||
|
|
||||||
|
l1 = log(r1)
|
||||||
|
l2 = log(r2)
|
||||||
|
l3 = log(r3)
|
||||||
|
|
||||||
|
y1 = 1/t1
|
||||||
|
y2 = 1/t2
|
||||||
|
y3 = 1/t3
|
||||||
|
|
||||||
|
g2 = (y2-y1)/(l2-l1)
|
||||||
|
g3 = (y3-y1)/(l3-l1)
|
||||||
|
|
||||||
|
c = (g3-g2)/(l3-l2) * 1/(l1+l2+l3)
|
||||||
|
b = g2 - c*(l1*l1 + l1*l2 + l2*l2)
|
||||||
|
a = y1 - (b + l1*l1*c) * l1
|
||||||
|
return a, b, c
|
||||||
|
|
||||||
|
|
||||||
|
def calc_b(value):
|
||||||
|
beta = value[CONF_B_CONSTANT]
|
||||||
|
t0 = value[CONF_REFERENCE_TEMPERATURE] + ZERO_POINT
|
||||||
|
r0 = value[CONF_REFERENCE_RESISTANCE]
|
||||||
|
|
||||||
|
a = (1/t0) - (1/beta) * log(r0)
|
||||||
|
b = 1/beta
|
||||||
|
c = 0
|
||||||
|
|
||||||
|
return a, b, c
|
||||||
|
|
||||||
|
|
||||||
|
def process_calibration(value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = cv.Schema({
|
||||||
|
cv.Required(CONF_B_CONSTANT): cv.float_,
|
||||||
|
cv.Required(CONF_REFERENCE_TEMPERATURE): cv.temperature,
|
||||||
|
cv.Required(CONF_REFERENCE_RESISTANCE): cv.resistance,
|
||||||
|
})(value)
|
||||||
|
a, b, c = calc_b(value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
if len(value) != 3:
|
||||||
|
raise cv.Invalid("Steinhart–Hart Calibration must consist of exactly three values")
|
||||||
|
value = cv.Schema([validate_calibration_parameter])(value)
|
||||||
|
a, b, c = calc_steinhart_hart(value)
|
||||||
|
else:
|
||||||
|
raise cv.Invalid("Calibration parameter accepts either a list for steinhart-hart "
|
||||||
|
"calibration, or mapping for b-constant calibration, "
|
||||||
|
"not {}".format(type(value)))
|
||||||
|
|
||||||
|
return {
|
||||||
|
CONF_A: a,
|
||||||
|
CONF_B: b,
|
||||||
|
CONF_C: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1).extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(NTC),
|
||||||
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_CALIBRATION): process_calibration,
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||||
|
cg.add(var.set_sensor(sens))
|
||||||
|
calib = config[CONF_CALIBRATION]
|
||||||
|
cg.add(var.set_a(calib[CONF_A]))
|
||||||
|
cg.add(var.set_b(calib[CONF_B]))
|
||||||
|
cg.add(var.set_c(calib[CONF_C]))
|
0
esphome/components/resistance/__init__.py
Normal file
0
esphome/components/resistance/__init__.py
Normal file
42
esphome/components/resistance/resistance_sensor.cpp
Normal file
42
esphome/components/resistance/resistance_sensor.cpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#include "resistance_sensor.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace resistance {
|
||||||
|
|
||||||
|
static const char *TAG = "resistance";
|
||||||
|
|
||||||
|
void ResistanceSensor::dump_config() {
|
||||||
|
LOG_SENSOR("", "Resistance Sensor", this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Configuration: %s", this->configuration_ == UPSTREAM ? "UPSTREAM" : "DOWNSTREAM");
|
||||||
|
ESP_LOGCONFIG(TAG, " Resistor: %.2fΩ", this->resistor_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Reference Voltage: %.1fV", this->reference_voltage_);
|
||||||
|
}
|
||||||
|
void ResistanceSensor::process_(float value) {
|
||||||
|
if (isnan(value)) {
|
||||||
|
this->publish_state(NAN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
float res = 0;
|
||||||
|
switch (this->configuration_) {
|
||||||
|
case UPSTREAM:
|
||||||
|
if (value == 0.0f)
|
||||||
|
res = NAN;
|
||||||
|
else
|
||||||
|
res = (this->reference_voltage_ - value) / value;
|
||||||
|
break;
|
||||||
|
case DOWNSTREAM:
|
||||||
|
if (value == this->reference_voltage_)
|
||||||
|
res = NAN;
|
||||||
|
else
|
||||||
|
res = value / (this->reference_voltage_ - value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
res *= this->resistor_;
|
||||||
|
ESP_LOGD(TAG, "'%s' - Resistance %.1fΩ", this->name_.c_str(), res);
|
||||||
|
this->publish_state(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace resistance
|
||||||
|
} // namespace esphome
|
38
esphome/components/resistance/resistance_sensor.h
Normal file
38
esphome/components/resistance/resistance_sensor.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace resistance {
|
||||||
|
|
||||||
|
enum ResistanceConfiguration {
|
||||||
|
UPSTREAM,
|
||||||
|
DOWNSTREAM,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResistanceSensor : public Component, public sensor::Sensor {
|
||||||
|
public:
|
||||||
|
void set_sensor(Sensor *sensor) { sensor_ = sensor; }
|
||||||
|
void set_configuration(ResistanceConfiguration configuration) { configuration_ = configuration; }
|
||||||
|
void set_resistor(float resistor) { resistor_ = resistor; }
|
||||||
|
void set_reference_voltage(float reference_voltage) { reference_voltage_ = reference_voltage; }
|
||||||
|
|
||||||
|
void setup() override {
|
||||||
|
this->sensor_->add_on_state_callback([this](float value) { this->process_(value); });
|
||||||
|
if (this->sensor_->has_state())
|
||||||
|
this->process_(this->sensor_->state);
|
||||||
|
}
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void process_(float value);
|
||||||
|
sensor::Sensor *sensor_;
|
||||||
|
ResistanceConfiguration configuration_;
|
||||||
|
float resistor_;
|
||||||
|
float reference_voltage_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace resistance
|
||||||
|
} // namespace esphome
|
37
esphome/components/resistance/sensor.py
Normal file
37
esphome/components/resistance/sensor.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import CONF_SENSOR, UNIT_OHM, ICON_FLASH, CONF_ID
|
||||||
|
|
||||||
|
resistance_ns = cg.esphome_ns.namespace('resistance')
|
||||||
|
ResistanceSensor = resistance_ns.class_('ResistanceSensor', cg.Component, sensor.Sensor)
|
||||||
|
|
||||||
|
CONF_REFERENCE_VOLTAGE = 'reference_voltage'
|
||||||
|
CONF_CONFIGURATION = 'configuration'
|
||||||
|
CONF_RESISTOR = 'resistor'
|
||||||
|
|
||||||
|
ResistanceConfiguration = resistance_ns.enum('ResistanceConfiguration')
|
||||||
|
CONFIGURATIONS = {
|
||||||
|
'DOWNSTREAM': ResistanceConfiguration.DOWNSTREAM,
|
||||||
|
'UPSTREAM': ResistanceConfiguration.UPSTREAM,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = sensor.sensor_schema(UNIT_OHM, ICON_FLASH, 1).extend({
|
||||||
|
cv.GenerateID(): cv.declare_id(ResistanceSensor),
|
||||||
|
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
|
||||||
|
cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True),
|
||||||
|
cv.Required(CONF_RESISTOR): cv.resistance,
|
||||||
|
cv.Optional(CONF_REFERENCE_VOLTAGE, default='3.3V'): cv.voltage,
|
||||||
|
}).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
|
||||||
|
def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
yield cg.register_component(var, config)
|
||||||
|
yield sensor.register_sensor(var, config)
|
||||||
|
|
||||||
|
sens = yield cg.get_variable(config[CONF_SENSOR])
|
||||||
|
cg.add(var.set_sensor(sens))
|
||||||
|
cg.add(var.set_configuration(config[CONF_CONFIGURATION]))
|
||||||
|
cg.add(var.set_resistor(config[CONF_RESISTOR]))
|
||||||
|
cg.add(var.set_reference_voltage(config[CONF_REFERENCE_VOLTAGE]))
|
|
@ -62,6 +62,7 @@ CONF_BUILD_PATH = 'build_path'
|
||||||
CONF_BUSY_PIN = 'busy_pin'
|
CONF_BUSY_PIN = 'busy_pin'
|
||||||
CONF_BUS_VOLTAGE = 'bus_voltage'
|
CONF_BUS_VOLTAGE = 'bus_voltage'
|
||||||
CONF_CALIBRATE_LINEAR = 'calibrate_linear'
|
CONF_CALIBRATE_LINEAR = 'calibrate_linear'
|
||||||
|
CONF_CALIBRATION = 'calibration'
|
||||||
CONF_CARRIER_DUTY_PERCENT = 'carrier_duty_percent'
|
CONF_CARRIER_DUTY_PERCENT = 'carrier_duty_percent'
|
||||||
CONF_CARRIER_FREQUENCY = 'carrier_frequency'
|
CONF_CARRIER_FREQUENCY = 'carrier_frequency'
|
||||||
CONF_CHANGE_MODE_EVERY = 'change_mode_every'
|
CONF_CHANGE_MODE_EVERY = 'change_mode_every'
|
||||||
|
|
|
@ -19,12 +19,16 @@ def patch_structhash():
|
||||||
# all issues
|
# all issues
|
||||||
from platformio.commands import run
|
from platformio.commands import run
|
||||||
from platformio import util
|
from platformio import util
|
||||||
|
try:
|
||||||
|
from platformio.util import get_project_dir
|
||||||
|
except ImportError:
|
||||||
|
from platformio.project.helpers import get_project_dir
|
||||||
from os.path import join, isdir, getmtime, isfile
|
from os.path import join, isdir, getmtime, isfile
|
||||||
from os import makedirs
|
from os import makedirs
|
||||||
|
|
||||||
def patched_clean_build_dir(build_dir):
|
def patched_clean_build_dir(build_dir):
|
||||||
structhash_file = join(build_dir, "structure.hash")
|
structhash_file = join(build_dir, "structure.hash")
|
||||||
platformio_ini = join(util.get_project_dir(), "platformio.ini")
|
platformio_ini = join(get_project_dir(), "platformio.ini")
|
||||||
|
|
||||||
# if project's config is modified
|
# if project's config is modified
|
||||||
if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir):
|
if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir):
|
||||||
|
|
|
@ -115,6 +115,20 @@ sensor:
|
||||||
- calibrate_linear:
|
- calibrate_linear:
|
||||||
- 0 -> 0
|
- 0 -> 0
|
||||||
- 100 -> 100
|
- 100 -> 100
|
||||||
|
- platform: resistance
|
||||||
|
sensor: my_sensor
|
||||||
|
configuration: DOWNSTREAM
|
||||||
|
resistor: 10kΩ
|
||||||
|
reference_voltage: 3.3V
|
||||||
|
name: Resistance
|
||||||
|
id: resist
|
||||||
|
- platform: ntc
|
||||||
|
sensor: resist
|
||||||
|
name: NTC Sensor
|
||||||
|
calibration:
|
||||||
|
b_constant: 3950
|
||||||
|
reference_resistance: 10k
|
||||||
|
reference_temperature: 25°C
|
||||||
- platform: tcs34725
|
- platform: tcs34725
|
||||||
red_channel:
|
red_channel:
|
||||||
name: Red Channel
|
name: Red Channel
|
||||||
|
|
Loading…
Reference in a new issue