From 9301f76482de58997712cefbe4391d5b9bca74ac Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 7 May 2026 06:59:22 +1000 Subject: [PATCH] [sensor] Add alternate calibration format for ntc (#15937) --- esphome/components/const/__init__.py | 5 ++- esphome/components/lc709203f/sensor.py | 3 +- esphome/components/ntc/sensor.py | 2 +- esphome/components/sensor/__init__.py | 49 ++++++++++++++++++---- tests/components/template/common-base.yaml | 7 +++- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/esphome/components/const/__init__.py b/esphome/components/const/__init__.py index 846d3fd883..6f418b48ea 100644 --- a/esphome/components/const/__init__.py +++ b/esphome/components/const/__init__.py @@ -2,11 +2,12 @@ CODEOWNERS = ["@esphome/core"] -CONF_BYTE_ORDER = "byte_order" -CONF_CLIMATE_ID = "climate_id" BYTE_ORDER_LITTLE = "little_endian" BYTE_ORDER_BIG = "big_endian" +CONF_B_CONSTANT = "b_constant" +CONF_BYTE_ORDER = "byte_order" +CONF_CLIMATE_ID = "climate_id" CONF_COLOR_DEPTH = "color_depth" CONF_CRC_ENABLE = "crc_enable" CONF_DATA_BITS = "data_bits" diff --git a/esphome/components/lc709203f/sensor.py b/esphome/components/lc709203f/sensor.py index 75ae703638..d4e6213425 100644 --- a/esphome/components/lc709203f/sensor.py +++ b/esphome/components/lc709203f/sensor.py @@ -1,5 +1,6 @@ import esphome.codegen as cg from esphome.components import i2c, sensor +from esphome.components.const import CONF_B_CONSTANT import esphome.config_validation as cv from esphome.const import ( CONF_BATTERY_LEVEL, @@ -22,8 +23,6 @@ DEPENDENCIES = ["i2c"] lc709203f_ns = cg.esphome_ns.namespace("lc709203f") -CONF_B_CONSTANT = "b_constant" - LC709203FBatteryVoltage = lc709203f_ns.enum("LC709203FBatteryVoltage") BATTERY_VOLTAGE_OPTIONS = { "3.7": LC709203FBatteryVoltage.LC709203F_BATTERY_VOLTAGE_3_7, diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index d47052cac6..dd7d1bd35d 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -2,6 +2,7 @@ from math import log import esphome.codegen as cg from esphome.components import sensor +from esphome.components.const import CONF_B_CONSTANT import esphome.config_validation as cv from esphome.const import ( CONF_CALIBRATION, @@ -18,7 +19,6 @@ from esphome.const import ( ntc_ns = cg.esphome_ns.namespace("ntc") NTC = ntc_ns.class_("NTC", cg.Component, sensor.Sensor) -CONF_B_CONSTANT = "b_constant" CONF_A = "a" CONF_B = "b" CONF_C = "c" diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ed02cc2543..f076c7f17b 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -4,6 +4,7 @@ import math from esphome import automation import esphome.codegen as cg from esphome.components import mqtt, web_server, zigbee +from esphome.components.const import CONF_B_CONSTANT import esphome.config_validation as cv from esphome.const import ( CONF_ABOVE, @@ -32,6 +33,8 @@ from esphome.const import ( CONF_OPTIMISTIC, CONF_PERIOD, CONF_QUANTILE, + CONF_REFERENCE_RESISTANCE, + CONF_REFERENCE_TEMPERATURE, CONF_SEND_EVERY, CONF_SEND_FIRST_AT, CONF_STATE_CLASS, @@ -1078,16 +1081,44 @@ def ntc_get_abc(value): return a, b, c +def ntc_calc_b_constant(value): + beta = value[CONF_B_CONSTANT] + t0 = value[CONF_REFERENCE_TEMPERATURE] + ZERO_POINT + r0 = value[CONF_REFERENCE_RESISTANCE] + + a = (1 / t0) - (1 / beta) * math.log(r0) + b = 1 / beta + c = 0 + return a, b, c + + def ntc_process_calibration(value): if isinstance(value, dict): - value = cv.Schema( - { - cv.Required(CONF_A): cv.float_, - cv.Required(CONF_B): cv.float_, - cv.Required(CONF_C): cv.float_, - } - )(value) - a, b, c = ntc_get_abc(value) + if CONF_B_CONSTANT in value: + value = cv.Schema( + { + cv.Required(CONF_B_CONSTANT): cv.All( + cv.float_, cv.Range(min=0, min_included=False) + ), + cv.Required(CONF_REFERENCE_TEMPERATURE): cv.All( + cv.temperature, + cv.Range(min=-ZERO_POINT, min_included=False), + ), + cv.Required(CONF_REFERENCE_RESISTANCE): cv.All( + cv.resistance, cv.Range(min=0, min_included=False) + ), + } + )(value) + a, b, c = ntc_calc_b_constant(value) + else: + value = cv.Schema( + { + cv.Required(CONF_A): cv.float_, + cv.Required(CONF_B): cv.float_, + cv.Required(CONF_C): cv.float_, + } + )(value) + a, b, c = ntc_get_abc(value) elif isinstance(value, list): if len(value) != 3: raise cv.Invalid( @@ -1097,7 +1128,7 @@ def ntc_process_calibration(value): a, b, c = ntc_calc_steinhart_hart(value) else: raise cv.Invalid( - f"Calibration parameter accepts either a list for steinhart-hart calibration, or mapping for b-constant calibration, not {type(value)}" + f"Calibration parameter accepts either a list for steinhart-hart calibration, or mapping for b-constant or precomputed (a, b, c) calibration, not {type(value)}" ) _LOGGER.info("Coefficient: a:%s, b:%s, c:%s", a, b, c) return { diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index b97cafd25c..d3985a848b 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -202,6 +202,11 @@ sensor: value: last - timeout: timeout: 1d + - to_ntc_temperature: + calibration: + b_constant: 3950 + reference_temperature: 25.0°C + reference_resistance: 10kOhm - to_ntc_resistance: calibration: - 10.0kOhm -> 25°C @@ -270,8 +275,6 @@ cover: stop_action: - logger.log: stop_action optimistic: true - on_open: - - logger.log: "Cover on_open (deprecated)" on_opened: - logger.log: "Cover fully opened" on_closed: