From 9cd173ef83b4a0acfc42a5406725d81d064fcb6a Mon Sep 17 00:00:00 2001 From: guillempages Date: Thu, 25 May 2023 23:49:52 +0200 Subject: [PATCH 1/3] Allow partially looping animations (#4693) Add the possibility of specifying a "loop" in an animation; where the requested frames (start - end) will be repeateadly shown for "count" times. --- esphome/components/animation/__init__.py | 25 ++++++++++++++++++- esphome/components/display/display_buffer.cpp | 23 +++++++++++++++-- esphome/components/display/display_buffer.h | 10 ++++++-- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 1b804bd527..f51d115d9e 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -6,7 +6,14 @@ import esphome.components.image as espImage from esphome.components.image import CONF_USE_TRANSPARENCY import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_RESIZE, CONF_TYPE +from esphome.const import ( + CONF_FILE, + CONF_ID, + CONF_RAW_DATA_ID, + CONF_REPEAT, + CONF_RESIZE, + CONF_TYPE, +) from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) @@ -14,6 +21,10 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ["display"] MULTI_CONF = True +CONF_LOOP = "loop" +CONF_START_FRAME = "start_frame" +CONF_END_FRAME = "end_frame" + Animation_ = display.display_ns.class_("Animation", espImage.Image_) @@ -48,6 +59,13 @@ ANIMATION_SCHEMA = cv.Schema( # Not setting default here on purpose; the default depends on the image type, # and thus will be set in the "validate_cross_dependencies" validator. cv.Optional(CONF_USE_TRANSPARENCY): cv.boolean, + cv.Optional(CONF_LOOP): cv.All( + { + cv.Optional(CONF_START_FRAME, default=0): cv.positive_int, + cv.Optional(CONF_END_FRAME): cv.positive_int, + cv.Optional(CONF_REPEAT): cv.positive_int, + } + ), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), }, validate_cross_dependencies, @@ -227,3 +245,8 @@ async def to_code(config): espImage.IMAGE_TYPE[config[CONF_TYPE]], ) cg.add(var.set_transparency(transparent)) + if CONF_LOOP in config: + start = config[CONF_LOOP][CONF_START_FRAME] + end = config[CONF_LOOP].get(CONF_END_FRAME, frames) + count = config[CONF_LOOP].get(CONF_REPEAT, -1) + cg.add(var.set_loop(start, end, count)) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 35e55bc1ba..0d76fa09ec 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -773,12 +773,31 @@ Color Animation::get_grayscale_pixel(int x, int y) const { return Color(gray, gray, gray, alpha); } Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) - : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {} -int Animation::get_animation_frame_count() const { return this->animation_frame_count_; } + : Image(data_start, width, height, type), + current_frame_(0), + animation_frame_count_(animation_frame_count), + loop_start_frame_(0), + loop_end_frame_(animation_frame_count_), + loop_count_(0), + loop_current_iteration_(1) {} +void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { + loop_start_frame_ = std::min(start_frame, animation_frame_count_); + loop_end_frame_ = std::min(end_frame, animation_frame_count_); + loop_count_ = count; + loop_current_iteration_ = 1; +} + +uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } int Animation::get_current_frame() const { return this->current_frame_; } void Animation::next_frame() { this->current_frame_++; + if (loop_count_ && this->current_frame_ == loop_end_frame_ && + (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { + this->current_frame_ = loop_start_frame_; + this->loop_current_iteration_++; + } if (this->current_frame_ >= animation_frame_count_) { + this->loop_current_iteration_ = 1; this->current_frame_ = 0; } } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index a8ec0e588f..2474d6f5a0 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -569,7 +569,7 @@ class Animation : public Image { Color get_rgb565_pixel(int x, int y) const override; Color get_grayscale_pixel(int x, int y) const override; - int get_animation_frame_count() const; + uint32_t get_animation_frame_count() const; int get_current_frame() const override; void next_frame(); void prev_frame(); @@ -580,9 +580,15 @@ class Animation : public Image { */ void set_frame(int frame); + void set_loop(uint32_t start_frame, uint32_t end_frame, int count); + protected: int current_frame_; - int animation_frame_count_; + uint32_t animation_frame_count_; + uint32_t loop_start_frame_; + uint32_t loop_end_frame_; + int loop_count_; + int loop_current_iteration_; }; template class DisplayPageShowAction : public Action { From 79abd773a2c402b65f0637b3d18267f383e51074 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 26 May 2023 15:50:44 +1200 Subject: [PATCH 2/3] Allow i2s microphone bits per sample to be configured (#4884) --- .../i2s_audio/microphone/__init__.py | 14 +++++++- .../microphone/i2s_audio_microphone.cpp | 35 ++++++++++++++++--- .../microphone/i2s_audio_microphone.h | 4 ++- esphome/components/microphone/__init__.py | 2 +- esphome/components/microphone/automation.h | 4 +-- esphome/components/microphone/microphone.h | 4 +-- .../voice_assistant/voice_assistant.cpp | 5 +-- 7 files changed, 54 insertions(+), 14 deletions(-) diff --git a/esphome/components/i2s_audio/microphone/__init__.py b/esphome/components/i2s_audio/microphone/__init__.py index 089e796ae0..07f5158188 100644 --- a/esphome/components/i2s_audio/microphone/__init__.py +++ b/esphome/components/i2s_audio/microphone/__init__.py @@ -20,6 +20,7 @@ DEPENDENCIES = ["i2s_audio"] CONF_ADC_PIN = "adc_pin" CONF_ADC_TYPE = "adc_type" CONF_PDM = "pdm" +CONF_BITS_PER_SAMPLE = "bits_per_sample" I2SAudioMicrophone = i2s_audio_ns.class_( "I2SAudioMicrophone", I2SAudioIn, microphone.Microphone, cg.Component @@ -30,10 +31,17 @@ CHANNELS = { "left": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_LEFT, "right": i2s_channel_fmt_t.I2S_CHANNEL_FMT_ONLY_RIGHT, } +i2s_bits_per_sample_t = cg.global_ns.enum("i2s_bits_per_sample_t") +BITS_PER_SAMPLE = { + 16: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_16BIT, + 32: i2s_bits_per_sample_t.I2S_BITS_PER_SAMPLE_32BIT, +} INTERNAL_ADC_VARIANTS = [esp32.const.VARIANT_ESP32] PDM_VARIANTS = [esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S3] +_validate_bits = cv.float_with_unit("bits", "bit") + def validate_esp32_variant(config): variant = esp32.get_esp32_variant() @@ -54,6 +62,9 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend( cv.GenerateID(): cv.declare_id(I2SAudioMicrophone), cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent), cv.Optional(CONF_CHANNEL, default="right"): cv.enum(CHANNELS), + cv.Optional(CONF_BITS_PER_SAMPLE, default="16bit"): cv.All( + _validate_bits, cv.enum(BITS_PER_SAMPLE) + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -93,6 +104,7 @@ async def to_code(config): cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN])) cg.add(var.set_pdm(config[CONF_PDM])) - cg.add(var.set_channel(CHANNELS[config[CONF_CHANNEL]])) + cg.add(var.set_channel(config[CONF_CHANNEL])) + cg.add(var.set_bits_per_sample(config[CONF_BITS_PER_SAMPLE])) await microphone.register_microphone(var, config) diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp index 9452762e94..9c661c3ac2 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp @@ -16,7 +16,13 @@ static const char *const TAG = "i2s_audio.microphone"; void I2SAudioMicrophone::setup() { ESP_LOGCONFIG(TAG, "Setting up I2S Audio Microphone..."); - this->buffer_.resize(BUFFER_SIZE); + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->buffer_ = allocator.allocate(BUFFER_SIZE); + if (this->buffer_ == nullptr) { + ESP_LOGE(TAG, "Failed to allocate buffer!"); + this->mark_failed(); + return; + } #if SOC_I2S_SUPPORTS_ADC if (this->adc_) { @@ -48,7 +54,7 @@ void I2SAudioMicrophone::start_() { i2s_driver_config_t config = { .mode = (i2s_mode_t) (I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = 16000, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .bits_per_sample = this->bits_per_sample_, .channel_format = this->channel_, .communication_format = I2S_COMM_FORMAT_STAND_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, @@ -107,16 +113,35 @@ void I2SAudioMicrophone::stop_() { void I2SAudioMicrophone::read_() { size_t bytes_read = 0; esp_err_t err = - i2s_read(this->parent_->get_port(), this->buffer_.data(), BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); + i2s_read(this->parent_->get_port(), this->buffer_, BUFFER_SIZE, &bytes_read, (100 / portTICK_PERIOD_MS)); if (err != ESP_OK) { ESP_LOGW(TAG, "Error reading from I2S microphone: %s", esp_err_to_name(err)); this->status_set_warning(); return; } - this->status_clear_warning(); - this->data_callbacks_.call(this->buffer_); + std::vector samples; + size_t samples_read = 0; + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + samples_read = bytes_read / sizeof(int16_t); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + samples_read = bytes_read / sizeof(int32_t); + } else { + ESP_LOGE(TAG, "Unsupported bits per sample: %d", this->bits_per_sample_); + return; + } + samples.resize(samples_read); + if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_16BIT) { + memcpy(samples.data(), this->buffer_, bytes_read); + } else if (this->bits_per_sample_ == I2S_BITS_PER_SAMPLE_32BIT) { + for (size_t i = 0; i < samples_read; i++) { + int32_t temp = reinterpret_cast(this->buffer_)[i] >> 14; + samples[i] = clamp(temp, INT16_MIN, INT16_MAX); + } + } + + this->data_callbacks_.call(samples); } void I2SAudioMicrophone::loop() { diff --git a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h index acc7d2b45a..0cb87d42fd 100644 --- a/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +++ b/esphome/components/i2s_audio/microphone/i2s_audio_microphone.h @@ -29,6 +29,7 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub #endif void set_channel(i2s_channel_fmt_t channel) { this->channel_ = channel; } + void set_bits_per_sample(i2s_bits_per_sample_t bits_per_sample) { this->bits_per_sample_ = bits_per_sample; } protected: void start_(); @@ -41,8 +42,9 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub bool adc_{false}; #endif bool pdm_{false}; - std::vector buffer_; + uint8_t *buffer_; i2s_channel_fmt_t channel_; + i2s_bits_per_sample_t bits_per_sample_; HighFrequencyLoopRequester high_freq_; }; diff --git a/esphome/components/microphone/__init__.py b/esphome/components/microphone/__init__.py index ff1f7aa963..d99500bbed 100644 --- a/esphome/components/microphone/__init__.py +++ b/esphome/components/microphone/__init__.py @@ -41,7 +41,7 @@ async def setup_microphone_core_(var, config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation( trigger, - [(cg.std_vector.template(cg.uint8).operator("ref").operator("const"), "x")], + [(cg.std_vector.template(cg.int16).operator("ref").operator("const"), "x")], conf, ) diff --git a/esphome/components/microphone/automation.h b/esphome/components/microphone/automation.h index 5f404b8d74..5313f07f72 100644 --- a/esphome/components/microphone/automation.h +++ b/esphome/components/microphone/automation.h @@ -16,10 +16,10 @@ template class StopCaptureAction : public Action, public void play(Ts... x) override { this->parent_->stop(); } }; -class DataTrigger : public Trigger &> { +class DataTrigger : public Trigger &> { public: explicit DataTrigger(Microphone *mic) { - mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); + mic->add_data_callback([this](const std::vector &data) { this->trigger(data); }); } }; diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index b725f66ad7..5b16a67c00 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -17,7 +17,7 @@ class Microphone { public: virtual void start() = 0; virtual void stop() = 0; - void add_data_callback(std::function &)> &&data_callback) { + void add_data_callback(std::function &)> &&data_callback) { this->data_callbacks_.add(std::move(data_callback)); } @@ -26,7 +26,7 @@ class Microphone { protected: State state_{STATE_STOPPED}; - CallbackManager &)> data_callbacks_{}; + CallbackManager &)> data_callbacks_{}; }; } // namespace microphone diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index fb96d484d4..4245578711 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -58,11 +58,12 @@ void VoiceAssistant::setup() { } #endif - this->mic_->add_data_callback([this](const std::vector &data) { + this->mic_->add_data_callback([this](const std::vector &data) { if (!this->running_) { return; } - this->socket_->sendto(data.data(), data.size(), 0, (struct sockaddr *) &this->dest_addr_, sizeof(this->dest_addr_)); + this->socket_->sendto(data.data(), data.size() * sizeof(int16_t), 0, (struct sockaddr *) &this->dest_addr_, + sizeof(this->dest_addr_)); }); } From 97c1c347082f471d345311a8d31361ebef5130ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 26 May 2023 07:01:21 +0200 Subject: [PATCH 3/3] Add support for TMP1075 temperature sensor (#4776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for TMP1075 temperature sensor TMP1075 is a temperature sensor with I2C interface in industry standard LM75 form factor and pinout. https://www.ti.com/product/TMP1075 Example YAML: ```yaml sensor: - platform: tmp1075 name: TMP1075 Temperature id: radiator_temp update_interval: 10s i2c_id: i2c_bus_1 conversion_rate: 27.5ms alert: limit_low: 50 limit_high: 75 fault_count: 1 polarity: active_high ``` * Add myself as codeowner of the TMP1075 component * Include '°C' unit when logging low/high limit setting * Reformat No functional changes. * Fix logging: use %.4f for temperatures, not %d * Fix config initialisation * Use relative include for `tmp1075.h` * Apply formatting changes suggested by script/clang-tidy for ESP32 * Add YAML to test1.yaml * Fix test1.yaml by giving TMP1075 a name * Less verbose logging (debug -> verbose level) * Schema: reduce accuracy_decimals to 2 * I2C address as hexadecimal * Proper name for enum in Python The enum on the C++ side was renamed (clang-tidy) but I forgot to take that into account in the Python code. * Expose 'alert function' to the code generator/YAML params and remove 'shutdown' Shutdown mode doesn't work the way I expect it, so remove it until someone actually asks for it. Also 'alert mode' was renamed to 'alert function' for clarity. * Move simple setters to header file * Remove `load_config_();` function --- CODEOWNERS | 1 + esphome/components/tmp1075/__init__.py | 1 + esphome/components/tmp1075/sensor.py | 92 ++++++++++++++++++ esphome/components/tmp1075/tmp1075.cpp | 129 +++++++++++++++++++++++++ esphome/components/tmp1075/tmp1075.h | 92 ++++++++++++++++++ tests/test1.yaml | 11 +++ 6 files changed, 326 insertions(+) create mode 100644 esphome/components/tmp1075/__init__.py create mode 100644 esphome/components/tmp1075/sensor.py create mode 100644 esphome/components/tmp1075/tmp1075.cpp create mode 100644 esphome/components/tmp1075/tmp1075.h diff --git a/CODEOWNERS b/CODEOWNERS index ded5501c62..1b75203654 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -285,6 +285,7 @@ esphome/components/tm1637/* @glmnet esphome/components/tm1638/* @skykingjwc esphome/components/tm1651/* @freekode esphome/components/tmp102/* @timsavage +esphome/components/tmp1075/* @sybrenstuvel esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 diff --git a/esphome/components/tmp1075/__init__.py b/esphome/components/tmp1075/__init__.py new file mode 100644 index 0000000000..ddd04ad11a --- /dev/null +++ b/esphome/components/tmp1075/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@sybrenstuvel"] diff --git a/esphome/components/tmp1075/sensor.py b/esphome/components/tmp1075/sensor.py new file mode 100644 index 0000000000..25ec350b7a --- /dev/null +++ b/esphome/components/tmp1075/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_THERMOMETER, +) + +DEPENDENCIES = ["i2c"] + +tmp1075_ns = cg.esphome_ns.namespace("tmp1075") + +TMP1075Sensor = tmp1075_ns.class_( + "TMP1075Sensor", cg.PollingComponent, sensor.Sensor, i2c.I2CDevice +) + +EConversionRate = tmp1075_ns.enum("EConversionRate") +CONVERSION_RATES = { + "27.5ms": EConversionRate.CONV_RATE_27_5_MS, + "55ms": EConversionRate.CONV_RATE_55_MS, + "110ms": EConversionRate.CONV_RATE_110_MS, + "220ms": EConversionRate.CONV_RATE_220_MS, +} + +POLARITY = { + "ACTIVE_LOW": 0, + "ACTIVE_HIGH": 1, +} + +EAlertFunction = tmp1075_ns.enum("EAlertFunction") +ALERT_FUNCTION = { + "COMPARATOR": EAlertFunction.ALERT_COMPARATOR, + "INTERRUPT": EAlertFunction.ALERT_INTERRUPT, +} + +CONF_ALERT = "alert" +CONF_LIMIT_LOW = "limit_low" +CONF_LIMIT_HIGH = "limit_high" +CONF_FAULT_COUNT = "fault_count" +CONF_POLARITY = "polarity" +CONF_CONVERSION_RATE = "conversion_rate" +CONF_FUNCTION = "function" + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + TMP1075Sensor, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.Optional(CONF_CONVERSION_RATE): cv.enum(CONVERSION_RATES, lower=True), + cv.Optional(CONF_ALERT, default={}): cv.Schema( + { + cv.Optional(CONF_LIMIT_LOW): cv.temperature, + cv.Optional(CONF_LIMIT_HIGH): cv.temperature, + cv.Optional(CONF_FAULT_COUNT): cv.int_range(min=1, max=4), + cv.Optional(CONF_POLARITY): cv.enum(POLARITY, upper=True), + cv.Optional(CONF_FUNCTION): cv.enum(ALERT_FUNCTION, upper=True), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x48)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_CONVERSION_RATE in config: + cg.add(var.set_conversion_rate(config[CONF_CONVERSION_RATE])) + + alert = config[CONF_ALERT] + if CONF_LIMIT_LOW in alert: + cg.add(var.set_alert_limit_low(alert[CONF_LIMIT_LOW])) + if CONF_LIMIT_HIGH in alert: + cg.add(var.set_alert_limit_high(alert[CONF_LIMIT_HIGH])) + if CONF_FAULT_COUNT in alert: + cg.add(var.set_fault_count(alert[CONF_FAULT_COUNT])) + if CONF_POLARITY in alert: + cg.add(var.set_alert_polarity(alert[CONF_POLARITY])) + if CONF_FUNCTION in alert: + cg.add(var.set_alert_function(alert[CONF_FUNCTION])) diff --git a/esphome/components/tmp1075/tmp1075.cpp b/esphome/components/tmp1075/tmp1075.cpp new file mode 100644 index 0000000000..38ed2bea31 --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.cpp @@ -0,0 +1,129 @@ +#include "esphome/core/log.h" +#include "tmp1075.h" + +namespace esphome { +namespace tmp1075 { + +static const char *const TAG = "tmp1075"; + +constexpr uint8_t REG_TEMP = 0x0; // Temperature result +constexpr uint8_t REG_CFGR = 0x1; // Configuration +constexpr uint8_t REG_LLIM = 0x2; // Low limit +constexpr uint8_t REG_HLIM = 0x3; // High limit +constexpr uint8_t REG_DIEID = 0xF; // Device ID + +constexpr uint16_t EXPECT_DIEID = 0x0075; // Expected Device ID. + +static uint16_t temp2regvalue(float temp); +static float regvalue2temp(uint16_t regvalue); + +void TMP1075Sensor::setup() { + uint8_t die_id; + if (!this->read_byte(REG_DIEID, &die_id)) { + ESP_LOGW(TAG, "'%s' - unable to read ID", this->name_.c_str()); + this->mark_failed(); + return; + } + if (die_id != EXPECT_DIEID) { + ESP_LOGW(TAG, "'%s' - unexpected ID 0x%x found, expected 0x%x", this->name_.c_str(), die_id, EXPECT_DIEID); + this->mark_failed(); + return; + } + + this->write_config(); +} + +void TMP1075Sensor::update() { + uint16_t regvalue; + if (!read_byte_16(REG_TEMP, ®value)) { + ESP_LOGW(TAG, "'%s' - unable to read temperature register", this->name_.c_str()); + this->status_set_warning(); + return; + } + + const float temp = regvalue2temp(regvalue); + this->publish_state(temp); +} + +void TMP1075Sensor::dump_config() { + LOG_SENSOR("", "TMP1075 Sensor", this); + if (this->is_failed()) { + ESP_LOGE(TAG, " Communication with TMP1075 failed!"); + return; + } + ESP_LOGCONFIG(TAG, " limit low : %.4f °C", alert_limit_low_); + ESP_LOGCONFIG(TAG, " limit high : %.4f °C", alert_limit_high_); + ESP_LOGCONFIG(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGCONFIG(TAG, " rate : %d", config_.fields.rate); + ESP_LOGCONFIG(TAG, " fault_count: %d", config_.fields.faults); + ESP_LOGCONFIG(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGCONFIG(TAG, " alert_mode : %d", config_.fields.alert_mode); + ESP_LOGCONFIG(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::set_fault_count(const int faults) { + if (faults < 1) { + ESP_LOGE(TAG, "'%s' - fault_count too low: %d", this->name_.c_str(), faults); + return; + } + if (faults > 4) { + ESP_LOGE(TAG, "'%s' - fault_count too high: %d", this->name_.c_str(), faults); + return; + } + config_.fields.faults = faults - 1; +} + +void TMP1075Sensor::log_config_() { + ESP_LOGV(TAG, " oneshot : %d", config_.fields.oneshot); + ESP_LOGV(TAG, " rate : %d", config_.fields.rate); + ESP_LOGV(TAG, " faults : %d", config_.fields.faults); + ESP_LOGV(TAG, " polarity : %d", config_.fields.polarity); + ESP_LOGV(TAG, " alert_mode: %d", config_.fields.alert_mode); + ESP_LOGV(TAG, " shutdown : %d", config_.fields.shutdown); +} + +void TMP1075Sensor::write_config() { + send_alert_limit_low_(); + send_alert_limit_high_(); + send_config_(); +} + +void TMP1075Sensor::send_config_() { + ESP_LOGV(TAG, "'%s' - sending configuration %04x", this->name_.c_str(), config_.regvalue); + log_config_(); + if (!this->write_byte_16(REG_CFGR, config_.regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write configuration register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_low_() { + ESP_LOGV(TAG, "'%s' - sending alert limit low %.3f °C", this->name_.c_str(), alert_limit_low_); + const uint16_t regvalue = temp2regvalue(alert_limit_low_); + if (!this->write_byte_16(REG_LLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write low limit register", this->name_.c_str()); + return; + } +} + +void TMP1075Sensor::send_alert_limit_high_() { + ESP_LOGV(TAG, "'%s' - sending alert limit high %.3f °C", this->name_.c_str(), alert_limit_high_); + const uint16_t regvalue = temp2regvalue(alert_limit_high_); + if (!this->write_byte_16(REG_HLIM, regvalue)) { + ESP_LOGW(TAG, "'%s' - unable to write high limit register", this->name_.c_str()); + return; + } +} + +static uint16_t temp2regvalue(const float temp) { + const uint16_t regvalue = temp / 0.0625f; + return regvalue << 4; +} + +static float regvalue2temp(const uint16_t regvalue) { + const int16_t signed_value = regvalue; + return (signed_value >> 4) * 0.0625f; +} + +} // namespace tmp1075 +} // namespace esphome diff --git a/esphome/components/tmp1075/tmp1075.h b/esphome/components/tmp1075/tmp1075.h new file mode 100644 index 0000000000..db2bac517a --- /dev/null +++ b/esphome/components/tmp1075/tmp1075.h @@ -0,0 +1,92 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace tmp1075 { + +struct TMP1075Config { + union { + struct { + uint8_t oneshot : 1; // One-shot conversion mode. Writing 1, starts a single temperature + // conversion. Read returns 0. + + uint8_t rate : 2; // Conversion rate setting when device is in continuous conversion mode. + // 00: 27.5 ms conversion rate + // 01: 55 ms conversion rate + // 10: 110 ms conversion rate + // 11: 220 ms conversion rate (35 ms TMP1075N) + + uint8_t faults : 2; // Consecutive fault measurements to trigger the alert function. + // 00: 1 fault + // 01: 2 faults + // 10: 3 faults (4 faults TMP1075N) + // 11: 4 faults (6 faults TMP1075N) + + uint8_t polarity : 1; // Polarity of the output pin. + // 0: Active low ALERT pin + // 1: Active high ALERT pin + + uint8_t alert_mode : 1; // Selects the function of the ALERT pin. + // 0: ALERT pin functions in comparator mode + // 1: ALERT pin functions in interrupt mode + + uint8_t shutdown : 1; // Sets the device in shutdown mode to conserve power. + // 0: Device is in continuous conversion + // 1: Device is in shutdown mode + uint8_t unused : 8; + } fields; + uint16_t regvalue; + }; +}; + +enum EConversionRate { + CONV_RATE_27_5_MS, + CONV_RATE_55_MS, + CONV_RATE_110_MS, + CONV_RATE_220_MS, +}; + +enum EAlertFunction { + ALERT_COMPARATOR = 0, + ALERT_INTERRUPT = 1, +}; + +class TMP1075Sensor : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void dump_config() override; + + // Call write_config() after calling any of these to send the new config to + // the IC. The setup() function also does this. + void set_alert_limit_low(const float temp) { this->alert_limit_low_ = temp; } + void set_alert_limit_high(const float temp) { this->alert_limit_high_ = temp; } + void set_oneshot(const bool oneshot) { config_.fields.oneshot = oneshot; } + void set_conversion_rate(const enum EConversionRate rate) { config_.fields.rate = rate; } + void set_alert_polarity(const bool polarity) { config_.fields.polarity = polarity; } + void set_alert_function(const enum EAlertFunction function) { config_.fields.alert_mode = function; } + void set_fault_count(int faults); + + void write_config(); + + protected: + TMP1075Config config_ = {}; + + // Disable the alert pin by default. + float alert_limit_low_ = -128.0f; + float alert_limit_high_ = 127.9375f; + + void send_alert_limit_low_(); + void send_alert_limit_high_(); + void send_config_(); + void log_config_(); +}; + +} // namespace tmp1075 +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 0058c08c74..bee0d93faf 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1299,6 +1299,17 @@ sensor: id: temp_etuve humidity: name: "Humidity hyt271" + - platform: tmp1075 + name: "Temperature TMP1075" + update_interval: 10s + i2c_id: i2c_bus + conversion_rate: 27.5ms + alert: + limit_low: 50 + limit_high: 75 + fault_count: 1 + polarity: active_high + function: comparator esp32_touch: setup_mode: false