forked from esphome/esphome
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CT Clamp component (esphome#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 <[email protected]>
- Loading branch information
1 parent
15cb0e4
commit 3089ffa
Showing
5 changed files
with
146 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters