mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00: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:
		| @@ -127,7 +127,10 @@ def wrap_to_code(name, comp): | ||||
|     def wrapped(conf): | ||||
|         cg.add(cg.LineComment(u"{}:".format(name))) | ||||
|         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) | ||||
|  | ||||
|     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_BUS_VOLTAGE = 'bus_voltage' | ||||
| CONF_CALIBRATE_LINEAR = 'calibrate_linear' | ||||
| CONF_CALIBRATION = 'calibration' | ||||
| CONF_CARRIER_DUTY_PERCENT = 'carrier_duty_percent' | ||||
| CONF_CARRIER_FREQUENCY = 'carrier_frequency' | ||||
| CONF_CHANGE_MODE_EVERY = 'change_mode_every' | ||||
|   | ||||
| @@ -19,12 +19,16 @@ def patch_structhash(): | ||||
|     # all issues | ||||
|     from platformio.commands import run | ||||
|     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 import makedirs | ||||
|  | ||||
|     def patched_clean_build_dir(build_dir): | ||||
|         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 isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir): | ||||
|   | ||||
| @@ -115,6 +115,20 @@ sensor: | ||||
|       - calibrate_linear: | ||||
|           - 0 -> 0 | ||||
|           - 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 | ||||
|     red_channel: | ||||
|       name: Red Channel | ||||
|   | ||||
		Reference in New Issue
	
	Block a user