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): |     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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user