#pragma once #include "esphome/core/automation.h" #include "esphome/components/output/binary_output.h" #include "Arduino.h" static const char *const TAG = "chlorine_pump"; namespace esphome { namespace chlorine_pump { class ChlorinePump : public Component { protected: sensor::Sensor *sensor_; output::BinaryOutput *pump_out; int cycle_time; int prop_band; bool state = true; unsigned long last_action; bool disable_clock_ = false; CallbackManager callback_pump_; CallbackManager callback_cycle_; int tOn = 0; int tOff = 0; float target_ = -1; float tOn_divider_; std::function target_lambda_ = nullptr; int min_on_time_ = 0; int max_on_time_ = -1; float on_time_multiplier_ = 1; int on_time_offset_ = 0; int ignore_max_x_below_target_ = -1; public: ChlorinePump(){ last_action = 0; } void disable_clock(bool disable_clock){ this->disable_clock_ = disable_clock; } void set_sensor(sensor::Sensor *sensor){ this->sensor_ = sensor; } void set_pump_time_divivder(float pump_time_divider){ this->tOn_divider_ = pump_time_divider; } void set_pump_out(output::BinaryOutput *pump_out){ this->pump_out = pump_out; } void set_cycle_time(int time_in_sec){ this->cycle_time = time_in_sec; } void set_prop_band(int prop_band){ this->prop_band = prop_band; } void set_target(float target){ this->target_ = target; } // Cycle Time Modifiers void set_min_on_time(int min_on_time){ this->min_on_time_ = min_on_time; } void set_max_on_time(int max_on_time){ this->max_on_time_ = max_on_time; } void set_time_multiplier(float time_multi){ this->on_time_multiplier_ = time_multi; } void set_time_offset(int offset){ this->on_time_offset_ = offset; } void set_ignore_max_x_below_target(int set_below){ this->ignore_max_x_below_target_ = set_below; } void setup() override { last_action = millis(); if(disable_clock_ && sensor_ != nullptr){ sensor_->add_on_state_callback([=](float val) -> void { this->tick_time(val); }); } } void set_target_lambda(std::function callback){ target_lambda_ = std::move(callback); } void prime() { tOn = 30; tOff = 2; this->pump_out->turn_on(); } void start() { this->state = true; } void stop() { this->state = false; tOn = 0; tOff = 0; this->pump_out->turn_off(); } bool get_state(){ return state; } void setMillisPrecies(unsigned long waitPeriod){ if(last_action + waitPeriod + waitPeriod > millis()){ last_action += waitPeriod; } else{ ESP_LOGW(TAG, "Reset millis(). Did the system experience a halt for some reason?"); last_action = millis(); } } int calculatePumpTimeSeconds(float last_mesurement){ float used_target = -1; if(target_ != -1){ used_target = target_; } else{ used_target = target_lambda_(); } if(last_mesurement >= used_target) return 0; float currentVal = used_target - last_mesurement; float proportionalValue = (currentVal / (float) prop_band); float timeInSeconds = proportionalValue * (float) cycle_time; if(timeInSeconds > cycle_time) timeInSeconds = cycle_time; return (int) timeInSeconds; } int calculate_changed_on_time(int tOn, float last_mesurement){ if(ignore_max_x_below_target_ != -1 && (float)(target_ - ignore_max_x_below_target_) >= last_mesurement){ return cycle_time; } tOn = (int)((float) tOn * on_time_multiplier_); tOn += on_time_offset_; if(tOn < min_on_time_) tOn = min_on_time_; if(max_on_time_ != -1 && tOn > max_on_time_) tOn = max_on_time_; return tOn; } void add_on_pump_callback(std::function &&callback){ this->callback_pump_.add(std::move(callback)); } void add_on_cycle_callback(std::function &&callback){ this->callback_cycle_.add(std::move(callback)); } void tick_time(float last_mesurement){ if(tOn == 0 && tOff == 0 && state){ int seconds = calculatePumpTimeSeconds(last_mesurement); if(seconds != 0) tOn = calculate_changed_on_time(seconds, last_mesurement); else tOn = 0; tOff = cycle_time - tOn; if(seconds == 0){ tOn = 0; tOff = 360; } ESP_LOGD(TAG, "Time => tOn: %d, tOff: %d", tOn, tOff); this->callback_cycle_.call(tOn, tOff); } else if(tOn > 0) { this->callback_pump_.call(true, tOn); --tOn; pump_out->turn_on(); } else if(tOff > 0) { this->callback_pump_.call(false, tOff); --tOff; pump_out->turn_off(); } } void loop() override{ if(disable_clock_) return; if(millis() - last_action > 1000){ if(sensor_->has_state()) tick_time(sensor_->get_state()); setMillisPrecies(1000); } } void dump_config() override { ESP_LOGCONFIG(TAG, "Chlorine_pump controller"); ESP_LOGCONFIG(TAG, " Ticker: %s", disable_clock_ ? "using on_value from sensor" : "using internal"); //LOG_BINARY_OUTPUT(pump_out); ESP_LOGCONFIG(TAG, " Cycle Time: %ds", cycle_time); ESP_LOGCONFIG(TAG, " Proportional Band: %d", prop_band); if(target_ != -1) ESP_LOGCONFIG(TAG, " Target: %.0fmV", target_); else ESP_LOGCONFIG(TAG, " Target: Using get_target lambda"); } }; class ChlorSensorOutputTrigger : public Trigger { public: explicit ChlorSensorOutputTrigger(ChlorinePump *parent) { parent->add_on_pump_callback([this](bool output_state, int value) { this->trigger(output_state, value); }); } }; class ChlorSensorCycleTrigger : public Trigger { public: explicit ChlorSensorCycleTrigger(ChlorinePump *parent) { parent->add_on_cycle_callback([this](int on_time, int off_time) { this->trigger(on_time, off_time); }); } }; template class ChlorinePrime : public Action { public: ChlorinePrime(ChlorinePump *pump) : pump_(pump) {} void play(Ts... x) override { this->pump_->prime(); } protected: ChlorinePump *pump_; }; template class ChlorineStart : public Action { public: ChlorineStart(ChlorinePump *pump) : pump_(pump) {} void play(Ts... x) override { this->pump_->start(); } protected: ChlorinePump *pump_; }; template class ChlorineStop : public Action { public: ChlorineStop(ChlorinePump *pump) : pump_(pump) {} void play(Ts... x) override { this->pump_->stop(); } protected: ChlorinePump *pump_; }; template class ChlorineSetTarget : public Action { public: ChlorineSetTarget(ChlorinePump *pump) : pump_(pump) {} TEMPLATABLE_VALUE(float, value) void play(Ts... x) override { this->pump_->set_target((int) this->value_.value(x...)); } protected: ChlorinePump *pump_; }; } }