mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	Add CT Clamp component (#559)
* Add CT Clamp component * Update lint * Some more fixes * Make updates to work as an analog sensor consumer * Remove unused imports Update lint suggestions * Move setup_priority to header * Remove unused calibration value * Remove Unique ID - Will be auto generated * Update to use loop and not slow down main loop Co-authored-by: Otto Winter <otto@otto-winter.com>
This commit is contained in:
		
							
								
								
									
										0
									
								
								esphome/components/ct_clamp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								esphome/components/ct_clamp/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										67
									
								
								esphome/components/ct_clamp/ct_clamp_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								esphome/components/ct_clamp/ct_clamp_sensor.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #include "ct_clamp_sensor.h" | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
| #include <cmath> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ct_clamp { | ||||
|  | ||||
| static const char *TAG = "ct_clamp"; | ||||
|  | ||||
| void CTClampSensor::dump_config() { | ||||
|   LOG_SENSOR("", "CT Clamp Sensor", this); | ||||
|   ESP_LOGCONFIG(TAG, "  Sample Duration: %.2fs", this->sample_duration_ / 1e3f); | ||||
|   LOG_UPDATE_INTERVAL(this); | ||||
| } | ||||
|  | ||||
| void CTClampSensor::update() { | ||||
|   // Update only starts the sampling phase, in loop() the actual sampling is happening. | ||||
|  | ||||
|   // Request a high loop() execution interval during sampling phase. | ||||
|   this->high_freq_.start(); | ||||
|  | ||||
|   // Set timeout for ending sampling phase | ||||
|   this->set_timeout("read", this->sample_duration_, [this]() { | ||||
|     this->is_sampling_ = false; | ||||
|     this->high_freq_.stop(); | ||||
|  | ||||
|     if (this->num_samples_ == 0) { | ||||
|       // Shouldn't happen, but let's not crash if it does. | ||||
|       this->publish_state(NAN); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     float raw = this->sample_sum_ / this->num_samples_; | ||||
|     float irms = std::sqrt(raw); | ||||
|     ESP_LOGD(TAG, "'%s' - Raw Value: %.2fA", this->name_.c_str(), irms); | ||||
|     this->publish_state(irms); | ||||
|   }); | ||||
|  | ||||
|   // Set sampling values | ||||
|   this->is_sampling_ = true; | ||||
|   this->num_samples_ = 0; | ||||
|   this->sample_sum_ = 0.0f; | ||||
| } | ||||
|  | ||||
| void CTClampSensor::loop() { | ||||
|   if (!this->is_sampling_) | ||||
|     return; | ||||
|  | ||||
|   // Perform a single sample | ||||
|   float value = this->source_->sample(); | ||||
|  | ||||
|   // Adjust DC offset via low pass filter (exponential moving average) | ||||
|   const float alpha = 0.001f; | ||||
|   this->offset_ = this->offset_ * (1 - alpha) + value * alpha; | ||||
|  | ||||
|   // Filtered value centered around the mid-point (0V) | ||||
|   float filtered = value - this->offset_; | ||||
|  | ||||
|   // IRMS is sqrt(∑v_i²) | ||||
|   float sq = filtered * filtered; | ||||
|   this->sample_sum_ += sq; | ||||
|   this->num_samples_++; | ||||
| } | ||||
|  | ||||
| }  // namespace ct_clamp | ||||
| }  // namespace esphome | ||||
							
								
								
									
										46
									
								
								esphome/components/ct_clamp/ct_clamp_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								esphome/components/ct_clamp/ct_clamp_sensor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/esphal.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/voltage_sampler/voltage_sampler.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ct_clamp { | ||||
|  | ||||
| class CTClampSensor : public sensor::Sensor, public PollingComponent { | ||||
|  public: | ||||
|   void update() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::DATA; } | ||||
|  | ||||
|   void set_sample_duration(uint32_t sample_duration) { sample_duration_ = sample_duration; } | ||||
|   void set_source(voltage_sampler::VoltageSampler *source) { source_ = source; } | ||||
|  | ||||
|  protected: | ||||
|   /// High Frequency loop() requester used during sampling phase. | ||||
|   HighFrequencyLoopRequester high_freq_; | ||||
|  | ||||
|   /// Duration in ms of the sampling phase. | ||||
|   uint32_t sample_duration_; | ||||
|   /// The sampling source to read values from. | ||||
|   voltage_sampler::VoltageSampler *source_; | ||||
|  | ||||
|   /** The DC offset of the circuit. | ||||
|    * | ||||
|    * Diagram: https://learn.openenergymonitor.org/electricity-monitoring/ct-sensors/interface-with-arduino | ||||
|    * | ||||
|    * This is automatically calculated with an exponential moving average/digital low pass filter. | ||||
|    * | ||||
|    * 0.5 is a good initial approximation to start with for most ESP8266 setups. | ||||
|    */ | ||||
|   float offset_ = 0.5f; | ||||
|  | ||||
|   float sample_sum_ = 0.0f; | ||||
|   uint32_t num_samples_ = 0; | ||||
|   bool is_sampling_ = false; | ||||
| }; | ||||
|  | ||||
| }  // namespace ct_clamp | ||||
| }  // namespace esphome | ||||
							
								
								
									
										27
									
								
								esphome/components/ct_clamp/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/ct_clamp/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, voltage_sampler | ||||
| from esphome.const import CONF_SENSOR, CONF_ID, ICON_FLASH, UNIT_AMPERE | ||||
|  | ||||
| AUTO_LOAD = ['voltage_sampler'] | ||||
|  | ||||
| CONF_SAMPLE_DURATION = 'sample_duration' | ||||
|  | ||||
| ct_clamp_ns = cg.esphome_ns.namespace('ct_clamp') | ||||
| CTClampSensor = ct_clamp_ns.class_('CTClampSensor', sensor.Sensor, cg.PollingComponent) | ||||
|  | ||||
| CONFIG_SCHEMA = sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2).extend({ | ||||
|     cv.GenerateID(): cv.declare_id(CTClampSensor), | ||||
|     cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), | ||||
|     cv.Optional(CONF_SAMPLE_DURATION, default='200ms'): cv.positive_time_period_milliseconds, | ||||
| }).extend(cv.polling_component_schema('60s')) | ||||
|  | ||||
|  | ||||
| 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_source(sens)) | ||||
|     cg.add(var.set_sample_duration(config[CONF_SAMPLE_DURATION])) | ||||
		Reference in New Issue
	
	Block a user