diff --git a/chlorPump.yaml b/chlorPump.yaml index 46acb39..973620d 100644 --- a/chlorPump.yaml +++ b/chlorPump.yaml @@ -45,7 +45,13 @@ sensor: pin: A0 zero_point: 640 inverted: true - update_interval: 2s + update_interval: 1s + average: + mesurements: 5 + send_state_every: 10 + on_completely_new_value: + - lambda: |- + ESP_LOGD("WHAT", "Just a test %.1f", value); # GPIO A0 to acd # Here is space for any display implementation that could possibliy be. Have fun OK \ No newline at end of file diff --git a/external_components/analog_orp/analog_orp.cpp b/external_components/analog_orp/analog_orp.cpp index 6df9de6..1bd3470 100644 --- a/external_components/analog_orp/analog_orp.cpp +++ b/external_components/analog_orp/analog_orp.cpp @@ -5,25 +5,65 @@ namespace analog_orp { static const char *const TAG = "sensor.analog_orp"; +FloatAverager::FloatAverager(size_t size): size_(size), vector_(size_){ + this->index_ = 0; +} + +void FloatAverager::add_value(float value){ + vector_.at(index_++) = value; + if(index_ == size_){ + index_ = 0; + has_filled_once_ = true; + } +} +float FloatAverager::create_average(){ + float sum = 0; + for(auto point : vector_){ + sum += point; + } + return sum / (float) size_; +} +bool FloatAverager::isFull(){ + return index_ == 0; +} +bool FloatAverager::has_filled_once(){ + return has_filled_once_; +} + void ChlorineSensor::set_pin(InternalGPIOPin *pin){ - this->pin = pin; + this->pin_ = pin; } void ChlorineSensor::set_zero_point(int zero_point){ - this->zero_point_int = zero_point; - this->zero_point = ((float) zero_point / 1024.0f) * 3.3f; // Base Value for Clorine in Volt + this->zero_point_int_ = zero_point; + this->zero_point_ = ((float) zero_point / 1024.0f) * 3.3f; // Base Value for Clorine in Volt } void ChlorineSensor::set_print_raw(bool print_raw){ - this->print_raw = print_raw; + this->print_raw_ = print_raw; } void ChlorineSensor::set_inverted(bool inverted){ - this->inverted = inverted; + this->inverted_ = inverted; +} + +void ChlorineSensor::init_averager(int mesurements, int send_average_every){ + this->averager_ = new FloatAverager(mesurements); + this->send_averager_every_ = send_average_every; +} + +void ChlorineSensor::add_read_callback(std::function &&callback){ + this->on_value_read_.add(std::move(callback)); +} +void ChlorineSensor::add_average_change_callback(std::function &&callback){ + this->on_average_changed_.add(std::move(callback)); +} +void ChlorineSensor::add_new_value_callback(std::function &&callback){ + this->new_value_callback_.add(std::move(callback)); } void ChlorineSensor::setup(){ ESP_LOGCONFIG(TAG, "Setting up AnalogOrp '%s'...", this->get_name().c_str()); - pin->setup(); + pin_->setup(); ESP_LOGCONFIG(TAG, "AnalogOrp '%s' setup finished!", this->get_name().c_str()); } @@ -31,6 +71,31 @@ void ChlorineSensor::setup(){ void ChlorineSensor::update() { float value_v = this->sample(); ESP_LOGV(TAG, "'%s': Got orp value=%.4fmV", this->get_name().c_str(), value_v); + if(averager_ != nullptr){ + averager_->add_value(value_v); + on_value_read_.call(value_v); + + if(++averager_send_counter_ >= send_averager_every_) averager_send_counter_ = 0; + + if(!averager_->has_filled_once()) return; + float average = averager_->create_average(); + on_average_changed_.call(average); + + if(send_averager_every_ == -1){ + if(averager_->isFull()){ + this->publish_state(average); + } + } + else{ + if(averager_->isFull()){ + this->new_value_callback_.call(average); + } + if(averager_send_counter_ == 0){ + this->publish_state(average); + } + } + return; + } this->publish_state(value_v); } @@ -39,10 +104,10 @@ float ChlorineSensor::get_setup_priority() const { return setup_priority::DATA; void ChlorineSensor::dump_config(){ LOG_SENSOR("", "AnalogOrp Sensor", this); LOG_UPDATE_INTERVAL(this); - LOG_PIN(" Pin: ", pin); - ESP_LOGCONFIG(TAG, " Zero Point: %d", zero_point_int); - ESP_LOGCONFIG(TAG, " Inverted: %s", inverted ? "true" : "false"); - ESP_LOGCONFIG(TAG, " Print Raw: %s", print_raw ? "true" : "false"); + LOG_PIN(" Pin: ", pin_); + ESP_LOGCONFIG(TAG, " Zero Point: %d", zero_point_int_); + ESP_LOGCONFIG(TAG, " Inverted: %s", inverted_ ? "true" : "false"); + ESP_LOGCONFIG(TAG, " Print Raw: %s", print_raw_ ? "true" : "false"); } /* @@ -50,15 +115,15 @@ Nullwert : 680 */ float ChlorineSensor::sample(){ - float raw_read = (float) analogRead(pin->get_pin()); - if(print_raw){ + float raw_read = (float) analogRead(pin_->get_pin()); + if(print_raw_){ ESP_LOGD(TAG, "analogRead read a raw value of: %.0f", raw_read); } float raw_voltage = (raw_read / 1024.0f) * 3.3f; - float messured_voltage = raw_voltage - zero_point; + float messured_voltage = raw_voltage - zero_point_; - if(inverted){ + if(inverted_){ messured_voltage = messured_voltage * -1.0f; } diff --git a/external_components/analog_orp/analog_orp.h b/external_components/analog_orp/analog_orp.h index 3273458..6c210bd 100644 --- a/external_components/analog_orp/analog_orp.h +++ b/external_components/analog_orp/analog_orp.h @@ -4,28 +4,72 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" #include "esphome/core/gpio.h" +#include "esphome/core/automation.h" namespace esphome { namespace analog_orp { +class FloatAverager{ + public: + FloatAverager(size_t size); + void add_value(float value); + bool isFull(); + float create_average(); + bool has_filled_once(); + protected: + size_t size_; + std::vector vector_; + size_t index_; + bool has_filled_once_ = false; +}; + class ChlorineSensor: public PollingComponent, public sensor::Sensor { public: void set_pin(InternalGPIOPin *pin); void set_zero_point(int zero_point); void set_print_raw(bool print_raw); void set_inverted(bool inverted); + void add_read_callback(std::function &&callback); + void add_average_change_callback(std::function &&callback); + void add_new_value_callback(std::function &&callback); + void init_averager(int mesurements, int send_average_every); void setup() override; void update() override; float sample(); void dump_config() override; float get_setup_priority() const; protected: - InternalGPIOPin *pin; - bool inverted; - bool print_raw; - float zero_point; - int zero_point_int; - + InternalGPIOPin *pin_; + CallbackManager on_value_read_; + CallbackManager on_average_changed_; + CallbackManager new_value_callback_; + bool inverted_; + bool print_raw_; + float zero_point_; + int zero_point_int_; + FloatAverager *averager_; + bool use_averager_ = false; + int send_averager_every_ = -1; + int averager_send_counter_ = 0; +}; + +class ChlorineValueReadTrigger : public Trigger { + public: + explicit ChlorineValueReadTrigger(ChlorineSensor *parent) { + parent->add_read_callback([this](int value) { this->trigger(value); }); + } +}; +class ChlorineAverageChangeTrigger : public Trigger { + public: + explicit ChlorineAverageChangeTrigger(ChlorineSensor *parent) { + parent->add_average_change_callback([this](int value) { this->trigger(value); }); + } +}; +class ChlorineNewValueTrigger : public Trigger { + public: + explicit ChlorineNewValueTrigger(ChlorineSensor *parent) { + parent->add_new_value_callback([this](int value) { this->trigger(value); }); + } }; diff --git a/external_components/analog_orp/sensor.py b/external_components/analog_orp/sensor.py index af266a1..529e73c 100644 --- a/external_components/analog_orp/sensor.py +++ b/external_components/analog_orp/sensor.py @@ -1,11 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import sensor from esphome.components.adc import sensor as adc from esphome.const import ( CONF_ID, DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT + STATE_CLASS_MEASUREMENT, + CONF_TRIGGER_ID ) chlorine_sensor_ns = cg.esphome_ns.namespace('analog_orp') @@ -15,11 +17,27 @@ CONF_SENSOR_PIN = "pin" CONF_INVERTED = "inverted" CONF_PRINT_RAW = "print_raw" CONF_ZERO_POINT = "zero_point" +CONF_AVERAGE = "average" +CONF_AVERAGE_MESUREMENTS="mesurements" +CONF_SEND_STATE_EVERY="send_state_every" +CONF_ON_READ_VALUE = "on_value_read" +CONF_AVERAGE_ON_VALUE = "on_value" +CONF_AVERAGE_NEW = "on_completely_new_value" UNIT_MILLI_VOLT = "mV" ICON_LIGHTNING_BOLT="mdi:lightning-bolt" +ChlorineValueRead = chlorine_sensor_ns.class_( + "ChlorineValueReadTrigger", automation.Trigger.template(cg.float_) +) +ChlorineAverageChange = chlorine_sensor_ns.class_( + "ChlorineAverageChangeTrigger", automation.Trigger.template(cg.float_) +) +ChlorineNewValue = chlorine_sensor_ns.class_( + "ChlorineNewValueTrigger", automation.Trigger.template(cg.float_) +) + CONFIG_SCHEMA = cv.All( sensor.sensor_schema( ChlorineSensor, @@ -32,19 +50,50 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_SENSOR_PIN): adc.validate_adc_pin, cv.Required(CONF_ZERO_POINT): cv.Range(min=0, max=1023), cv.Optional(CONF_INVERTED, False): cv.boolean, - cv.Optional(CONF_PRINT_RAW, False): cv.boolean + cv.Optional(CONF_PRINT_RAW, False): cv.boolean, + cv.Optional(CONF_AVERAGE): cv.Schema({ + cv.Required(CONF_AVERAGE_MESUREMENTS): cv.int_range(min=1), + cv.Optional(CONF_SEND_STATE_EVERY): cv.int_range(min=1), + cv.Optional(CONF_ON_READ_VALUE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineValueRead), + }), + cv.Optional(CONF_AVERAGE_ON_VALUE): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineAverageChange), + }), + cv.Optional(CONF_AVERAGE_NEW): automation.validate_automation({ + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChlorineNewValue), + }), + }), } ).extend(cv.polling_component_schema("60s")) ) -def to_code(config): +async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield sensor.register_sensor(var, config) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) - sensor_pin = yield cg.gpio_pin_expression(config[CONF_SENSOR_PIN]) + sensor_pin = await cg.gpio_pin_expression(config[CONF_SENSOR_PIN]) cg.add(var.set_pin(sensor_pin)) cg.add(var.set_inverted(config[CONF_INVERTED])) cg.add(var.set_print_raw(config[CONF_PRINT_RAW])) - cg.add(var.set_zero_point(config[CONF_ZERO_POINT])) \ No newline at end of file + cg.add(var.set_zero_point(config[CONF_ZERO_POINT])) + + if averager_config := config.get(CONF_AVERAGE): + update_each = -1 + if CONF_SEND_STATE_EVERY in averager_config: + update_each = averager_config[CONF_SEND_STATE_EVERY] + cg.add(var.init_averager(averager_config[CONF_AVERAGE_MESUREMENTS], update_each)) + + for conf in averager_config.get(CONF_ON_READ_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "value")], conf) + + for conf in averager_config.get(CONF_AVERAGE_ON_VALUE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "value")], conf) + + for conf in averager_config.get(CONF_AVERAGE_NEW, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "value")], conf) \ No newline at end of file